diff --git a/catalyst_voices/lib/dependency/dependencies.dart b/catalyst_voices/lib/dependency/dependencies.dart index 3c9a6b4ba0..713343dc16 100644 --- a/catalyst_voices/lib/dependency/dependencies.dart +++ b/catalyst_voices/lib/dependency/dependencies.dart @@ -27,7 +27,9 @@ final class Dependencies extends DependencyProvider { authenticationRepository: get(), ), ) - ..registerLazySingleton(SessionBloc.new); + ..registerLazySingleton(SessionBloc.new) + // Factory will rebuild it each time needed + ..registerFactory(RegistrationBloc.new); } void _registerRepositories() { diff --git a/catalyst_voices/lib/pages/registration/create_keychain/create_keychain_panel.dart b/catalyst_voices/lib/pages/registration/create_keychain/create_keychain_panel.dart new file mode 100644 index 0000000000..da7629c063 --- /dev/null +++ b/catalyst_voices/lib/pages/registration/create_keychain/create_keychain_panel.dart @@ -0,0 +1,28 @@ +import 'package:catalyst_voices/pages/registration/create_keychain/stage/stages.dart'; +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:flutter/material.dart'; + +class CreateKeychainPanel extends StatelessWidget { + final CreateKeychainStage stage; + + const CreateKeychainPanel({ + super.key, + required this.stage, + }); + + @override + Widget build(BuildContext context) { + return switch (stage) { + CreateKeychainStage.splash => const SplashPanel(), + CreateKeychainStage.instructions => const InstructionsPanel(), + CreateKeychainStage.seedPhrase || + CreateKeychainStage.checkSeedPhraseInstructions || + CreateKeychainStage.checkSeedPhrase || + CreateKeychainStage.checkSeedPhraseResult || + CreateKeychainStage.unlockPasswordInstructions || + CreateKeychainStage.unlockPasswordCreate || + CreateKeychainStage.created => + const Placeholder(), + }; + } +} diff --git a/catalyst_voices/lib/pages/registration/create_keychain/stage/instructions_panel.dart b/catalyst_voices/lib/pages/registration/create_keychain/stage/instructions_panel.dart new file mode 100644 index 0000000000..c09e7b281a --- /dev/null +++ b/catalyst_voices/lib/pages/registration/create_keychain/stage/instructions_panel.dart @@ -0,0 +1,59 @@ +import 'package:catalyst_voices/widgets/widgets.dart'; +import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; +import 'package:catalyst_voices_brands/catalyst_voices_brands.dart'; +import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; +import 'package:flutter/material.dart'; + +class InstructionsPanel extends StatelessWidget { + const InstructionsPanel({super.key}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final textColor = theme.colors.textOnPrimaryLevel0; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + context.l10n.accountInstructionsTitle, + style: theme.textTheme.titleMedium?.copyWith(color: textColor), + ), + const SizedBox(height: 24), + Text( + context.l10n.accountInstructionsMessage, + style: theme.textTheme.bodyMedium?.copyWith(color: textColor), + ), + const Spacer(), + const _Navigation(), + ], + ); + } +} + +class _Navigation extends StatelessWidget { + const _Navigation(); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: VoicesBackButton( + onTap: () { + RegistrationBloc.of(context).add(const PreviousStepEvent()); + }, + ), + ), + const SizedBox(width: 10), + Expanded( + child: VoicesNextButton( + onTap: () { + RegistrationBloc.of(context).add(const NextStepEvent()); + }, + ), + ), + ], + ); + } +} diff --git a/catalyst_voices/lib/pages/registration/create_keychain/stage/splash_panel.dart b/catalyst_voices/lib/pages/registration/create_keychain/stage/splash_panel.dart new file mode 100644 index 0000000000..484cb67d31 --- /dev/null +++ b/catalyst_voices/lib/pages/registration/create_keychain/stage/splash_panel.dart @@ -0,0 +1,37 @@ +import 'package:catalyst_voices/widgets/widgets.dart'; +import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; +import 'package:catalyst_voices_brands/catalyst_voices_brands.dart'; +import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; +import 'package:flutter/material.dart'; + +class SplashPanel extends StatelessWidget { + const SplashPanel({super.key}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final textColor = theme.colors.textOnPrimaryLevel0; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + context.l10n.accountCreationSplashTitle, + style: theme.textTheme.titleMedium?.copyWith(color: textColor), + ), + const SizedBox(height: 24), + Text( + context.l10n.accountCreationSplashMessage, + style: theme.textTheme.bodyMedium?.copyWith(color: textColor), + ), + const Spacer(), + VoicesFilledButton( + child: Text(context.l10n.accountCreationSplashNextButton), + onTap: () { + RegistrationBloc.of(context).add(const NextStepEvent()); + }, + ), + ], + ); + } +} diff --git a/catalyst_voices/lib/pages/registration/create_keychain/stage/stages.dart b/catalyst_voices/lib/pages/registration/create_keychain/stage/stages.dart new file mode 100644 index 0000000000..69b95fc8bc --- /dev/null +++ b/catalyst_voices/lib/pages/registration/create_keychain/stage/stages.dart @@ -0,0 +1,2 @@ +export 'instructions_panel.dart'; +export 'splash_panel.dart'; diff --git a/catalyst_voices/lib/pages/account/creation/get_started/account_create_dialog.dart b/catalyst_voices/lib/pages/registration/get_started/get_started_panel.dart similarity index 64% rename from catalyst_voices/lib/pages/account/creation/get_started/account_create_dialog.dart rename to catalyst_voices/lib/pages/registration/get_started/get_started_panel.dart index ea69f13db6..30c15365c7 100644 --- a/catalyst_voices/lib/pages/account/creation/get_started/account_create_dialog.dart +++ b/catalyst_voices/lib/pages/registration/get_started/get_started_panel.dart @@ -1,79 +1,13 @@ -import 'package:catalyst_voices/pages/account/creation/task_picture.dart'; -import 'package:catalyst_voices/widgets/buttons/voices_buttons.dart'; -import 'package:catalyst_voices/widgets/modals/voices_desktop_dialog.dart'; -import 'package:catalyst_voices/widgets/modals/voices_dialog.dart'; import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; +import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; import 'package:catalyst_voices_brands/catalyst_voices_brands.dart'; import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; import 'package:flutter/material.dart'; -enum AccountCreateType { - createNew, - recover; - - SvgGenImage get _icon => switch (this) { - AccountCreateType.createNew => VoicesAssets.icons.colorSwatch, - AccountCreateType.recover => VoicesAssets.icons.download, - }; - - String _getTitle(VoicesLocalizations l10n) => switch (this) { - AccountCreateType.createNew => l10n.accountCreationCreate, - AccountCreateType.recover => l10n.accountCreationRecover, - }; - - String _getSubtitle(VoicesLocalizations l10n) { - return l10n.accountCreationOnThisDevice; - } -} - -class AccountCreateDialog extends StatelessWidget { - const AccountCreateDialog._(); - - static Future show(BuildContext context) { - return VoicesDialog.show( - context: context, - builder: (context) => const AccountCreateDialog._(), - ); - } - - @override - Widget build(BuildContext context) { - return const VoicesDesktopPanelsDialog( - left: _LeftPanel(), - right: _RightPanel(), - ); - } -} - -class _LeftPanel extends StatelessWidget { - const _LeftPanel(); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.l10n.getStarted, - style: theme.textTheme.titleLarge?.copyWith( - color: theme.colors.textOnPrimaryLevel0, - ), - ), - const SizedBox(height: 12), - const Expanded(child: Center(child: TaskKeychainPicture())), - const SizedBox(height: 32), - // TODO(damian-molinski): External url redirect - VoicesLearnMoreButton(onTap: () {}), - ], - ); - } -} - -class _RightPanel extends StatelessWidget { - const _RightPanel(); +class GetStartedPanel extends StatelessWidget { + const GetStartedPanel({super.key}); @override Widget build(BuildContext context) { @@ -106,12 +40,15 @@ class _RightPanel extends StatelessWidget { const SizedBox(height: 24), Column( mainAxisSize: MainAxisSize.min, - children: AccountCreateType.values + children: CreateAccountType.values .map((type) { - return _AccountCreateTypeTile( + return _CreateAccountTypeTile( key: ValueKey(type), type: type, - onTap: () => Navigator.of(context).pop(type), + onTap: () { + final event = CreateAccountTypeEvent(type: type); + RegistrationBloc.of(context).add(event); + }, ); }) .separatedBy(const SizedBox(height: 12)) @@ -122,11 +59,11 @@ class _RightPanel extends StatelessWidget { } } -class _AccountCreateTypeTile extends StatelessWidget { - final AccountCreateType type; +class _CreateAccountTypeTile extends StatelessWidget { + final CreateAccountType type; final VoidCallback? onTap; - const _AccountCreateTypeTile({ + const _CreateAccountTypeTile({ super.key, required this.type, this.onTap, @@ -185,3 +122,19 @@ class _AccountCreateTypeTile extends StatelessWidget { ); } } + +extension _CreateAccountTypeExt on CreateAccountType { + SvgGenImage get _icon => switch (this) { + CreateAccountType.createNew => VoicesAssets.icons.colorSwatch, + CreateAccountType.recover => VoicesAssets.icons.download, + }; + + String _getTitle(VoicesLocalizations l10n) => switch (this) { + CreateAccountType.createNew => l10n.accountCreationCreate, + CreateAccountType.recover => l10n.accountCreationRecover, + }; + + String _getSubtitle(VoicesLocalizations l10n) { + return l10n.accountCreationOnThisDevice; + } +} diff --git a/catalyst_voices/lib/pages/registration/information_panel.dart b/catalyst_voices/lib/pages/registration/information_panel.dart new file mode 100644 index 0000000000..4d15bc3539 --- /dev/null +++ b/catalyst_voices/lib/pages/registration/information_panel.dart @@ -0,0 +1,109 @@ +import 'package:catalyst_voices/widgets/widgets.dart'; +import 'package:catalyst_voices_brands/catalyst_voices_brands.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; +import 'package:flutter/material.dart'; + +class InformationPanel extends StatelessWidget { + final String title; + final String? subtitle; + final String? body; + final Widget picture; + final double? progress; + + const InformationPanel({ + super.key, + required this.title, + this.subtitle, + this.body, + required this.picture, + this.progress, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _Header( + title: title, + subtitle: subtitle, + body: body, + ), + const SizedBox(height: 12), + Expanded(child: Center(child: picture)), + const SizedBox(height: 12), + _Footer( + progress: progress, + ), + ], + ); + } +} + +class _Header extends StatelessWidget { + final String title; + final String? subtitle; + final String? body; + + const _Header({ + required this.title, + this.subtitle, + this.body, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final textColor = theme.colors.textOnPrimaryLevel0; + + final subtitle = this.subtitle; + final body = this.body; + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: theme.textTheme.titleLarge?.copyWith(color: textColor), + ), + if (subtitle != null) + Text( + subtitle, + style: theme.textTheme.titleMedium?.copyWith(color: textColor), + ), + if (body != null) + Text( + body, + style: theme.textTheme.bodyMedium?.copyWith(color: textColor), + ), + ].separatedBy(const SizedBox(height: 12)).toList(), + ); + } +} + +class _Footer extends StatelessWidget { + final double? progress; + + const _Footer({ + this.progress, + }); + + @override + Widget build(BuildContext context) { + final progress = this.progress; + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Offstage( + offstage: progress == null, + child: VoicesLinearProgressIndicator(value: progress ?? 0), + ), + const SizedBox(height: 10), + VoicesLearnMoreButton(onTap: () {}), + ], + ); + } +} diff --git a/catalyst_voices/lib/pages/registration/link_wallet/intro/link_wallet_intro_dialog.dart b/catalyst_voices/lib/pages/registration/link_wallet/intro/link_wallet_intro_dialog.dart deleted file mode 100644 index 066cc83863..0000000000 --- a/catalyst_voices/lib/pages/registration/link_wallet/intro/link_wallet_intro_dialog.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:catalyst_voices/pages/account/creation/task_picture.dart'; -import 'package:catalyst_voices/widgets/widgets.dart'; -import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; -import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; -import 'package:flutter/material.dart'; - -/// The initial screen for the link wallet flow during registration. -class LinkWalletIntroDialog extends StatelessWidget { - final VoidCallback onSelectWallet; - - const LinkWalletIntroDialog({ - super.key, - required this.onSelectWallet, - }); - - @override - Widget build(BuildContext context) { - return VoicesDesktopPanelsDialog( - left: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.l10n.walletLink_header, - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 12), - Text( - context.l10n.walletLink_subheader, - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 50), - const TaskKeychainPicture(), - const Spacer(), - VoicesLearnMoreButton( - onTap: () {}, - ), - ], - ), - right: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox(height: 24), - Text( - context.l10n.walletLink_intro_title, - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 24), - Text( - context.l10n.walletLink_intro_content, - style: Theme.of(context).textTheme.bodyMedium, - ), - const Spacer(), - VoicesFilledButton( - leading: VoicesAssets.icons.wallet.buildIcon(), - onTap: onSelectWallet, - child: Text(context.l10n.chooseCardanoWallet), - ), - ], - ), - ); - } -} diff --git a/catalyst_voices/lib/pages/registration/link_wallet/link_wallet_dialog.dart b/catalyst_voices/lib/pages/registration/link_wallet/link_wallet_dialog.dart deleted file mode 100644 index edfd4312ec..0000000000 --- a/catalyst_voices/lib/pages/registration/link_wallet/link_wallet_dialog.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:catalyst_cardano/catalyst_cardano.dart'; -import 'package:catalyst_voices/pages/registration/link_wallet/intro/link_wallet_intro_dialog.dart'; -import 'package:catalyst_voices/pages/registration/link_wallet/link_wallet_stage.dart'; -import 'package:catalyst_voices/pages/registration/link_wallet/select_wallet/select_wallet_dialog.dart'; -import 'package:catalyst_voices/widgets/modals/voices_dialog.dart'; -import 'package:flutter/material.dart'; - -/// The link wallet flow consisting -/// of [LinkWalletStage]'s during the registration. -class LinkWalletDialog extends StatefulWidget { - const LinkWalletDialog._(); - - /// Shows the [LinkWalletDialog] flow. - static Future show({required BuildContext context}) { - return VoicesDialog.show( - context: context, - routeSettings: const RouteSettings(name: '/register/link-wallet'), - builder: (context) => const LinkWalletDialog._(), - ); - } - - @override - State createState() => _LinkWalletDialogState(); -} - -class _LinkWalletDialogState extends State { - LinkWalletStage _stage = LinkWalletStage.intro; - - @override - Widget build(BuildContext context) { - return switch (_stage) { - LinkWalletStage.intro => LinkWalletIntroDialog( - onSelectWallet: _onSelectWallet, - ), - LinkWalletStage.selectWallet => SelectWalletDialog( - onSelectedWallet: _onSelectedWallet, - ), - }; - } - - void _onSelectWallet() { - setState(() { - _stage = LinkWalletStage.selectWallet; - }); - } - - void _onSelectedWallet(CardanoWallet wallet) { - // TODO(dtscalac): store selected wallet and proceed to next stage - } -} diff --git a/catalyst_voices/lib/pages/registration/link_wallet/link_wallet_stage.dart b/catalyst_voices/lib/pages/registration/link_wallet/link_wallet_stage.dart deleted file mode 100644 index 4ca59a842e..0000000000 --- a/catalyst_voices/lib/pages/registration/link_wallet/link_wallet_stage.dart +++ /dev/null @@ -1,8 +0,0 @@ -/// Describes the link wallet flow during registration. -enum LinkWalletStage { - /// The welcome screen for the link wallet flow. - intro, - - /// A screen where the user is asked to connect the cardano wallet. - selectWallet, -} diff --git a/catalyst_voices/lib/pages/registration/link_wallet/select_wallet/select_wallet_dialog.dart b/catalyst_voices/lib/pages/registration/link_wallet/select_wallet/select_wallet_dialog.dart deleted file mode 100644 index 6799dafcff..0000000000 --- a/catalyst_voices/lib/pages/registration/link_wallet/select_wallet/select_wallet_dialog.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:catalyst_cardano/catalyst_cardano.dart'; -import 'package:catalyst_voices/widgets/widgets.dart'; -import 'package:flutter/material.dart'; - -// TODO(dtscalac): add content for the screen -class SelectWalletDialog extends StatelessWidget { - final ValueChanged onSelectedWallet; - - const SelectWalletDialog({ - super.key, - required this.onSelectedWallet, - }); - - @override - Widget build(BuildContext context) { - return const VoicesDesktopPanelsDialog( - left: Column( - children: [], - ), - right: Column( - children: [], - ), - ); - } -} diff --git a/catalyst_voices/lib/pages/registration/registration_dialog.dart b/catalyst_voices/lib/pages/registration/registration_dialog.dart new file mode 100644 index 0000000000..f2115810bd --- /dev/null +++ b/catalyst_voices/lib/pages/registration/registration_dialog.dart @@ -0,0 +1,57 @@ +import 'package:catalyst_voices/dependency/dependencies.dart'; +import 'package:catalyst_voices/pages/registration/create_keychain/create_keychain_panel.dart'; +import 'package:catalyst_voices/pages/registration/get_started/get_started_panel.dart'; +import 'package:catalyst_voices/pages/registration/registration_info_panel.dart'; +import 'package:catalyst_voices/pages/registration/wallet_link/wallet_link_panel.dart'; +import 'package:catalyst_voices/widgets/widgets.dart'; +import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class RegistrationDialog extends StatelessWidget { + const RegistrationDialog._(); + + static Future show(BuildContext context) { + return VoicesDialog.show( + context: context, + routeSettings: const RouteSettings(name: '/registration'), + builder: (context) => const RegistrationDialog._(), + ); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => Dependencies.instance.get(), + child: BlocBuilder( + builder: (context, state) { + return _RegistrationDialog(state: state); + }, + ), + ); + } +} + +class _RegistrationDialog extends StatelessWidget { + final RegistrationState state; + + const _RegistrationDialog({ + required this.state, + }); + + @override + Widget build(BuildContext context) { + return VoicesDesktopPanelsDialog( + left: RegistrationInfoPanel( + state: state, + ), + right: switch (state) { + GetStarted() => const GetStartedPanel(), + FinishAccountCreation() => const Placeholder(), + Recover() => const Placeholder(), + CreateKeychain(:final stage) => CreateKeychainPanel(stage: stage), + WalletLink(:final stage) => WalletLinkPanel(stage: stage), + }, + ); + } +} diff --git a/catalyst_voices/lib/pages/registration/registration_info_panel.dart b/catalyst_voices/lib/pages/registration/registration_info_panel.dart new file mode 100644 index 0000000000..85842d255f --- /dev/null +++ b/catalyst_voices/lib/pages/registration/registration_info_panel.dart @@ -0,0 +1,94 @@ +import 'package:catalyst_voices/pages/registration/information_panel.dart'; +import 'package:catalyst_voices/pages/registration/task_picture.dart'; +import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; +import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:flutter/material.dart'; + +class _HeaderStrings { + final String title; + final String? subtitle; + final String? body; + + _HeaderStrings({ + required this.title, + this.subtitle, + this.body, + }); +} + +class RegistrationInfoPanel extends StatelessWidget { + final RegistrationState state; + + const RegistrationInfoPanel({ + super.key, + required this.state, + }); + + @override + Widget build(BuildContext context) { + final headerStrings = _buildHeaderStrings(context); + + return InformationPanel( + title: headerStrings.title, + subtitle: headerStrings.subtitle, + body: headerStrings.body, + picture: const TaskKeychainPicture(), + ); + } + + _HeaderStrings _buildHeaderStrings(BuildContext context) { + _HeaderStrings buildKeychainStageHeader(CreateKeychainStage stage) { + return switch (stage) { + CreateKeychainStage.splash || + CreateKeychainStage.instructions => + _HeaderStrings(title: context.l10n.catalystKeychain), + + // TODO(damian-molinski): Extract to l10n in next step + CreateKeychainStage.seedPhrase => _HeaderStrings( + title: 'Catalyst Keychain', + subtitle: 'Write down your 12 Catalyst security words', + body: 'Make sure you create an offline backup ' + 'of your recovery phrase as well.', + ), + CreateKeychainStage.checkSeedPhraseInstructions || + CreateKeychainStage.checkSeedPhrase || + CreateKeychainStage.checkSeedPhraseResult || + CreateKeychainStage.unlockPasswordInstructions || + CreateKeychainStage.unlockPasswordCreate || + CreateKeychainStage.created => + _HeaderStrings(title: 'TODO'), + }; + } + + _HeaderStrings buildWalletLinkStageHeader(WalletLinkStage stage) { + return switch (stage) { + WalletLinkStage.intro || + WalletLinkStage.selectWallet || + WalletLinkStage.walletDetails => + _HeaderStrings( + title: context.l10n.walletLinkHeader, + subtitle: context.l10n.walletLinkWalletSubheader, + ), + WalletLinkStage.rolesChooser || + WalletLinkStage.rolesSummary => + _HeaderStrings( + title: context.l10n.walletLinkHeader, + subtitle: context.l10n.walletLinkRolesSubheader, + ), + WalletLinkStage.rbacTransaction => _HeaderStrings( + title: context.l10n.walletLinkHeader, + subtitle: context.l10n.walletLinkTransactionSubheader, + ), + }; + } + + return switch (state) { + GetStarted() => _HeaderStrings(title: context.l10n.getStarted), + FinishAccountCreation() => _HeaderStrings(title: 'TODO'), + Recover() => _HeaderStrings(title: 'TODO'), + CreateKeychain(:final stage) => buildKeychainStageHeader(stage), + WalletLink(:final stage) => buildWalletLinkStageHeader(stage), + }; + } +} diff --git a/catalyst_voices/lib/pages/account/creation/task_picture.dart b/catalyst_voices/lib/pages/registration/task_picture.dart similarity index 100% rename from catalyst_voices/lib/pages/account/creation/task_picture.dart rename to catalyst_voices/lib/pages/registration/task_picture.dart diff --git a/catalyst_voices/lib/pages/registration/wallet_link/intro/intro_panel.dart b/catalyst_voices/lib/pages/registration/wallet_link/intro/intro_panel.dart new file mode 100644 index 0000000000..d61ce2af09 --- /dev/null +++ b/catalyst_voices/lib/pages/registration/wallet_link/intro/intro_panel.dart @@ -0,0 +1,36 @@ +import 'package:catalyst_voices/widgets/widgets.dart'; +import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; +import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; +import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; +import 'package:flutter/material.dart'; + +class IntroPanel extends StatelessWidget { + const IntroPanel({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 24), + Text( + context.l10n.walletLinkIntroTitle, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 24), + Text( + context.l10n.walletLinkIntroContent, + style: Theme.of(context).textTheme.bodyMedium, + ), + const Spacer(), + VoicesFilledButton( + leading: VoicesAssets.icons.wallet.buildIcon(), + onTap: () { + RegistrationBloc.of(context).add(const NextStepEvent()); + }, + child: Text(context.l10n.chooseCardanoWallet), + ), + ], + ); + } +} diff --git a/catalyst_voices/lib/pages/registration/wallet_link/rbac_transaction/rbac_transaction_panel.dart b/catalyst_voices/lib/pages/registration/wallet_link/rbac_transaction/rbac_transaction_panel.dart new file mode 100644 index 0000000000..4848892b93 --- /dev/null +++ b/catalyst_voices/lib/pages/registration/wallet_link/rbac_transaction/rbac_transaction_panel.dart @@ -0,0 +1,34 @@ +import 'package:catalyst_voices/widgets/buttons/voices_filled_button.dart'; +import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; +import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; +import 'package:flutter/material.dart'; + +// TODO(dtscalac): define content +class RbacTransactionPanel extends StatelessWidget { + const RbacTransactionPanel({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Spacer(), + VoicesFilledButton( + leading: VoicesAssets.icons.wallet.buildIcon(), + onTap: () { + RegistrationBloc.of(context).add(const PreviousStepEvent()); + }, + child: const Text('Previous'), + ), + const SizedBox(height: 12), + VoicesFilledButton( + leading: VoicesAssets.icons.wallet.buildIcon(), + onTap: () { + RegistrationBloc.of(context).add(const NextStepEvent()); + }, + child: const Text('Next'), + ), + ], + ); + } +} diff --git a/catalyst_voices/lib/pages/registration/wallet_link/roles_chooser/roles_chooser_panel.dart b/catalyst_voices/lib/pages/registration/wallet_link/roles_chooser/roles_chooser_panel.dart new file mode 100644 index 0000000000..5fbf8589df --- /dev/null +++ b/catalyst_voices/lib/pages/registration/wallet_link/roles_chooser/roles_chooser_panel.dart @@ -0,0 +1,34 @@ +import 'package:catalyst_voices/widgets/buttons/voices_filled_button.dart'; +import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; +import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; +import 'package:flutter/material.dart'; + +// TODO(dtscalac): define content +class RolesChooserPanel extends StatelessWidget { + const RolesChooserPanel({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Spacer(), + VoicesFilledButton( + leading: VoicesAssets.icons.wallet.buildIcon(), + onTap: () { + RegistrationBloc.of(context).add(const PreviousStepEvent()); + }, + child: const Text('Previous'), + ), + const SizedBox(height: 12), + VoicesFilledButton( + leading: VoicesAssets.icons.wallet.buildIcon(), + onTap: () { + RegistrationBloc.of(context).add(const NextStepEvent()); + }, + child: const Text('Next'), + ), + ], + ); + } +} diff --git a/catalyst_voices/lib/pages/registration/wallet_link/roles_summary/roles_summary_panel.dart b/catalyst_voices/lib/pages/registration/wallet_link/roles_summary/roles_summary_panel.dart new file mode 100644 index 0000000000..c8af179f27 --- /dev/null +++ b/catalyst_voices/lib/pages/registration/wallet_link/roles_summary/roles_summary_panel.dart @@ -0,0 +1,34 @@ +import 'package:catalyst_voices/widgets/buttons/voices_filled_button.dart'; +import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; +import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; +import 'package:flutter/material.dart'; + +// TODO(dtscalac): define content +class RolesSummaryPanel extends StatelessWidget { + const RolesSummaryPanel({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Spacer(), + VoicesFilledButton( + leading: VoicesAssets.icons.wallet.buildIcon(), + onTap: () { + RegistrationBloc.of(context).add(const PreviousStepEvent()); + }, + child: const Text('Previous'), + ), + const SizedBox(height: 12), + VoicesFilledButton( + leading: VoicesAssets.icons.wallet.buildIcon(), + onTap: () { + RegistrationBloc.of(context).add(const NextStepEvent()); + }, + child: const Text('Next'), + ), + ], + ); + } +} diff --git a/catalyst_voices/lib/pages/registration/wallet_link/select_wallet/select_wallet_panel.dart b/catalyst_voices/lib/pages/registration/wallet_link/select_wallet/select_wallet_panel.dart new file mode 100644 index 0000000000..f240e8d37c --- /dev/null +++ b/catalyst_voices/lib/pages/registration/wallet_link/select_wallet/select_wallet_panel.dart @@ -0,0 +1,148 @@ +import 'dart:async'; + +import 'package:catalyst_cardano/catalyst_cardano.dart'; +import 'package:catalyst_voices/widgets/widgets.dart'; +import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; +import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; +import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:result_type/result_type.dart'; + +class SelectWalletPanel extends StatelessWidget { + const SelectWalletPanel({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 24), + Text( + context.l10n.walletLinkSelectWalletTitle, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 24), + Text( + context.l10n.walletLinkSelectWalletContent, + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: 40), + const Expanded(child: _Wallets()), + const SizedBox(height: 24), + VoicesBackButton( + onTap: () { + RegistrationBloc.of(context).add(const PreviousStepEvent()); + }, + ), + const SizedBox(height: 10), + VoicesTextButton( + trailing: VoicesAssets.icons.externalLink.buildIcon(), + onTap: () {}, + child: Text(context.l10n.seeAllSupportedWallets), + ), + ], + ); + } +} + +class _Wallets extends StatefulWidget { + const _Wallets(); + + @override + State<_Wallets> createState() => _WalletsState(); +} + +class _WalletsState extends State<_Wallets> { + @override + void initState() { + super.initState(); + + final bloc = RegistrationBloc.of(context); + if (bloc.cardanoWallets.value == null) { + unawaited(bloc.refreshCardanoWallets()); + } + } + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: RegistrationBloc.of(context).cardanoWallets, + builder: (context, result, _) { + return switch (result) { + Success(:final value) => value.isNotEmpty + ? _WalletsList(wallets: value) + : _WalletsEmpty(onRetry: _onRetry), + Failure() => _WalletsError(onRetry: _onRetry), + _ => const Center(child: VoicesCircularProgressIndicator()), + }; + }, + ); + } + + void _onRetry() { + unawaited(RegistrationBloc.of(context).refreshCardanoWallets()); + } +} + +class _WalletsList extends StatelessWidget { + final List wallets; + + const _WalletsList({required this.wallets}); + + @override + Widget build(BuildContext context) { + return ListView.builder( + itemCount: wallets.length, + itemBuilder: (context, index) { + final wallet = wallets[index]; + return VoicesWalletTile( + iconSrc: wallet.icon, + name: Text(wallet.name), + onTap: () { + RegistrationBloc.of(context).add(const NextStepEvent()); + }, + ); + }, + ); + } +} + +class _WalletsEmpty extends StatelessWidget { + final VoidCallback onRetry; + + const _WalletsEmpty({required this.onRetry}); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.topCenter, + child: SizedBox( + width: double.infinity, + child: VoicesErrorIndicator( + message: context.l10n.noWalletFound, + onRetry: onRetry, + ), + ), + ); + } +} + +class _WalletsError extends StatelessWidget { + final VoidCallback onRetry; + + const _WalletsError({required this.onRetry}); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.topCenter, + child: SizedBox( + width: double.infinity, + child: VoicesErrorIndicator( + message: context.l10n.somethingWentWrong, + onRetry: onRetry, + ), + ), + ); + } +} diff --git a/catalyst_voices/lib/pages/registration/wallet_link/wallet_details/wallet_details_panel.dart b/catalyst_voices/lib/pages/registration/wallet_link/wallet_details/wallet_details_panel.dart new file mode 100644 index 0000000000..8c1bc2cbfb --- /dev/null +++ b/catalyst_voices/lib/pages/registration/wallet_link/wallet_details/wallet_details_panel.dart @@ -0,0 +1,34 @@ +import 'package:catalyst_voices/widgets/buttons/voices_filled_button.dart'; +import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; +import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; +import 'package:flutter/material.dart'; + +// TODO(dtscalac): define content +class WalletDetailsPanel extends StatelessWidget { + const WalletDetailsPanel({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Spacer(), + VoicesFilledButton( + leading: VoicesAssets.icons.wallet.buildIcon(), + onTap: () { + RegistrationBloc.of(context).add(const PreviousStepEvent()); + }, + child: const Text('Previous'), + ), + const SizedBox(height: 12), + VoicesFilledButton( + leading: VoicesAssets.icons.wallet.buildIcon(), + onTap: () { + RegistrationBloc.of(context).add(const NextStepEvent()); + }, + child: const Text('Next'), + ), + ], + ); + } +} diff --git a/catalyst_voices/lib/pages/registration/wallet_link/wallet_link_panel.dart b/catalyst_voices/lib/pages/registration/wallet_link/wallet_link_panel.dart new file mode 100644 index 0000000000..a6bae7b7c7 --- /dev/null +++ b/catalyst_voices/lib/pages/registration/wallet_link/wallet_link_panel.dart @@ -0,0 +1,29 @@ +import 'package:catalyst_voices/pages/registration/wallet_link/intro/intro_panel.dart'; +import 'package:catalyst_voices/pages/registration/wallet_link/rbac_transaction/rbac_transaction_panel.dart'; +import 'package:catalyst_voices/pages/registration/wallet_link/roles_chooser/roles_chooser_panel.dart'; +import 'package:catalyst_voices/pages/registration/wallet_link/roles_summary/roles_summary_panel.dart'; +import 'package:catalyst_voices/pages/registration/wallet_link/select_wallet/select_wallet_panel.dart'; +import 'package:catalyst_voices/pages/registration/wallet_link/wallet_details/wallet_details_panel.dart'; +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:flutter/material.dart'; + +class WalletLinkPanel extends StatelessWidget { + final WalletLinkStage stage; + + const WalletLinkPanel({ + super.key, + required this.stage, + }); + + @override + Widget build(BuildContext context) { + return switch (stage) { + WalletLinkStage.intro => const IntroPanel(), + WalletLinkStage.selectWallet => const SelectWalletPanel(), + WalletLinkStage.walletDetails => const WalletDetailsPanel(), + WalletLinkStage.rolesChooser => const RolesChooserPanel(), + WalletLinkStage.rolesSummary => const RolesSummaryPanel(), + WalletLinkStage.rbacTransaction => const RbacTransactionPanel(), + }; + } +} diff --git a/catalyst_voices/lib/pages/spaces/spaces_shell_page.dart b/catalyst_voices/lib/pages/spaces/spaces_shell_page.dart index 8f5b6f9c00..ebea205e41 100644 --- a/catalyst_voices/lib/pages/spaces/spaces_shell_page.dart +++ b/catalyst_voices/lib/pages/spaces/spaces_shell_page.dart @@ -1,5 +1,5 @@ import 'package:catalyst_voices/common/ext/ext.dart'; -import 'package:catalyst_voices/pages/account/creation/get_started/account_create_dialog.dart'; +import 'package:catalyst_voices/pages/registration/registration_dialog.dart'; import 'package:catalyst_voices/pages/spaces/drawer/spaces_drawer.dart'; import 'package:catalyst_voices/widgets/widgets.dart'; import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; @@ -63,7 +63,7 @@ class _SpacesShellPageState extends State { automaticallyImplyLeading: false, actions: [ SessionActionHeader( - onGetStartedTap: _showAccountGetStarted, + onGetStartedTap: _showAccountSetup, ), const SessionStateHeader(), ], @@ -81,23 +81,7 @@ class _SpacesShellPageState extends State { ); } - Future _showAccountGetStarted() async { - final type = await AccountCreateDialog.show(context); - if (type == null) { - return; - } - - if (mounted) { - switch (type) { - case AccountCreateType.createNew: - _showCreateAccountFlow().ignore(); - case AccountCreateType.recover: - _showRecoverAccountFlow().ignore(); - } - } + Future _showAccountSetup() async { + await RegistrationDialog.show(context); } - - Future _showCreateAccountFlow() async {} - - Future _showRecoverAccountFlow() async {} } diff --git a/catalyst_voices/lib/widgets/buttons/voices_buttons.dart b/catalyst_voices/lib/widgets/buttons/voices_buttons.dart index a12419eb16..3b5c495f1d 100644 --- a/catalyst_voices/lib/widgets/buttons/voices_buttons.dart +++ b/catalyst_voices/lib/widgets/buttons/voices_buttons.dart @@ -1,6 +1,8 @@ import 'dart:async'; +import 'package:catalyst_voices/widgets/buttons/voices_filled_button.dart'; import 'package:catalyst_voices/widgets/buttons/voices_icon_button.dart'; +import 'package:catalyst_voices/widgets/buttons/voices_outlined_button.dart'; import 'package:catalyst_voices/widgets/buttons/voices_text_button.dart'; import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; @@ -167,3 +169,37 @@ class VoicesLearnMoreButton extends StatelessWidget { ); } } + +class VoicesNextButton extends StatelessWidget { + final VoidCallback? onTap; + + const VoicesNextButton({ + super.key, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return VoicesFilledButton( + onTap: onTap, + child: Text(context.l10n.next), + ); + } +} + +class VoicesBackButton extends StatelessWidget { + final VoidCallback? onTap; + + const VoicesBackButton({ + super.key, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return VoicesOutlinedButton( + onTap: onTap, + child: Text(context.l10n.back), + ); + } +} diff --git a/catalyst_voices/lib/widgets/common/infrastructure/voices_future_builder.dart b/catalyst_voices/lib/widgets/common/infrastructure/voices_future_builder.dart new file mode 100644 index 0000000000..53698f8fb9 --- /dev/null +++ b/catalyst_voices/lib/widgets/common/infrastructure/voices_future_builder.dart @@ -0,0 +1,168 @@ +import 'package:catalyst_voices/widgets/indicators/voices_circular_progress_indicator.dart'; +import 'package:catalyst_voices/widgets/indicators/voices_error_indicator.dart'; +import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; +import 'package:flutter/material.dart'; + +/// A callback that generates a new [Future] of type [T]. +typedef VoicesFutureProvider = Future Function(); + +/// A callback that builds a widget from a [T] value. +/// +/// Call [onRetry] if your data state contains +/// the retry button, it will reload the widget. +typedef VoicesFutureDataBuilder = Widget Function( + BuildContext context, + T value, + VoidCallback onRetry, +); + +/// A callback that builds a widget in an error state. +/// +/// Call [onRetry] if your error state contains +/// the retry button, it will reload the widget. +typedef VoicesFutureErrorBuilder = Widget Function( + BuildContext context, + Object? error, + VoidCallback onRetry, +); + +/// A [FutureBuilder] which simplifies handling a [Future] gently. +class VoicesFutureBuilder extends StatefulWidget { + /// The future provider, make sure to return a fresh future + /// each time it is called, the widget takes care of caching + /// the future internally. + final VoicesFutureProvider future; + + /// The builder called to build a child + /// when the [future] finishes successfully. + final VoicesFutureDataBuilder dataBuilder; + + /// The builder called to build a child + /// when the [future] finishes with an error. + /// + /// If not provided then [VoicesErrorIndicator] is used instead. + final VoicesFutureErrorBuilder errorBuilder; + + /// The builder called to build a child + /// when the [future] hasn't finished yet. + /// + /// If not provided then a centered [VoicesCircularProgressIndicator] + /// is used instead. + final WidgetBuilder loaderBuilder; + + /// The minimum duration during which the loader state is shown. + /// + /// It is useful to delay a future which finishes in a split second + /// as this results in jumpy UI, not giving the user enough time to see + /// the loader state before data or error states are shown. + /// + /// Pass [Duration.zero] to disable it. + final Duration minimumDelay; + + const VoicesFutureBuilder({ + super.key, + required this.future, + required this.dataBuilder, + this.errorBuilder = _defaultErrorBuilder, + this.loaderBuilder = _defaultLoaderBuilder, + this.minimumDelay = const Duration(milliseconds: 300), + }); + + @override + State createState() => _VoicesFutureBuilderState(); +} + +class _VoicesFutureBuilderState + extends State> { + Future? _future; + + @override + void initState() { + super.initState(); + + // ignore: discarded_futures + _future = _makeDelayedFuture(); + } + + @override + void dispose() { + _future = null; + super.dispose(); + } + + @override + void didUpdateWidget(VoicesFutureBuilder oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.future != oldWidget.future) { + // ignore: discarded_futures + _future = _makeDelayedFuture(); + } + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: _future, + builder: (context, snapshot) { + if (snapshot.hasError) { + return widget.errorBuilder(context, snapshot.error, _onRetry); + } + + final data = snapshot.data; + if (data == null) { + return widget.loaderBuilder(context); + } + + return widget.dataBuilder(context, data, _onRetry); + }, + ); + } + + void _onRetry() { + setState(() { + // ignore: discarded_futures + _future = _makeDelayedFuture(); + }); + } + + Future _makeDelayedFuture() async { + return widget.future().withMinimumDelay(widget.minimumDelay); + } +} + +Widget _defaultErrorBuilder( + BuildContext context, + Object? error, + VoidCallback onRetry, +) { + return _Error(onRetry: onRetry); +} + +Widget _defaultLoaderBuilder(BuildContext context) { + return const _Loader(); +} + +class _Error extends StatelessWidget { + final VoidCallback onRetry; + + const _Error({required this.onRetry}); + + @override + Widget build(BuildContext context) { + return VoicesErrorIndicator( + message: context.l10n.somethingWentWrong, + onRetry: onRetry, + ); + } +} + +class _Loader extends StatelessWidget { + const _Loader(); + + @override + Widget build(BuildContext context) { + return const Center(child: VoicesCircularProgressIndicator()); + } +} diff --git a/catalyst_voices/lib/widgets/indicators/voices_error_indicator.dart b/catalyst_voices/lib/widgets/indicators/voices_error_indicator.dart new file mode 100644 index 0000000000..b34675fe93 --- /dev/null +++ b/catalyst_voices/lib/widgets/indicators/voices_error_indicator.dart @@ -0,0 +1,59 @@ +import 'package:catalyst_voices/widgets/widgets.dart'; +import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; +import 'package:catalyst_voices_brands/catalyst_voices_brands.dart'; +import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; +import 'package:flutter/material.dart'; + +/// A generic error state with optional retry button. +class VoicesErrorIndicator extends StatelessWidget { + /// The description of the error. + final String message; + + /// The callback called when refresh button is tapped. + /// + /// If null then retry button is hidden. + final VoidCallback? onRetry; + + const VoicesErrorIndicator({ + super.key, + required this.message, + this.onRetry, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 16), + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colors.outlineBorderVariant!, + width: 1, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + VoicesAssets.icons.exclamation.buildIcon( + color: Theme.of(context).colors.iconsError, + size: 20, + ), + const SizedBox(width: 10), + Text( + message, + style: Theme.of(context).textTheme.titleSmall?.copyWith(height: 1), + ), + if (onRetry != null) ...[ + const SizedBox(width: 10), + VoicesTextButton( + leading: VoicesAssets.icons.refresh.buildIcon(), + onTap: onRetry, + child: Text(context.l10n.retry), + ), + ], + ], + ), + ); + } +} diff --git a/catalyst_voices/lib/widgets/widgets.dart b/catalyst_voices/lib/widgets/widgets.dart index ff32170b6e..0f2fe87976 100644 --- a/catalyst_voices/lib/widgets/widgets.dart +++ b/catalyst_voices/lib/widgets/widgets.dart @@ -32,6 +32,7 @@ export 'headers/section_header.dart'; export 'headers/segment_header.dart'; export 'indicators/process_progress_indicator.dart'; export 'indicators/voices_circular_progress_indicator.dart'; +export 'indicators/voices_error_indicator.dart'; export 'indicators/voices_linear_progress_indicator.dart'; export 'indicators/voices_no_internet_connection_banner.dart'; export 'indicators/voices_password_strength_indicator.dart'; diff --git a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/catalyst_voices_blocs.dart b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/catalyst_voices_blocs.dart index e21458b916..00c5c6e0cc 100644 --- a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/catalyst_voices_blocs.dart +++ b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/catalyst_voices_blocs.dart @@ -1,4 +1,5 @@ export 'authentication/authentication.dart'; export 'brand/brand.dart'; export 'login/login.dart'; +export 'registration/registration.dart'; export 'session/session.dart'; diff --git a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/controllers/keychain_creation_controller.dart b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/controllers/keychain_creation_controller.dart new file mode 100644 index 0000000000..4a52f4b46b --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/controllers/keychain_creation_controller.dart @@ -0,0 +1,64 @@ +import 'package:catalyst_voices_blocs/src/registration/registration_navigator.dart'; +import 'package:catalyst_voices_blocs/src/registration/registration_state.dart'; +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; + +abstract interface class KeychainCreationController {} + +final class RegistrationKeychainCreationController + implements + KeychainCreationController, + RegistrationNavigator { + CreateKeychainStage _stage; + + RegistrationKeychainCreationController({ + CreateKeychainStage stage = CreateKeychainStage.splash, + }) : _stage = stage; + + @override + CreateKeychain? nextStep() { + final nextStep = switch (_stage) { + CreateKeychainStage.splash => + const CreateKeychain(stage: CreateKeychainStage.instructions), + CreateKeychainStage.instructions => throw UnimplementedError(), + CreateKeychainStage.seedPhrase => throw UnimplementedError(), + CreateKeychainStage.checkSeedPhraseInstructions => + throw UnimplementedError(), + CreateKeychainStage.checkSeedPhrase => throw UnimplementedError(), + CreateKeychainStage.checkSeedPhraseResult => throw UnimplementedError(), + CreateKeychainStage.unlockPasswordInstructions => + throw UnimplementedError(), + CreateKeychainStage.unlockPasswordCreate => throw UnimplementedError(), + CreateKeychainStage.created => null, + }; + + if (nextStep != null) { + _stage = nextStep.stage; + } + + return nextStep; + } + + @override + CreateKeychain? previousStep() { + final previousStep = switch (_stage) { + CreateKeychainStage.splash => null, + CreateKeychainStage.instructions => + const CreateKeychain(stage: CreateKeychainStage.splash), + CreateKeychainStage.seedPhrase => throw UnimplementedError(), + CreateKeychainStage.checkSeedPhraseInstructions => + throw UnimplementedError(), + CreateKeychainStage.checkSeedPhrase => throw UnimplementedError(), + CreateKeychainStage.checkSeedPhraseResult => throw UnimplementedError(), + CreateKeychainStage.unlockPasswordInstructions => + throw UnimplementedError(), + CreateKeychainStage.unlockPasswordCreate => throw UnimplementedError(), + CreateKeychainStage.created => throw UnimplementedError(), + }; + + if (previousStep != null) { + _stage = previousStep.stage; + } + + return previousStep; + } +} diff --git a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/controllers/wallet_link_controller.dart b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/controllers/wallet_link_controller.dart new file mode 100644 index 0000000000..8917312fb4 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/controllers/wallet_link_controller.dart @@ -0,0 +1,91 @@ +import 'package:catalyst_cardano/catalyst_cardano.dart'; +import 'package:catalyst_voices_blocs/src/registration/registration_navigator.dart'; +import 'package:catalyst_voices_blocs/src/registration/registration_state.dart'; +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; +import 'package:flutter/foundation.dart'; +import 'package:result_type/result_type.dart'; + +// ignore: one_member_abstracts +abstract interface class WalletLinkController { + /// A value listenable with available cardano wallets. + ValueListenable, Exception>?> get cardanoWallets; + + /// Refreshes the [cardanoWallets]. + Future refreshCardanoWallets(); +} + +final class RegistrationWalletLinkController + implements WalletLinkController, RegistrationNavigator { + final ValueNotifier, Exception>?> _wallets = + ValueNotifier(null); + + WalletLinkStage _stage; + + RegistrationWalletLinkController({ + WalletLinkStage stage = WalletLinkStage.intro, + }) : _stage = stage; + + @override + WalletLink? nextStep() { + final nextStep = switch (_stage) { + WalletLinkStage.intro => + const WalletLink(stage: WalletLinkStage.selectWallet), + WalletLinkStage.selectWallet => + const WalletLink(stage: WalletLinkStage.walletDetails), + WalletLinkStage.walletDetails => + const WalletLink(stage: WalletLinkStage.rolesChooser), + WalletLinkStage.rolesChooser => + const WalletLink(stage: WalletLinkStage.rolesSummary), + WalletLinkStage.rolesSummary => + const WalletLink(stage: WalletLinkStage.rbacTransaction), + WalletLinkStage.rbacTransaction => null, + }; + + if (nextStep != null) { + _stage = nextStep.stage; + } + + return nextStep; + } + + @override + WalletLink? previousStep() { + final previousStep = switch (_stage) { + WalletLinkStage.intro => null, + WalletLinkStage.selectWallet => + const WalletLink(stage: WalletLinkStage.intro), + WalletLinkStage.walletDetails => + const WalletLink(stage: WalletLinkStage.selectWallet), + WalletLinkStage.rolesChooser => + const WalletLink(stage: WalletLinkStage.walletDetails), + WalletLinkStage.rolesSummary => + const WalletLink(stage: WalletLinkStage.rolesChooser), + WalletLinkStage.rbacTransaction => + const WalletLink(stage: WalletLinkStage.rolesSummary), + }; + + if (previousStep != null) { + _stage = previousStep.stage; + } + + return previousStep; + } + + @override + ValueListenable, Exception>?> get cardanoWallets => + _wallets; + + @override + Future refreshCardanoWallets() async { + try { + _wallets.value = null; + + final wallets = + await CatalystCardano.instance.getWallets().withMinimumDelay(); + _wallets.value = Success(wallets); + } on Exception catch (error) { + _wallets.value = Failure(error); + } + } +} diff --git a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/registration.dart b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/registration.dart new file mode 100644 index 0000000000..b22d82608f --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/registration.dart @@ -0,0 +1,3 @@ +export 'registration_bloc.dart'; +export 'registration_event.dart'; +export 'registration_state.dart'; diff --git a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/registration_bloc.dart b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/registration_bloc.dart new file mode 100644 index 0000000000..c03585012a --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/registration_bloc.dart @@ -0,0 +1,115 @@ +import 'package:catalyst_cardano/catalyst_cardano.dart'; +import 'package:catalyst_voices_blocs/src/registration/controllers/keychain_creation_controller.dart'; +import 'package:catalyst_voices_blocs/src/registration/controllers/wallet_link_controller.dart'; +import 'package:catalyst_voices_blocs/src/registration/registration_event.dart'; +import 'package:catalyst_voices_blocs/src/registration/registration_state.dart'; +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:result_type/result_type.dart'; + +/// Manages the registration state. +final class RegistrationBloc extends Bloc + implements KeychainCreationController, WalletLinkController { + final RegistrationKeychainCreationController _keychainCreationController; + final RegistrationWalletLinkController _walletLinkController; + + RegistrationBloc() + : _keychainCreationController = RegistrationKeychainCreationController(), + _walletLinkController = RegistrationWalletLinkController(), + super(const GetStarted()) { + on(_handleRegistrationEvent); + } + + /// Returns [RegistrationBloc] if found in widget tree. Does not add + /// rebuild dependency when called. + static RegistrationBloc of(BuildContext context) { + return context.read(); + } + + /// Returns [RegistrationBloc] if found in widget tree. Adds rebuild + /// dependency when called so you can not call it in initState. + static RegistrationBloc watch(BuildContext context) { + return context.watch(); + } + + void _handleRegistrationEvent( + RegistrationEvent event, + Emitter emit, + ) { + final nextState = switch (event) { + CreateAccountTypeEvent(:final type) => _createAccountNextStep(type), + NextStepEvent() => _nextStep(), + PreviousStepEvent() => _previousStep(), + }; + + emit(nextState); + } + + RegistrationState _createAccountNextStep(CreateAccountType type) { + return switch (type) { + CreateAccountType.createNew => const CreateKeychain(), + CreateAccountType.recover => const Recover(), + }; + } + + RegistrationState _nextStep() { + /// Nested function. Responsible only for keychain steps logic. + RegistrationState keychainNextStep() { + final nextStep = _keychainCreationController.nextStep(); + + return nextStep ?? const FinishAccountCreation(); + } + + /// Nested function. Responsible only for wallet link steps logic. + RegistrationState walletLinkNextStep() { + final nextStep = _walletLinkController.nextStep(); + + return nextStep ?? state; + } + + return switch (state) { + GetStarted() => throw StateError( + 'GetStarted has two routes that may go to. ' + 'NextStep is not valid here.', + ), + FinishAccountCreation() => throw UnimplementedError(), + Recover() => throw UnimplementedError(), + CreateKeychain() => keychainNextStep(), + WalletLink() => walletLinkNextStep(), + }; + } + + RegistrationState _previousStep() { + /// Nested function. Responsible only for keychain steps logic. + RegistrationState keychainPreviousStep() { + final previousStep = _keychainCreationController.previousStep(); + + return previousStep ?? const GetStarted(); + } + + /// Nested function. Responsible only for wallet link steps logic. + RegistrationState walletLinkPreviousStep() { + final previousStep = _walletLinkController.previousStep(); + + return previousStep ?? const FinishAccountCreation(); + } + + return switch (state) { + GetStarted() => throw StateError('GetStarted is initial step.'), + FinishAccountCreation() => throw UnimplementedError(), + Recover() => throw UnimplementedError(), + CreateKeychain() => keychainPreviousStep(), + WalletLink() => walletLinkPreviousStep(), + }; + } + + @override + ValueListenable, Exception>?> get cardanoWallets => + _walletLinkController.cardanoWallets; + + @override + Future refreshCardanoWallets() async => + _walletLinkController.refreshCardanoWallets(); +} diff --git a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/registration_event.dart b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/registration_event.dart new file mode 100644 index 0000000000..18ccb57338 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/registration_event.dart @@ -0,0 +1,32 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:equatable/equatable.dart'; + +/// Describes events that change the registration. +sealed class RegistrationEvent extends Equatable { + const RegistrationEvent(); +} + +final class CreateAccountTypeEvent extends RegistrationEvent { + final CreateAccountType type; + + const CreateAccountTypeEvent({ + required this.type, + }); + + @override + List get props => [type]; +} + +final class NextStepEvent extends RegistrationEvent { + const NextStepEvent(); + + @override + List get props => []; +} + +final class PreviousStepEvent extends RegistrationEvent { + const PreviousStepEvent(); + + @override + List get props => []; +} diff --git a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/registration_navigator.dart b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/registration_navigator.dart new file mode 100644 index 0000000000..680bb5e878 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/registration_navigator.dart @@ -0,0 +1,11 @@ +import 'package:catalyst_voices_blocs/src/registration/registration_state.dart'; + +// Note. Maybe make it non null and add hasNextStep / hasPreviousStep +/// Abstraction for navigation between different [RegistrationState] steps. +abstract interface class RegistrationNavigator { + /// Returns null if there is no next step. + T? nextStep(); + + /// Returns null if there is no previous step. + T? previousStep(); +} diff --git a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/registration_state.dart b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/registration_state.dart new file mode 100644 index 0000000000..c4db67a0a7 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/registration_state.dart @@ -0,0 +1,68 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:equatable/equatable.dart'; + +/// Determines the state of registration flow. +/// It consists of 4 separate steps +/// - [GetStarted] which is first one. +/// - [FinishAccountCreation] is special step in case of partially created +/// account. +/// - [Recover] when want to start with existing one. +/// - [CreateNew] is entire flow in it self and has two distinguish sub-steps +/// - [CreateKeychain] where user is creating new keychain. +/// - [WalletLink] where user is linking Keychain with wallet. +sealed class RegistrationState extends Equatable { + const RegistrationState(); +} + +/// User decides where to go here [CreateNew] or [Recover] route. +final class GetStarted extends RegistrationState { + const GetStarted(); + + @override + List get props => []; +} + +/// When [CreateKeychain] is completed but [WalletLink] not. +final class FinishAccountCreation extends RegistrationState { + const FinishAccountCreation(); + + @override + List get props => []; +} + +/// User enters existing seed phrase here. +final class Recover extends RegistrationState { + const Recover(); + + @override + List get props => []; +} + +/// Encapsulates entire process of registration. +sealed class CreateNew extends RegistrationState { + const CreateNew(); +} + +/// Building up information for creating new Keychain. +final class CreateKeychain extends CreateNew { + final CreateKeychainStage stage; + + const CreateKeychain({ + this.stage = CreateKeychainStage.splash, + }); + + @override + List get props => [stage]; +} + +/// Linking existing keychain with wallet. +final class WalletLink extends CreateNew { + final WalletLinkStage stage; + + const WalletLink({ + this.stage = WalletLinkStage.intro, + }); + + @override + List get props => [stage]; +} diff --git a/catalyst_voices/packages/catalyst_voices_blocs/pubspec.yaml b/catalyst_voices/packages/catalyst_voices_blocs/pubspec.yaml index e3f11a3fcc..c759c635c1 100644 --- a/catalyst_voices/packages/catalyst_voices_blocs/pubspec.yaml +++ b/catalyst_voices/packages/catalyst_voices_blocs/pubspec.yaml @@ -9,6 +9,9 @@ environment: dependencies: bloc_concurrency: ^0.2.2 + catalyst_cardano: ^0.3.0 + catalyst_cardano_serialization: ^0.4.0 + catalyst_cardano_web: ^0.3.0 catalyst_voices_brands: path: ../catalyst_voices_brands catalyst_voices_models: @@ -17,6 +20,8 @@ dependencies: path: ../catalyst_voices_repositories catalyst_voices_services: path: ../catalyst_voices_services + catalyst_voices_shared: + path: ../catalyst_voices_shared catalyst_voices_view_models: path: ../catalyst_voices_view_models collection: ^1.18.0 diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart index b7f6afd7ab..fed990d61d 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart @@ -680,25 +680,55 @@ abstract class VoicesLocalizations { /// /// In en, this message translates to: /// **'Link keys to your Catalyst Keychain'** - String get walletLink_header; + String get walletLinkHeader; - /// A subheader in link wallet flow in registration. + /// A subheader in link wallet flow in registration for wallet connection. /// /// In en, this message translates to: /// **'Link your Cardano wallet'** - String get walletLink_subheader; + String get walletLinkWalletSubheader; + + /// A subheader in link wallet flow in registration for role chooser state. + /// + /// In en, this message translates to: + /// **'Select your Catalyst roles'** + String get walletLinkRolesSubheader; + + /// A subheader in link wallet flow in registration for RBAC transaction. + /// + /// In en, this message translates to: + /// **'Sign your Catalyst roles to the\nCardano mainnet'** + String get walletLinkTransactionSubheader; /// A title in link wallet flow on intro screen. /// /// In en, this message translates to: /// **'Link Cardano Wallet & Catalyst Roles to you Catalyst Keychain.'** - String get walletLink_intro_title; + String get walletLinkIntroTitle; /// A message (content) in link wallet flow on intro screen. /// /// In en, this message translates to: /// **'You\'re almost there! This is the final and most important step in your account setup.\n\nWe\'re going to link a Cardano Wallet to your Catalyst Keychain, so you can start collecting Role Keys.\n\nRole Keys allow you to enter new spaces, discover new ways to participate, and unlock new ways to earn rewards.\n\nWe\'ll start with your Voter Key by default. You can decide to add a Proposer Key and Drep key if you want, or you can always add them later.'** - String get walletLink_intro_content; + String get walletLinkIntroContent; + + /// A title in link wallet flow on select wallet screen. + /// + /// In en, this message translates to: + /// **'Select the Cardano wallet to link\nto your Catalyst Keychain.'** + String get walletLinkSelectWalletTitle; + + /// A message (content) in link wallet flow on select wallet screen. + /// + /// In en, this message translates to: + /// **'To complete this action, you\'ll submit a signed transaction to Cardano. There will be an ADA transaction fee.'** + String get walletLinkSelectWalletContent; + + /// Message shown when redirecting to external content that describes which wallets are supported. + /// + /// In en, this message translates to: + /// **'See all supported wallets'** + String get seeAllSupportedWallets; /// No description provided for @accountCreationCreate. /// @@ -754,12 +784,6 @@ abstract class VoicesLocalizations { /// **'Profile & Keychain'** String get profileAndKeychain; - /// Title of Catalyst Keychain card - /// - /// In en, this message translates to: - /// **'Catalyst Keychain'** - String get catalystKeychain; - /// Action on Catalyst Keychain card /// /// In en, this message translates to: @@ -801,6 +825,72 @@ abstract class VoicesLocalizations { /// In en, this message translates to: /// **'Default'** String get defaultRole; + + /// No description provided for @catalystKeychain. + /// + /// In en, this message translates to: + /// **'Catalyst Keychain'** + String get catalystKeychain; + + /// No description provided for @accountCreationSplashTitle. + /// + /// In en, this message translates to: + /// **'Create your Catalyst Keychain'** + String get accountCreationSplashTitle; + + /// No description provided for @accountCreationSplashMessage. + /// + /// In en, this message translates to: + /// **'Your keychain is your ticket to participate in 
distributed innovation on the global stage. 

