@@ -11,6 +11,7 @@ import 'package:scouting_dashboard_app/reusable/emphasized_container.dart';
1111import 'package:scouting_dashboard_app/reusable/friendly_error_view.dart' ;
1212import 'package:scouting_dashboard_app/reusable/inset_picker.dart' ;
1313import 'package:scouting_dashboard_app/reusable/lovat_api/delete_account.dart' ;
14+ import 'package:scouting_dashboard_app/reusable/lovat_api/edit_team_email.dart' ;
1415import 'package:scouting_dashboard_app/reusable/lovat_api/get_analysts.dart' ;
1516import 'package:scouting_dashboard_app/reusable/lovat_api/get_csv_export.dart' ;
1617import 'package:scouting_dashboard_app/reusable/lovat_api/get_team_code.dart' ;
@@ -102,6 +103,8 @@ class _SettingsPageState extends State<SettingsPage> {
102103 label: const Text ("Export CSV" ),
103104 ),
104105 ],
106+ if (cachedUserProfile? .role == UserRole .scoutingLead)
107+ const EmailBox (),
105108 const AnalystsBox (),
106109 const SizedBox (height: 40 ),
107110 const ResetAppButton (),
@@ -190,7 +193,7 @@ class _TeamSourceSelectorState extends State<TeamSourceSelector> {
190193 bool thisTeamLoaded = false ;
191194
192195 bool get isLoading => mode == null || ! thisTeamLoaded;
193- String ? errorMesssage ;
196+ String ? errorMessage ;
194197
195198 Future <void > load () async {
196199 try {
@@ -202,7 +205,7 @@ class _TeamSourceSelectorState extends State<TeamSourceSelector> {
202205 });
203206 } catch (e) {
204207 setState (() {
205- errorMesssage = "Failed to load source teams" ;
208+ errorMessage = "Failed to load source teams" ;
206209 });
207210 }
208211
@@ -216,7 +219,7 @@ class _TeamSourceSelectorState extends State<TeamSourceSelector> {
216219 });
217220 } catch (e) {
218221 setState (() {
219- errorMesssage = "Failed to load profile" ;
222+ errorMessage = "Failed to load profile" ;
220223 });
221224 }
222225 }
@@ -229,7 +232,7 @@ class _TeamSourceSelectorState extends State<TeamSourceSelector> {
229232
230233 @override
231234 Widget build (BuildContext context) {
232- if (isLoading && errorMesssage == null ) {
235+ if (isLoading && errorMessage == null ) {
233236 return const SkeletonAvatar (
234237 style: SkeletonAvatarStyle (
235238 width: 200 ,
@@ -239,15 +242,15 @@ class _TeamSourceSelectorState extends State<TeamSourceSelector> {
239242 );
240243 }
241244
242- if (isLoading && errorMesssage != null ) {
245+ if (isLoading && errorMessage != null ) {
243246 return Container (
244247 decoration: BoxDecoration (
245248 color: Theme .of (context).colorScheme.surfaceContainerHighest,
246249 borderRadius: BorderRadius .circular (50 ),
247250 ),
248251 height: 48 ,
249252 child: Center (
250- child: MediumErrorMessage (message: errorMesssage ),
253+ child: MediumErrorMessage (message: errorMessage ),
251254 ),
252255 );
253256 }
@@ -293,7 +296,7 @@ class _TeamSourceSelectorState extends State<TeamSourceSelector> {
293296 await load ();
294297 } catch (e) {
295298 setState (() {
296- errorMesssage = "Failed to save source team settings" ;
299+ errorMessage = "Failed to save source team settings" ;
297300 });
298301 }
299302 })();
@@ -323,7 +326,7 @@ class _TeamSourceSelectorState extends State<TeamSourceSelector> {
323326 );
324327 } catch (e) {
325328 setState (() {
326- errorMesssage = "Failed to save source team settings" ;
329+ errorMessage = "Failed to save source team settings" ;
327330 });
328331 }
329332 },
@@ -332,10 +335,10 @@ class _TeamSourceSelectorState extends State<TeamSourceSelector> {
332335 }
333336 },
334337 ),
335- if (errorMesssage != null )
338+ if (errorMessage != null )
336339 Padding (
337340 padding: const EdgeInsets .only (top: 10 ),
338- child: MediumErrorMessage (message: errorMesssage ),
341+ child: MediumErrorMessage (message: errorMessage ),
339342 ),
340343 ],
341344 );
@@ -888,6 +891,136 @@ class _AnalystsBoxState extends State<AnalystsBox> {
888891 }
889892}
890893
894+ class EmailBox extends StatefulWidget {
895+ const EmailBox ({super .key});
896+
897+ @override
898+ State <EmailBox > createState () => _EmailBoxState ();
899+ }
900+
901+ class _EmailBoxState extends State <EmailBox > {
902+ String ? email;
903+ bool loaded = false ;
904+ String ? errorMessage;
905+
906+ Future <void > load () async {
907+ try {
908+ final email = await lovatAPI.getTeamEmail ();
909+
910+ setState (() {
911+ this .email = email;
912+ });
913+ } catch (e) {
914+ setState (() {
915+ errorMessage = "Failed to load email" ;
916+ });
917+ } finally {
918+ setState (() {
919+ loaded = true ;
920+ });
921+ }
922+ }
923+
924+ @override
925+ void initState () {
926+ super .initState ();
927+ load ();
928+ }
929+
930+ @override
931+ Widget build (BuildContext context) {
932+ if (! loaded && errorMessage == null ) {
933+ return Column (
934+ crossAxisAlignment: CrossAxisAlignment .stretch,
935+ children: [
936+ const SizedBox (height: 28 ),
937+ Text (
938+ "Team email" ,
939+ style: Theme .of (context).textTheme.labelLarge,
940+ ),
941+ const SizedBox (height: 7 ),
942+ const SkeletonAvatar (
943+ style: SkeletonAvatarStyle (
944+ width: 200 ,
945+ height: 60 ,
946+ borderRadius: BorderRadius .all (Radius .circular (4 )),
947+ ),
948+ ),
949+ ],
950+ );
951+ }
952+
953+ if (! loaded && errorMessage != null ) {
954+ return Container (
955+ decoration: BoxDecoration (
956+ color: Theme .of (context).colorScheme.surfaceContainerHighest,
957+ borderRadius: BorderRadius .circular (4 ),
958+ ),
959+ height: 60 ,
960+ child: Center (
961+ child: MediumErrorMessage (message: errorMessage),
962+ ),
963+ );
964+ }
965+
966+ if (loaded && email == null ) {
967+ return Container ();
968+ }
969+
970+ return Column (
971+ crossAxisAlignment: CrossAxisAlignment .stretch,
972+ children: [
973+ const SizedBox (height: 28 ),
974+ Text (
975+ "Team email" ,
976+ style: Theme .of (context).textTheme.labelLarge,
977+ ),
978+ const SizedBox (height: 7 ),
979+ Container (
980+ height: 60 ,
981+ decoration: BoxDecoration (
982+ color: Theme .of (context).colorScheme.surfaceContainerHighest,
983+ borderRadius: BorderRadius .circular (4 ),
984+ ),
985+ child: Padding (
986+ padding: const EdgeInsets .all (12 ),
987+ child: Row (
988+ mainAxisAlignment: MainAxisAlignment .spaceBetween,
989+ children: [
990+ Flexible (
991+ child: Text (
992+ email! ,
993+ style: TextStyle (
994+ color: Theme .of (context).colorScheme.onSurfaceVariant,
995+ ),
996+ maxLines: 1 ,
997+ overflow: TextOverflow .ellipsis,
998+ ),
999+ ),
1000+ TextButton (
1001+ onPressed: () {
1002+ showDialog (
1003+ context: context,
1004+ barrierDismissible: false ,
1005+ builder: (context) => ChangeEmailDialog (onAdd: load),
1006+ );
1007+ },
1008+ child: const Text ("Change" ),
1009+ ),
1010+ ],
1011+ ),
1012+ ),
1013+ ),
1014+ if (errorMessage != null )
1015+ Padding (
1016+ padding: const EdgeInsets .only (top: 10 ),
1017+ child: MediumErrorMessage (message: errorMessage),
1018+ ),
1019+ ],
1020+ );
1021+ }
1022+ }
1023+
8911024class AnalystPromotionPage extends StatefulWidget {
8921025 const AnalystPromotionPage ({super .key, this .onSubmit});
8931026
@@ -1375,3 +1508,111 @@ class _DataExportPageState extends State<DataExportPage> {
13751508 );
13761509 }
13771510}
1511+
1512+ class ChangeEmailDialog extends StatefulWidget {
1513+ const ChangeEmailDialog ({
1514+ super .key,
1515+ this .onAdd,
1516+ });
1517+
1518+ final Function ()? onAdd;
1519+
1520+ @override
1521+ State <ChangeEmailDialog > createState () => _ChangeEmailDialogState ();
1522+ }
1523+
1524+ class _ChangeEmailDialogState extends State <ChangeEmailDialog > {
1525+ String email = '' ;
1526+ bool submitting = false ;
1527+ bool submitted = false ;
1528+ String ? error;
1529+
1530+ @override
1531+ Widget build (BuildContext context) {
1532+ if (submitted) {
1533+ return AlertDialog (
1534+ title: const Text ("Check the team's inbox" ),
1535+ content: const Text (
1536+ "We've sent an email to your team with a link to verify that it belongs to your team." ),
1537+ actions: [
1538+ TextButton (
1539+ child: const Text ("Close" ),
1540+ onPressed: () => Navigator .of (context).pop ())
1541+ ],
1542+ );
1543+ } else {
1544+ return AlertDialog (
1545+ title: const Text ("Change email" ),
1546+ content: Column (
1547+ mainAxisSize: MainAxisSize .min,
1548+ children: [
1549+ TextField (
1550+ autofocus: true ,
1551+ decoration: InputDecoration (
1552+ labelText: "New Email" ,
1553+ filled: true ,
1554+ errorText: error,
1555+ ),
1556+ textCapitalization: TextCapitalization .none,
1557+ keyboardType: TextInputType .emailAddress,
1558+ onChanged: (value) {
1559+ setState (() {
1560+ email = value;
1561+ });
1562+ },
1563+ ),
1564+ ],
1565+ ),
1566+ actions: [
1567+ TextButton (
1568+ onPressed: submitting
1569+ ? null
1570+ : () {
1571+ Navigator .of (context).pop ();
1572+ },
1573+ child: const Text ("Cancel" ),
1574+ ),
1575+ FilledButton (
1576+ onPressed: submitting || email.isEmpty
1577+ ? null
1578+ : () async {
1579+ setState (() {
1580+ submitting = true ;
1581+ error = null ;
1582+ });
1583+
1584+ try {
1585+ await lovatAPI.editTeamEmail (email);
1586+ widget.onAdd? .call ();
1587+ setState (() {
1588+ submitting = false ;
1589+ submitted = true ;
1590+ });
1591+ } on LovatAPIException catch (e) {
1592+ setState (() {
1593+ error = e.message;
1594+ submitting = false ;
1595+ });
1596+ } catch (_) {
1597+ setState (() {
1598+ error = "Failed to update email" ;
1599+ submitting = false ;
1600+ });
1601+ }
1602+ },
1603+ child: submitting
1604+ ? const SizedBox (
1605+ height: 16 ,
1606+ width: 16 ,
1607+ child: CircularProgressIndicator (
1608+ valueColor: AlwaysStoppedAnimation (Colors .white),
1609+ strokeWidth: 2 ,
1610+ ),
1611+ )
1612+ : const Text ("Update" ),
1613+ ),
1614+ ],
1615+ );
1616+ }
1617+ }
1618+ }
0 commit comments