import "dart:convert"; import "package:bus_running_record/provider/collaboration_state.dart"; import "package:bus_running_record/provider/supabase_state.dart"; import "package:provider/provider.dart"; import "package:shadcn_flutter/shadcn_flutter.dart"; Future showCreateOrganizationDialog(BuildContext context) async { final result = await showDialog( context: context, builder: (dialogContext) { String name = ""; return AlertDialog( title: const Text("Create Organization"), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text("Name your new organization."), const Gap(12), ConstrainedBox( constraints: const BoxConstraints(maxWidth: 400), child: TextField( autofocus: true, placeholder: const Text("Enter organization name"), onChanged: (value) { name = value; }, onSubmitted: (_) { Navigator.of(dialogContext).pop(name); }, ), ), ], ), actions: [ Button.text( onPressed: () => Navigator.of(dialogContext).pop(), child: const Text("Cancel"), ), Button.primary( onPressed: () => Navigator.of(dialogContext).pop(name), child: const Text("Create"), ), ], ); }, ); final name = (result ?? "").trim(); if (name.isEmpty) { return; } if (!context.mounted) return; await context.read().createOrganization(name); } Future showCreateChannelDialog( BuildContext context, { required String organizationId, }) async { final result = await showDialog>( context: context, builder: (dialogContext) => const _CreateChannelDialog(), ); final channelName = (result?["name"] ?? "").trim(); final channelDescription = (result?["description"] ?? "").trim(); final channelType = (result?["type"] ?? "text").trim(); if (channelName.isEmpty) return; if (!context.mounted) return; try { await context.read().createChannel( organizationId: organizationId, name: channelName, description: channelDescription, type: channelType, ); } catch (error, stackTrace) { debugPrint("[HomePage] createChannel dialog failed: $error"); debugPrintStack(stackTrace: stackTrace); if (!context.mounted) return; await showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text("Create Channel Failed"), content: Text(error.toString()), actions: [ Button.primary( onPressed: () => Navigator.of(dialogContext).pop(), child: const Text("Close"), ), ], ), ); } } String extractInviteToken(String input) { final trimmed = input.trim(); if (trimmed.isEmpty) return ""; final invitePathMatch = RegExp(r"/invite/([0-9a-fA-F]+)").firstMatch(trimmed); if (invitePathMatch != null) { return (invitePathMatch.group(1) ?? "").toLowerCase(); } final hashInviteMatch = RegExp( r"#/invite/([0-9a-fA-F]+)", ).firstMatch(trimmed); if (hashInviteMatch != null) { return (hashInviteMatch.group(1) ?? "").toLowerCase(); } return trimmed.toLowerCase(); } Future showJoinOrganizationDialog(BuildContext context) async { final result = await showDialog( context: context, builder: (dialogContext) { String inviteInput = ""; return AlertDialog( title: const Text("Join Organization"), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text("Paste an invite link or invite token."), const Gap(12), ConstrainedBox( constraints: const BoxConstraints(maxWidth: 420), child: TextField( autofocus: true, placeholder: const Text("https://.../#/invite/"), onChanged: (value) { inviteInput = value; }, onSubmitted: (_) { Navigator.of(dialogContext).pop(inviteInput); }, ), ), ], ), actions: [ Button.text( onPressed: () => Navigator.of(dialogContext).pop(), child: const Text("Cancel"), ), Button.primary( onPressed: () => Navigator.of(dialogContext).pop(inviteInput), child: const Text("Join"), ), ], ); }, ); final token = extractInviteToken(result ?? ""); if (token.isEmpty) return; if (!context.mounted) return; try { await context.read().acceptInviteToken(token); if (!context.mounted) return; await showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text("Joined"), content: const Text("You have joined the organization."), actions: [ Button.primary( onPressed: () => Navigator.of(dialogContext).pop(), child: const Text("Close"), ), ], ), ); } catch (error, stackTrace) { debugPrint("[HomePage] join organization failed: $error"); debugPrintStack(stackTrace: stackTrace); if (!context.mounted) return; await showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text("Join Failed"), content: Text(error.toString()), actions: [ Button.primary( onPressed: () => Navigator.of(dialogContext).pop(), child: const Text("Close"), ), ], ), ); } } Future runAuthDebug(BuildContext context) async { final client = context.read().client; final encoder = const JsonEncoder.withIndent(" "); try { final response = await client.functions.invoke("auth-debug", body: {}); final payload = response.data; final formatted = payload is Map || payload is List ? encoder.convert(payload) : payload.toString(); if (!context.mounted) return; await showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text("Auth Debug Response"), content: SizedBox( width: 600, child: SingleChildScrollView(child: Text(formatted).small), ), actions: [ Button.primary( onPressed: () => Navigator.of(dialogContext).pop(), child: const Text("Close"), ), ], ), ); } catch (error, stackTrace) { debugPrint("[HomePage] auth-debug failed (${error.runtimeType}): $error"); debugPrintStack(stackTrace: stackTrace); if (!context.mounted) return; await showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text("Auth Debug Failed"), content: Text(error.toString()), actions: [ Button.primary( onPressed: () => Navigator.of(dialogContext).pop(), child: const Text("Close"), ), ], ), ); } } class _CreateChannelDialog extends StatelessWidget { const _CreateChannelDialog(); @override Widget build(BuildContext context) { final selectedType = ValueNotifier(1); final channelName = ValueNotifier(""); final channelDescription = ValueNotifier(""); return AlertDialog( title: const Text("Create Channel"), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ ValueListenableBuilder( valueListenable: selectedType, builder: (context, value, child) { return RadioGroup( value: value, onChanged: (v) { selectedType.value = v; }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: 8, children: [ RadioItem( value: 1, trailing: Basic( title: Text("Text Channel").large, subtitle: Text( "Standard chat channel for communications and discussions.", ), ), ), RadioItem( value: 2, trailing: Basic( title: Text("Operations Channel").large, subtitle: Text( "Upload a schedule document and interact with it in a way that matters.", ), ), ), ], ), ); }, ), const Gap(12), const Text("Channel Name").medium.semiBold, const Gap(8), ConstrainedBox( constraints: const BoxConstraints(maxWidth: 440), child: TextField( autofocus: true, features: [ InputFeature.leading( Icon( LucideIcons.hash, color: Theme.of(context).colorScheme.mutedForeground, ).iconSmall, ), ], placeholder: const Text("general"), onChanged: (value) { channelName.value = value; }, onSubmitted: (_) { Navigator.of(context).pop({ "name": channelName.value, "type": _channelTypeFromValue(selectedType.value), }); }, ), ), const Gap(12), const Text("Channel Description").medium.semiBold, const Gap(8), ConstrainedBox( constraints: const BoxConstraints(maxWidth: 440), child: TextField( placeholder: const Text("What this channel is for"), onChanged: (value) { channelDescription.value = value; }, onSubmitted: (_) { Navigator.of(context).pop({ "name": channelName.value, "description": channelDescription.value, "type": _channelTypeFromValue(selectedType.value), }); }, ), ), ], ), actions: [ Button.text( onPressed: () => Navigator.of(context).pop(), child: const Text("Cancel"), ), Button.primary( onPressed: () => Navigator.of(context).pop({ "name": channelName.value, "description": channelDescription.value, "type": _channelTypeFromValue(selectedType.value), }), child: const Text("Create"), ), ], ); } } String _channelTypeFromValue(int value) { switch (value) { case 2: return "operations"; default: return "text"; } }