Once you have it, you\'ll be able to enter different spaces, discover awesome ideas, and share your feedback to hep improve ideas. 

As you add new keys to your keychain, you\'ll be able to enter new spaces, unlock new rewards opportunities, and have your voice heard in community decisions.'** + String get accountCreationSplashMessage; + + /// No description provided for @accountCreationSplashNextButton. + /// + /// In en, this message translates to: + /// **'Create your Keychain now'** + String get accountCreationSplashNextButton; + + /// No description provided for @accountInstructionsTitle. + /// + /// In en, this message translates to: + /// **'Great! Your Catalyst Keychain 
has been created.'** + String get accountInstructionsTitle; + + /// No description provided for @accountInstructionsMessage. + /// + /// In en, this message translates to: + /// **'On the next screen, you\'re going to see 12 words. 
This is called your \"seed phrase\". 

It\'s like a super secure password that only you know, 
that allows you to prove ownership of your keychain. 

You\'ll use it to login and recover your account on 
different devices, so be sure to put it somewhere safe!\n\nYou need to write this seed phrase down with pen and paper, so get this ready.'** + String get accountInstructionsMessage; + + /// For example in button that goes to next stage of registration + /// + /// In en, this message translates to: + /// **'Next'** + String get next; + + /// For example in button that goes to previous stage of registration + /// + /// In en, this message translates to: + /// **'Back'** + String get back; + + /// Retry action when something goes wrong. + /// + /// In en, this message translates to: + /// **'Retry'** + String get retry; + + /// Error description when something goes wrong. + /// + /// In en, this message translates to: + /// **'Something went wrong.'** + String get somethingWentWrong; + + /// A description when no wallet extension was found. + /// + /// In en, this message translates to: + /// **'No wallet found.'** + String get noWalletFound; } class _VoicesLocalizationsDelegate extends LocalizationsDelegate { diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart index da28f6cac2..850a07bbe0 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart @@ -353,16 +353,31 @@ class VoicesLocalizationsEn extends VoicesLocalizations { String get learnMore => 'Learn More'; @override - String get walletLink_header => 'Link keys to your Catalyst Keychain'; + String get walletLinkHeader => 'Link keys to your Catalyst Keychain'; @override - String get walletLink_subheader => 'Link your Cardano wallet'; + String get walletLinkWalletSubheader => 'Link your Cardano wallet'; @override - String get walletLink_intro_title => 'Link Cardano Wallet & Catalyst Roles to you Catalyst Keychain.'; + String get walletLinkRolesSubheader => 'Select your Catalyst roles'; @override - String get walletLink_intro_content => 'You\'re almost there! This is the final and most important step in your account setup.\n\nWe\'re going to link a Cardano Wallet to your Catalyst Keychain, so you can start collecting Role Keys.\n\nRole Keys allow you to enter new spaces, discover new ways to participate, and unlock new ways to earn rewards.\n\nWe\'ll start with your Voter Key by default. You can decide to add a Proposer Key and Drep key if you want, or you can always add them later.'; + String get walletLinkTransactionSubheader => 'Sign your Catalyst roles to the\nCardano mainnet'; + + @override + String get walletLinkIntroTitle => 'Link Cardano Wallet & Catalyst Roles to you Catalyst Keychain.'; + + @override + String get walletLinkIntroContent => 'You\'re almost there! This is the final and most important step in your account setup.\n\nWe\'re going to link a Cardano Wallet to your Catalyst Keychain, so you can start collecting Role Keys.\n\nRole Keys allow you to enter new spaces, discover new ways to participate, and unlock new ways to earn rewards.\n\nWe\'ll start with your Voter Key by default. You can decide to add a Proposer Key and Drep key if you want, or you can always add them later.'; + + @override + String get walletLinkSelectWalletTitle => 'Select the Cardano wallet to link\nto your Catalyst Keychain.'; + + @override + String get walletLinkSelectWalletContent => 'To complete this action, you\'ll submit a signed transaction to Cardano. There will be an ADA transaction fee.'; + + @override + String get seeAllSupportedWallets => 'See all supported wallets'; @override String get accountCreationCreate => 'Create a new 
Catalyst Keychain'; @@ -391,9 +406,6 @@ class VoicesLocalizationsEn extends VoicesLocalizations { @override String get profileAndKeychain => 'Profile & Keychain'; - @override - String get catalystKeychain => 'Catalyst Keychain'; - @override String get removeKeychain => 'Remove Keychain'; @@ -414,4 +426,37 @@ class VoicesLocalizationsEn extends VoicesLocalizations { @override String get defaultRole => 'Default'; + + @override + String get catalystKeychain => 'Catalyst Keychain'; + + @override + String get accountCreationSplashTitle => 'Create your Catalyst Keychain'; + + @override + String get accountCreationSplashMessage => 'Your keychain is your ticket to participate in 
distributed innovation on the global stage. 

