From 6ad0824d810664d92e9c12dbe2890df6c3c4687c Mon Sep 17 00:00:00 2001 From: leok18 <178525594+leok18@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:04:42 -0800 Subject: [PATCH 1/3] add way to edit team email --- lib/pages/settings.dart | 256 +++++++++++++++++++- lib/reusable/lovat_api/edit_team_email.dart | 17 ++ 2 files changed, 260 insertions(+), 13 deletions(-) diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 5904a5c9..c93c3f86 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -11,6 +11,7 @@ import 'package:scouting_dashboard_app/reusable/emphasized_container.dart'; import 'package:scouting_dashboard_app/reusable/friendly_error_view.dart'; import 'package:scouting_dashboard_app/reusable/inset_picker.dart'; import 'package:scouting_dashboard_app/reusable/lovat_api/delete_account.dart'; +import 'package:scouting_dashboard_app/reusable/lovat_api/edit_team_email.dart'; import 'package:scouting_dashboard_app/reusable/lovat_api/get_analysts.dart'; import 'package:scouting_dashboard_app/reusable/lovat_api/get_csv_export.dart'; import 'package:scouting_dashboard_app/reusable/lovat_api/get_team_code.dart'; @@ -102,6 +103,7 @@ class _SettingsPageState extends State { label: const Text("Export CSV"), ), ], + const EmailBox(), const AnalystsBox(), const SizedBox(height: 40), const ResetAppButton(), @@ -188,7 +190,7 @@ class _TeamSourceSelectorState extends State { bool thisTeamLoaded = false; bool get isLoading => mode == null || !thisTeamLoaded; - String? errorMesssage; + String? errorMessage; Future load() async { try { @@ -200,7 +202,7 @@ class _TeamSourceSelectorState extends State { }); } catch (e) { setState(() { - errorMesssage = "Failed to load source teams"; + errorMessage = "Failed to load source teams"; }); } @@ -214,7 +216,7 @@ class _TeamSourceSelectorState extends State { }); } catch (e) { setState(() { - errorMesssage = "Failed to load profile"; + errorMessage = "Failed to load profile"; }); } } @@ -227,7 +229,7 @@ class _TeamSourceSelectorState extends State { @override Widget build(BuildContext context) { - if (isLoading && errorMesssage == null) { + if (isLoading && errorMessage == null) { return const SkeletonAvatar( style: SkeletonAvatarStyle( width: 200, @@ -237,7 +239,7 @@ class _TeamSourceSelectorState extends State { ); } - if (isLoading && errorMesssage != null) { + if (isLoading && errorMessage != null) { return Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceVariant, @@ -245,7 +247,7 @@ class _TeamSourceSelectorState extends State { ), height: 48, child: Center( - child: MediumErrorMessage(message: errorMesssage), + child: MediumErrorMessage(message: errorMessage), ), ); } @@ -291,7 +293,7 @@ class _TeamSourceSelectorState extends State { await load(); } catch (e) { setState(() { - errorMesssage = "Failed to save source team settings"; + errorMessage = "Failed to save source team settings"; }); } })(); @@ -321,7 +323,7 @@ class _TeamSourceSelectorState extends State { ); } catch (e) { setState(() { - errorMesssage = "Failed to save source team settings"; + errorMessage = "Failed to save source team settings"; }); } }, @@ -330,10 +332,10 @@ class _TeamSourceSelectorState extends State { } }, ), - if (errorMesssage != null) + if (errorMessage != null) Padding( padding: const EdgeInsets.only(top: 10), - child: MediumErrorMessage(message: errorMesssage), + child: MediumErrorMessage(message: errorMessage), ), ], ); @@ -886,6 +888,125 @@ class _AnalystsBoxState extends State { } } +class EmailBox extends StatefulWidget { + const EmailBox({super.key}); + + @override + State createState() => _EmailBoxState(); +} + +class _EmailBoxState extends State { + String? email; + bool loaded = false; + String? errorMessage; + + Future load() async { + try { + final email = await lovatAPI.getTeamEmail(); + + setState(() { + this.email = email; + }); + } catch (e) { + setState(() { + errorMessage = "Failed to load email"; + }); + } finally { + setState(() { + loaded = true; + }); + } + } + + @override + void initState() { + super.initState(); + load(); + } + + @override + Widget build(BuildContext context) { + if (!loaded && errorMessage == null) { + return const SkeletonAvatar( + style: SkeletonAvatarStyle( + width: 200, + height: 60, + borderRadius: BorderRadius.all(Radius.circular(4)), + ), + ); + } + + if (!loaded && errorMessage != null) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceVariant, + borderRadius: BorderRadius.circular(4), + ), + height: 60, + child: Center( + child: MediumErrorMessage(message: errorMessage), + ), + ); + } + + if (loaded && email == null) { + return Container(); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 28), + Text( + "Team email", + style: Theme.of(context).textTheme.labelLarge, + ), + const SizedBox(height: 7), + Container( + height: 60, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceVariant, + borderRadius: BorderRadius.circular(4), + ), + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + email!, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + TextButton( + onPressed: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => ChangeEmailDialog(onAdd: load), + ); + }, + child: const Text("Change"), + ), + ], + ), + ), + ), + if (errorMessage != null) + Padding( + padding: const EdgeInsets.only(top: 10), + child: MediumErrorMessage(message: errorMessage), + ), + ], + ); + } +} + class AnalystPromotionPage extends StatefulWidget { const AnalystPromotionPage({super.key, this.onSubmit}); @@ -1140,10 +1261,10 @@ class _DeleteConfigurationDialogState extends State { }); }, style: ButtonStyle( - elevation: MaterialStateProperty.all(0), - backgroundColor: MaterialStateProperty.resolveWith( + elevation: WidgetStateProperty.all(0), + backgroundColor: WidgetStateProperty.resolveWith( (states) => Theme.of(context).colorScheme.error), - foregroundColor: MaterialStateProperty.resolveWith( + foregroundColor: WidgetStateProperty.resolveWith( (states) => Theme.of(context).colorScheme.onError), ), child: const Text("Delete"), @@ -1373,3 +1494,112 @@ class _DataExportPageState extends State { ); } } + +class ChangeEmailDialog extends StatefulWidget { + const ChangeEmailDialog({ + super.key, + this.onAdd, + }); + + final Function()? onAdd; + + @override + State createState() => _ChangeEmailDialogState(); +} + +class _ChangeEmailDialogState extends State { + String email = ''; + bool submitting = false; + bool submitted = false; + String? error; + + @override + Widget build(BuildContext context) { + if (submitted) { + return AlertDialog( + title: const Text("Check the team's inbox"), + content: const Text( + "We've sent an email to your team with a link to verify that it belongs to your team."), + actions: [ + TextButton( + child: const Text("Close"), + onPressed: () => Navigator.of(context).pop()) + ], + ); + } else { + return AlertDialog( + title: const Text("Change email"), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + autofocus: true, + decoration: InputDecoration( + labelText: "New Email", + filled: true, + errorText: error, + ), + textCapitalization: TextCapitalization.none, + keyboardType: TextInputType.emailAddress, + onChanged: (value) { + setState(() { + email = value; + }); + }, + ), + ], + ), + actions: [ + TextButton( + onPressed: submitting + ? null + : () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + FilledButton( + onPressed: submitting || email.isEmpty + ? null + : () async { + setState(() { + submitting = true; + error = null; + }); + + try { + await lovatAPI.editTeamEmail(email); + widget.onAdd?.call(); + setState(() { + submitting = false; + submitted = true; + }); + } on LovatAPIException catch (e) { + setState(() { + error = e.message; + submitting = false; + }); + } catch (_) { + setState(() { + error = "Failed to update email"; + submitting = false; + }); + } + }, + child: submitting + ? const SizedBox( + height: 16, + width: 16, + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Colors.white), + strokeWidth: 2, + ), + ) + : const Text("Update"), + ), + ], + ); + } + ; + } +} diff --git a/lib/reusable/lovat_api/edit_team_email.dart b/lib/reusable/lovat_api/edit_team_email.dart index 8ed4dd4b..6151756e 100644 --- a/lib/reusable/lovat_api/edit_team_email.dart +++ b/lib/reusable/lovat_api/edit_team_email.dart @@ -23,4 +23,21 @@ extension EditTeamEmail on LovatAPI { } } } + + Future getTeamEmail() async { + final response = await lovatAPI.get('/v1/manager/settings/teamemail'); + + if (response?.statusCode != 200) { + debugPrint(response?.body ?? ''); + try { + throw LovatAPIException(jsonDecode(response!.body)['displayError']); + } on LovatAPIException { + rethrow; + } catch (_) { + throw Exception('Failed to get team email'); + } + } else { + return response!.body; + } + } } From 18420ee67c006107d47f4cec167e9f6e6f408b63 Mon Sep 17 00:00:00 2001 From: leok18 <178525594+leok18@users.noreply.github.com> Date: Mon, 23 Feb 2026 15:59:33 -0800 Subject: [PATCH 2/3] fix issues or whatever --- lib/pages/settings.dart | 5 ++--- lib/pages/team_lookup/tabs/team_lookup_notes.dart | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 3676c09d..5cb63756 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -941,7 +941,7 @@ class _EmailBoxState extends State { if (!loaded && errorMessage != null) { return Container( decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceVariant, + color: Theme.of(context).colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(4), ), height: 60, @@ -967,7 +967,7 @@ class _EmailBoxState extends State { Container( height: 60, decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceVariant, + color: Theme.of(context).colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(4), ), child: Padding( @@ -1602,6 +1602,5 @@ class _ChangeEmailDialogState extends State { ], ); } - ; } } diff --git a/lib/pages/team_lookup/tabs/team_lookup_notes.dart b/lib/pages/team_lookup/tabs/team_lookup_notes.dart index a2296307..567348c9 100644 --- a/lib/pages/team_lookup/tabs/team_lookup_notes.dart +++ b/lib/pages/team_lookup/tabs/team_lookup_notes.dart @@ -112,7 +112,6 @@ class RobotBrokeBox extends StatelessWidget { .toColor(); const double iconSize = 24; - const double horizontalSpacing = 7; final String description = "View ${breakDescriptions.length} ${breakDescriptions.length > 1 ? "reports" : "report"}"; From 99a1c9aa3836b667144d813dc17fb24dea564d30 Mon Sep 17 00:00:00 2001 From: leok18 <178525594+leok18@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:57:28 -0800 Subject: [PATCH 3/3] improve team email changing --- lib/pages/settings.dart | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 5cb63756..a1dd273a 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -103,7 +103,8 @@ class _SettingsPageState extends State { label: const Text("Export CSV"), ), ], - const EmailBox(), + if (cachedUserProfile?.role == UserRole.scoutingLead) + const EmailBox(), const AnalystsBox(), const SizedBox(height: 40), const ResetAppButton(), @@ -929,12 +930,23 @@ class _EmailBoxState extends State { @override Widget build(BuildContext context) { if (!loaded && errorMessage == null) { - return const SkeletonAvatar( - style: SkeletonAvatarStyle( - width: 200, - height: 60, - borderRadius: BorderRadius.all(Radius.circular(4)), - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 28), + Text( + "Team email", + style: Theme.of(context).textTheme.labelLarge, + ), + const SizedBox(height: 7), + const SkeletonAvatar( + style: SkeletonAvatarStyle( + width: 200, + height: 60, + borderRadius: BorderRadius.all(Radius.circular(4)), + ), + ), + ], ); }