Once you have it, you\'ll be able to enter different spaces, discover awesome ideas, and share your feedback to hep improve ideas. 

As you add new keys to your keychain, you\'ll be able to enter new spaces, unlock new rewards opportunities, and have your voice heard in community decisions.'; + + @override + String get accountCreationSplashNextButton => 'Create your Keychain now'; + + @override + String get accountInstructionsTitle => 'Great! Your Catalyst Keychain 
has been created.'; + + @override + String get accountInstructionsMessage => 'On the next screen, you\'re going to see 12 words. 
This is called your \"seed phrase\". 

It\'s like a super secure password that only you know, 
that allows you to prove ownership of your keychain. 

You\'ll use it to login and recover your account on 
different devices, so be sure to put it somewhere safe!\n\nYou need to write this seed phrase down with pen and paper, so get this ready.'; + + @override + String get next => 'Next'; + + @override + String get back => 'Back'; + + @override + String get retry => 'Retry'; + + @override + String get somethingWentWrong => 'Something went wrong.'; + + @override + String get noWalletFound => 'No wallet found.'; } diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart index b319a90a12..795fd9e380 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart @@ -353,16 +353,31 @@ class VoicesLocalizationsEs extends VoicesLocalizations { String get learnMore => 'Learn More'; @override - String get walletLink_header => 'Link keys to your Catalyst Keychain'; + String get walletLinkHeader => 'Link keys to your Catalyst Keychain'; @override - String get walletLink_subheader => 'Link your Cardano wallet'; + String get walletLinkWalletSubheader => 'Link your Cardano wallet'; @override - String get walletLink_intro_title => 'Link Cardano Wallet & Catalyst Roles to you Catalyst Keychain.'; + String get walletLinkRolesSubheader => 'Select your Catalyst roles'; @override - String get walletLink_intro_content => 'You\'re almost there! This is the final and most important step in your account setup.\n\nWe\'re going to link a Cardano Wallet to your Catalyst Keychain, so you can start collecting Role Keys.\n\nRole Keys allow you to enter new spaces, discover new ways to participate, and unlock new ways to earn rewards.\n\nWe\'ll start with your Voter Key by default. You can decide to add a Proposer Key and Drep key if you want, or you can always add them later.'; + String get walletLinkTransactionSubheader => 'Sign your Catalyst roles to the\nCardano mainnet'; + + @override + String get walletLinkIntroTitle => 'Link Cardano Wallet & Catalyst Roles to you Catalyst Keychain.'; + + @override + String get walletLinkIntroContent => 'You\'re almost there! This is the final and most important step in your account setup.\n\nWe\'re going to link a Cardano Wallet to your Catalyst Keychain, so you can start collecting Role Keys.\n\nRole Keys allow you to enter new spaces, discover new ways to participate, and unlock new ways to earn rewards.\n\nWe\'ll start with your Voter Key by default. You can decide to add a Proposer Key and Drep key if you want, or you can always add them later.'; + + @override + String get walletLinkSelectWalletTitle => 'Select the Cardano wallet to link\nto your Catalyst Keychain.'; + + @override + String get walletLinkSelectWalletContent => 'To complete this action, you\'ll submit a signed transaction to Cardano. There will be an ADA transaction fee.'; + + @override + String get seeAllSupportedWallets => 'See all supported wallets'; @override String get accountCreationCreate => 'Create a new 
Catalyst Keychain'; @@ -391,9 +406,6 @@ class VoicesLocalizationsEs extends VoicesLocalizations { @override String get profileAndKeychain => 'Profile & Keychain'; - @override - String get catalystKeychain => 'Catalyst Keychain'; - @override String get removeKeychain => 'Remove Keychain'; @@ -414,4 +426,37 @@ class VoicesLocalizationsEs extends VoicesLocalizations { @override String get defaultRole => 'Default'; + + @override + String get catalystKeychain => 'Catalyst Keychain'; + + @override + String get accountCreationSplashTitle => 'Create your Catalyst Keychain'; + + @override + String get accountCreationSplashMessage => 'Your keychain is your ticket to participate in 
distributed innovation on the global stage. 

Once you have it, you\'ll be able to enter different spaces, discover awesome ideas, and share your feedback to hep improve ideas. 

As you add new keys to your keychain, you\'ll be able to enter new spaces, unlock new rewards opportunities, and have your voice heard in community decisions.'; + + @override + String get accountCreationSplashNextButton => 'Create your Keychain now'; + + @override + String get accountInstructionsTitle => 'Great! Your Catalyst Keychain 
has been created.'; + + @override + String get accountInstructionsMessage => 'On the next screen, you\'re going to see 12 words. 
This is called your \"seed phrase\". 

It\'s like a super secure password that only you know, 
that allows you to prove ownership of your keychain. 

You\'ll use it to login and recover your account on 
different devices, so be sure to put it somewhere safe!\n\nYou need to write this seed phrase down with pen and paper, so get this ready.'; + + @override + String get next => 'Next'; + + @override + String get back => 'Back'; + + @override + String get retry => 'Retry'; + + @override + String get somethingWentWrong => 'Something went wrong.'; + + @override + String get noWalletFound => 'No wallet found.'; } diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb b/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb index f09843eabb..760be780be 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb @@ -434,22 +434,42 @@ "@learnMore": { "description": "A label on a clickable element that can show more content." }, - "walletLink_header": "Link keys to your Catalyst Keychain", - "@walletLink_header": { + "walletLinkHeader": "Link keys to your Catalyst Keychain", + "@walletLinkHeader": { "description": "A header in link wallet flow in registration." }, - "walletLink_subheader": "Link your Cardano wallet", - "@walletLink_subheader": { - "description": "A subheader in link wallet flow in registration." + "walletLinkWalletSubheader": "Link your Cardano wallet", + "@walletLinkWalletSubheader": { + "description": "A subheader in link wallet flow in registration for wallet connection." }, - "walletLink_intro_title": "Link Cardano Wallet & Catalyst Roles to you Catalyst Keychain.", - "@walletLink_intro_title": { + "walletLinkRolesSubheader": "Select your Catalyst roles", + "@walletLinkRolesSubheader": { + "description": "A subheader in link wallet flow in registration for role chooser state." + }, + "walletLinkTransactionSubheader": "Sign your Catalyst roles to the\nCardano mainnet", + "@walletLinkTransactionSubheader": { + "description": "A subheader in link wallet flow in registration for RBAC transaction." + }, + "walletLinkIntroTitle": "Link Cardano Wallet & Catalyst Roles to you Catalyst Keychain.", + "@walletLinkIntroTitle": { "description": "A title in link wallet flow on intro screen." }, - "walletLink_intro_content": "You're almost there! This is the final and most important step in your account setup.\n\nWe're going to link a Cardano Wallet to your Catalyst Keychain, so you can start collecting Role Keys.\n\nRole Keys allow you to enter new spaces, discover new ways to participate, and unlock new ways to earn rewards.\n\nWe'll start with your Voter Key by default. You can decide to add a Proposer Key and Drep key if you want, or you can always add them later.", - "@walletLink_intro_content": { + "walletLinkIntroContent": "You're almost there! This is the final and most important step in your account setup.\n\nWe're going to link a Cardano Wallet to your Catalyst Keychain, so you can start collecting Role Keys.\n\nRole Keys allow you to enter new spaces, discover new ways to participate, and unlock new ways to earn rewards.\n\nWe'll start with your Voter Key by default. You can decide to add a Proposer Key and Drep key if you want, or you can always add them later.", + "@walletLinkIntroContent": { "description": "A message (content) in link wallet flow on intro screen." }, + "walletLinkSelectWalletTitle": "Select the Cardano wallet to link\nto your Catalyst Keychain.", + "@walletLinkSelectWalletTitle": { + "description": "A title in link wallet flow on select wallet screen." + }, + "walletLinkSelectWalletContent": "To complete this action, you'll submit a signed transaction to Cardano. There will be an ADA transaction fee.", + "@walletLinkSelectWalletContent": { + "description": "A message (content) in link wallet flow on select wallet screen." + }, + "seeAllSupportedWallets": "See all supported wallets", + "@seeAllSupportedWallets": { + "description": "Message shown when redirecting to external content that describes which wallets are supported." + }, "accountCreationCreate": "Create a new \u2028Catalyst Keychain", "accountCreationRecover": "Recover your\u2028Catalyst Keychain", "accountCreationOnThisDevice": "On this device", @@ -459,7 +479,7 @@ "accountCreationGetStartedTitle": "Welcome to Catalyst", "accountCreationGetStatedDesc": "If you already have a Catalyst keychain you can restore it on this device, or you can create a new Catalyst Keychain.", "accountCreationGetStatedWhatNext": "What do you want to do?", - "myAccountProfileKeychain": "My Account / Profile & Keychain", + "myAccountProfileKeychain": "My Account / Profile & Keychain", "@myAccountProfileKeychain": { "description": "Title of My Account page" }, @@ -471,10 +491,6 @@ "@profileAndKeychain": { "description": "Tab on My Account page" }, - "catalystKeychain": "Catalyst Keychain", - "@catalystKeychain": { - "description": "Title of Catalyst Keychain card" - }, "removeKeychain": "Remove Keychain", "@removeKeychain": { "description": "Action on Catalyst Keychain card" @@ -502,5 +518,31 @@ "defaultRole": "Default", "@defaultRole": { "description": "Related to account role" + }, + "catalystKeychain": "Catalyst Keychain", + "accountCreationSplashTitle": "Create your Catalyst Keychain", + "accountCreationSplashMessage": "Your keychain is your ticket to participate in \u2028distributed innovation on the global stage. \u2028\u2028Once you have it, you'll be able to enter different spaces, discover awesome ideas, and share your feedback to hep improve ideas. \u2028\u2028As you add new keys to your keychain, you'll be able to enter new spaces, unlock new rewards opportunities, and have your voice heard in community decisions.", + "accountCreationSplashNextButton": "Create your Keychain now", + "accountInstructionsTitle": "Great! Your Catalyst Keychain \u2028has been created.", + "accountInstructionsMessage": "On the next screen, you're going to see 12 words. \u2028This is called your \"seed phrase\". \u2028\u2028It's like a super secure password that only you know, \u2028that allows you to prove ownership of your keychain. \u2028\u2028You'll use it to login and recover your account on \u2028different devices, so be sure to put it somewhere safe!\n\nYou need to write this seed phrase down with pen and paper, so get this ready.", + "next": "Next", + "@next": { + "description": "For example in button that goes to next stage of registration" + }, + "back": "Back", + "@back": { + "description": "For example in button that goes to previous stage of registration" + }, + "retry": "Retry", + "@retry": { + "description": "Retry action when something goes wrong." + }, + "somethingWentWrong": "Something went wrong.", + "@somethingWentWrong": { + "description": "Error description when something goes wrong." + }, + "noWalletFound": "No wallet found.", + "@noWalletFound": { + "description": "A description when no wallet extension was found." } } \ No newline at end of file diff --git a/catalyst_voices/packages/catalyst_voices_models/lib/src/catalyst_voices_models.dart b/catalyst_voices/packages/catalyst_voices_models/lib/src/catalyst_voices_models.dart index 3afed13082..2cbe5ea91d 100644 --- a/catalyst_voices/packages/catalyst_voices_models/lib/src/catalyst_voices_models.dart +++ b/catalyst_voices/packages/catalyst_voices_models/lib/src/catalyst_voices_models.dart @@ -7,6 +7,7 @@ export 'errors/errors.dart'; export 'proposal/funded_proposal.dart'; export 'proposal/pending_proposal.dart'; export 'proposal/proposal_status.dart'; +export 'registration/registration.dart'; export 'session_data.dart'; export 'space.dart'; export 'treasury/treasury_campaign_builder.dart'; diff --git a/catalyst_voices/packages/catalyst_voices_models/lib/src/registration/create_account_type.dart b/catalyst_voices/packages/catalyst_voices_models/lib/src/registration/create_account_type.dart new file mode 100644 index 0000000000..891e4e7e63 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_models/lib/src/registration/create_account_type.dart @@ -0,0 +1 @@ +enum CreateAccountType { createNew, recover } diff --git a/catalyst_voices/packages/catalyst_voices_models/lib/src/registration/create_keychain_stage.dart b/catalyst_voices/packages/catalyst_voices_models/lib/src/registration/create_keychain_stage.dart new file mode 100644 index 0000000000..61ca5e435a --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_models/lib/src/registration/create_keychain_stage.dart @@ -0,0 +1,12 @@ +/// Describes the keychain creation flow during registration. +enum CreateKeychainStage { + splash, + instructions, + seedPhrase, + checkSeedPhraseInstructions, + checkSeedPhrase, + checkSeedPhraseResult, + unlockPasswordInstructions, + unlockPasswordCreate, + created, +} diff --git a/catalyst_voices/packages/catalyst_voices_models/lib/src/registration/registration.dart b/catalyst_voices/packages/catalyst_voices_models/lib/src/registration/registration.dart new file mode 100644 index 0000000000..67b3eeedfe --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_models/lib/src/registration/registration.dart @@ -0,0 +1,3 @@ +export 'create_account_type.dart'; +export 'create_keychain_stage.dart'; +export 'wallet_link_stage.dart'; diff --git a/catalyst_voices/packages/catalyst_voices_models/lib/src/registration/wallet_link_stage.dart b/catalyst_voices/packages/catalyst_voices_models/lib/src/registration/wallet_link_stage.dart new file mode 100644 index 0000000000..4b03ab1e87 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_models/lib/src/registration/wallet_link_stage.dart @@ -0,0 +1,20 @@ +/// Describes the link wallet flow during registration. +enum WalletLinkStage { + /// The welcome screen for the link wallet flow. + intro, + + /// A screen where the user is asked to connect the cardano wallet. + selectWallet, + + /// Wallet details after successfully connecting a wallet. + walletDetails, + + /// A user is asked to select the user roles. + rolesChooser, + + /// Summary of chosen user roles. + rolesSummary, + + /// The user submits an RBAC transaction to finish the registration. + rbacTransaction, +} diff --git a/catalyst_voices/packages/catalyst_voices_shared/lib/src/catalyst_voices_shared.dart b/catalyst_voices/packages/catalyst_voices_shared/lib/src/catalyst_voices_shared.dart index b0ad652197..f47e0ebb5a 100644 --- a/catalyst_voices/packages/catalyst_voices_shared/lib/src/catalyst_voices_shared.dart +++ b/catalyst_voices/packages/catalyst_voices_shared/lib/src/catalyst_voices_shared.dart @@ -9,5 +9,6 @@ export 'responsive/responsive_builder.dart'; export 'responsive/responsive_child.dart'; export 'responsive/responsive_padding.dart'; export 'utils/date_time_ext.dart'; +export 'utils/future_ext.dart'; export 'utils/iterable_ext.dart'; export 'utils/typedefs.dart'; diff --git a/catalyst_voices/packages/catalyst_voices_shared/lib/src/utils/future_ext.dart b/catalyst_voices/packages/catalyst_voices_shared/lib/src/utils/future_ext.dart new file mode 100644 index 0000000000..dd215d0545 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_shared/lib/src/utils/future_ext.dart @@ -0,0 +1,16 @@ +extension FutureExt on Future { + /// The minimum loading delay after which the state changes from loading + /// to success/failure will not be perceived too jumpy. + /// + /// Use it to avoid showing a loading state for split second. + static const Duration minimumDelay = Duration(milliseconds: 300); + + /// Returns the result of awaiting the [Future] + /// but applies [delay] to it or [minimumDelay] if [delay] is null. + Future withMinimumDelay([Duration delay = minimumDelay]) async { + final delayed = Future.delayed(delay); + final result = await this; + await delayed; + return result; + } +} diff --git a/catalyst_voices/packages/catalyst_voices_view_models/pubspec.yaml b/catalyst_voices/packages/catalyst_voices_view_models/pubspec.yaml index 5268e3c258..0880d5c290 100644 --- a/catalyst_voices/packages/catalyst_voices_view_models/pubspec.yaml +++ b/catalyst_voices/packages/catalyst_voices_view_models/pubspec.yaml @@ -8,6 +8,9 @@ environment: flutter: ">=3.24.1" dependencies: + catalyst_cardano: ^0.3.0 + catalyst_cardano_serialization: ^0.4.0 + catalyst_cardano_web: ^0.3.0 equatable: ^2.0.5 flutter: sdk: flutter diff --git a/catalyst_voices/pubspec.yaml b/catalyst_voices/pubspec.yaml index e18d1bbbaf..f40af71fe3 100644 --- a/catalyst_voices/pubspec.yaml +++ b/catalyst_voices/pubspec.yaml @@ -46,6 +46,7 @@ dependencies: go_router: ^14.0.2 google_fonts: ^6.2.1 intl: ^0.19.0 + result_type: ^0.2.0 sentry_flutter: ^8.8.0 url_launcher: ^6.2.2 url_strategy: ^0.3.0 diff --git a/catalyst_voices/test/widgets/common/infrastructure/voices_future_builder_test.dart b/catalyst_voices/test/widgets/common/infrastructure/voices_future_builder_test.dart new file mode 100644 index 0000000000..9a7ff45d03 --- /dev/null +++ b/catalyst_voices/test/widgets/common/infrastructure/voices_future_builder_test.dart @@ -0,0 +1,56 @@ +import 'package:catalyst_voices/widgets/common/infrastructure/voices_future_builder.dart'; +import 'package:catalyst_voices/widgets/indicators/voices_circular_progress_indicator.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../helpers/helpers.dart'; + +void main() { + group(VoicesFutureBuilder, () { + testWidgets('Displays data when future completes successfully', + (tester) async { + await tester.pumpApp( + VoicesFutureBuilder( + future: () async => 'Test Data', + dataBuilder: (_, __, ___) => const Text('Success'), + ), + ); + + // Let the future start + await tester.pump(const Duration(milliseconds: 100)); + + // Shows loading indicator while waiting for data + expect(find.byType(VoicesCircularProgressIndicator), findsOneWidget); + + // Let the future finish + await tester.pump(const Duration(seconds: 1)); + + // Shows success state + expect(find.text('Success'), findsOneWidget); + }); + + testWidgets('Displays error when future completes with an error', + (tester) async { + // Act + await tester.pumpApp( + VoicesFutureBuilder( + future: () async => throw Exception('Error'), + dataBuilder: (_, __, ___) => const SizedBox.shrink(), + errorBuilder: (_, __, ___) => const Text('Error Occurred'), + ), + ); + + // Let the future start + await tester.pump(const Duration(milliseconds: 100)); + + // Shows loading indicator while waiting for data + expect(find.byType(VoicesCircularProgressIndicator), findsOneWidget); + + // Let the future finish + await tester.pump(const Duration(seconds: 1)); + + // Shows error state + expect(find.text('Error Occurred'), findsOneWidget); + }); + }); +} diff --git a/catalyst_voices/test/widgets/indicators/voices_status_indicator_test.dart b/catalyst_voices/test/widgets/indicators/voices_status_indicator_test.dart index bd7469e25f..a9df84e169 100644 --- a/catalyst_voices/test/widgets/indicators/voices_status_indicator_test.dart +++ b/catalyst_voices/test/widgets/indicators/voices_status_indicator_test.dart @@ -11,7 +11,7 @@ void main() { // Arrange const status = 'QR VERIFIED'; const title = 'Your QR code verified successfully'; - const body = 'You can now use your QR-code 
to login into Catalyst.'; + const body = 'You can now use your QR-code to login into Catalyst.'; const colors = VoicesColorScheme.optional( successContainer: Colors.green, diff --git a/catalyst_voices/uikit_example/lib/examples/voices_indicators_example.dart b/catalyst_voices/uikit_example/lib/examples/voices_indicators_example.dart index 60c4b6c294..684f897298 100644 --- a/catalyst_voices/uikit_example/lib/examples/voices_indicators_example.dart +++ b/catalyst_voices/uikit_example/lib/examples/voices_indicators_example.dart @@ -80,6 +80,19 @@ class VoicesIndicatorsExample extends StatelessWidget { VoicesCircularProgressIndicator(value: 0.75, showTrack: false), ], ), + const Text('Generic error indicator'), + Row( + children: [ + VoicesErrorIndicator( + message: 'Something went wrong', + onRetry: () {}, + ), + const SizedBox(width: 16), + const VoicesErrorIndicator( + message: 'Something went wrong', + ), + ], + ), const Text('No Internet Connection Banner'), const NoInternetConnectionBanner(), const Text('Password strength indicator'), diff --git a/utilities/wallet-tester/package-lock.json b/utilities/wallet-tester/package-lock.json index 0cce2b825c..c1873bd412 100644 --- a/utilities/wallet-tester/package-lock.json +++ b/utilities/wallet-tester/package-lock.json @@ -37,7 +37,7 @@ "postcss": "^8.4.35", "tailwindcss": "^3.4.1", "typescript": "^5.4.2", - "vite": "^5.1.7", + "vite": "^5.4.7", "vite-tsconfig-paths": "^4.3.1" }, "optionalDependencies": { @@ -521,9 +521,9 @@ "integrity": "sha512-QQkavjEug/EmBFg02bmSg0eLYiOaRa1lmRG8q6fGRkx3O9BX1iZQDZJmirPDBTkTO3CpNB0q9se8DQFFcjREIw==" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -537,9 +537,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -553,9 +553,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -569,9 +569,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -585,9 +585,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -601,9 +601,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -617,9 +617,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -633,9 +633,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -649,9 +649,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -665,9 +665,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -681,9 +681,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -697,9 +697,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -713,9 +713,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -729,9 +729,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -745,9 +745,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -761,9 +761,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -777,9 +777,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -793,9 +793,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -809,9 +809,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -825,9 +825,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -841,9 +841,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -857,9 +857,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -873,9 +873,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -1463,9 +1463,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", - "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", "cpu": [ "arm" ], @@ -1476,9 +1476,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", - "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", "cpu": [ "arm64" ], @@ -1489,9 +1489,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", - "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", "cpu": [ "arm64" ], @@ -1502,9 +1502,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", - "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", "cpu": [ "x64" ], @@ -1515,9 +1515,22 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", - "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", "cpu": [ "arm" ], @@ -1528,9 +1541,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", - "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", "cpu": [ "arm64" ], @@ -1541,9 +1554,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", - "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", "cpu": [ "arm64" ], @@ -1553,10 +1566,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", - "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", "cpu": [ "riscv64" ], @@ -1566,10 +1592,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", - "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", "cpu": [ "x64" ], @@ -1580,9 +1619,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", - "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", "cpu": [ "x64" ], @@ -1593,9 +1632,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", - "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", "cpu": [ "arm64" ], @@ -1606,9 +1645,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", - "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", "cpu": [ "ia32" ], @@ -1619,9 +1658,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", - "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", "cpu": [ "x64" ], @@ -3474,9 +3513,9 @@ } }, "node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -3486,29 +3525,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { @@ -5604,9 +5643,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "dev": true }, "node_modules/picomatch": { @@ -5649,9 +5688,9 @@ } }, "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -5669,8 +5708,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -6103,9 +6142,9 @@ } }, "node_modules/rollup": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", - "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -6118,19 +6157,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.13.0", - "@rollup/rollup-android-arm64": "4.13.0", - "@rollup/rollup-darwin-arm64": "4.13.0", - "@rollup/rollup-darwin-x64": "4.13.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", - "@rollup/rollup-linux-arm64-gnu": "4.13.0", - "@rollup/rollup-linux-arm64-musl": "4.13.0", - "@rollup/rollup-linux-riscv64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-musl": "4.13.0", - "@rollup/rollup-win32-arm64-msvc": "4.13.0", - "@rollup/rollup-win32-ia32-msvc": "4.13.0", - "@rollup/rollup-win32-x64-msvc": "4.13.0", + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", "fsevents": "~2.3.2" } }, @@ -6331,9 +6373,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -6963,14 +7005,14 @@ "dev": true }, "node_modules/vite": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.7.tgz", - "integrity": "sha512-sgnEEFTZYMui/sTlH1/XEnVNHMujOahPLGMxn1+5sIT45Xjng1Ec1K78jRP15dSmVgg5WBin9yO81j3o9OxofA==", + "version": "5.4.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz", + "integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==", "dev": true, "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -6989,6 +7031,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -7006,6 +7049,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, diff --git a/utilities/wallet-tester/package.json b/utilities/wallet-tester/package.json index ac77cf7219..2be5b52baf 100644 --- a/utilities/wallet-tester/package.json +++ b/utilities/wallet-tester/package.json @@ -42,7 +42,7 @@ "postcss": "^8.4.35", "tailwindcss": "^3.4.1", "typescript": "^5.4.2", - "vite": "^5.1.7", + "vite": "^5.4.7", "vite-tsconfig-paths": "^4.3.1" }, "optionalDependencies": {