From fbc8b8b9d49261346cad71967cecc8ebc5ac06e4 Mon Sep 17 00:00:00 2001 From: sBubshait Date: Thu, 7 Aug 2025 04:07:09 +0300 Subject: [PATCH] feat: creating new invitation codes from Settings for admins --- .../lib/screens/create_invitation_screen.dart | 368 ++++++++++++++++++ frontend/lib/screens/pages/profile_page.dart | 9 +- 2 files changed, 374 insertions(+), 3 deletions(-) create mode 100644 frontend/lib/screens/create_invitation_screen.dart diff --git a/frontend/lib/screens/create_invitation_screen.dart b/frontend/lib/screens/create_invitation_screen.dart new file mode 100644 index 0000000..b518552 --- /dev/null +++ b/frontend/lib/screens/create_invitation_screen.dart @@ -0,0 +1,368 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:convert'; +import '../services/http_service.dart'; +import '../constants/api_constants.dart'; + +class CreateInvitationScreen extends StatefulWidget { + @override + _CreateInvitationScreenState createState() => _CreateInvitationScreenState(); +} + +class _CreateInvitationScreenState extends State { + bool _isLoading = false; + Map? _createdInvitation; + + Future _createInvitationCode() async { + setState(() { + _isLoading = true; + }); + + try { + final response = await HttpService.post(ApiConstants.createInvitationCodeEndpoint, {}); + final data = json.decode(response.body); + + setState(() { + _isLoading = false; + }); + + if (data['status'] == true && data['data'] != null) { + setState(() { + _createdInvitation = data['data']; + }); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(data['message'] ?? 'Invitation code created successfully'), + backgroundColor: Colors.green, + duration: Duration(seconds: 3), + ), + ); + } else { + _showErrorAlert(data['message'] ?? 'Failed to create invitation code'); + } + } catch (e) { + setState(() { + _isLoading = false; + }); + _showErrorAlert('Network error. Please try again.'); + } + } + + void _showErrorAlert(String message) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('Error'), + content: Text(message), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('OK', style: TextStyle(color: Color(0xFF6A4C93))), + ), + ], + ), + ); + } + + Future _copyToClipboard(String text) async { + await Clipboard.setData(ClipboardData(text: text)); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Copied to clipboard'), + backgroundColor: Color(0xFF6A4C93), + duration: Duration(seconds: 2), + ), + ); + } + + String _formatDate(String dateString) { + try { + final date = DateTime.parse(dateString); + return '${date.day}/${date.month}/${date.year} at ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}'; + } catch (e) { + return dateString; + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + 'Create Invitation Code', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + backgroundColor: Color(0xFF6A4C93), + foregroundColor: Colors.white, + elevation: 0, + ), + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xFF6A4C93), Colors.white], + stops: [0.0, 0.3], + ), + ), + child: SafeArea( + child: Padding( + padding: EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Header + Container( + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 10, + offset: Offset(0, 5), + ), + ], + ), + child: Column( + children: [ + Icon( + Icons.confirmation_number_outlined, + size: 48, + color: Color(0xFF6A4C93), + ), + SizedBox(height: 16), + Text( + 'Generate New Invitation Code', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 8), + Text( + 'Create a new invitation code for user registration', + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 24), + + // Create button + Container( + width: double.infinity, + height: 56, + child: ElevatedButton( + onPressed: _isLoading ? null : _createInvitationCode, + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF6A4C93), + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 2, + ), + child: _isLoading + ? CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Colors.white), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.add, size: 20), + SizedBox(width: 8), + Text( + 'Create New Invitation Code', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ], + ), + ), + + SizedBox(height: 24), + + // Created invitation code display + if (_createdInvitation != null) ...[ + Container( + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 10, + offset: Offset(0, 5), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Icon( + Icons.check_circle, + color: Colors.green, + size: 24, + ), + SizedBox(width: 8), + Text( + 'Invitation Code Created', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.green, + ), + ), + ], + ), + + SizedBox(height: 20), + + // Invitation code + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Color(0xFF6A4C93), width: 2), + ), + child: Row( + children: [ + Expanded( + child: Text( + _createdInvitation!['code'] ?? 'N/A', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Color(0xFF6A4C93), + letterSpacing: 2.0, + ), + textAlign: TextAlign.center, + ), + ), + SizedBox(width: 12), + IconButton( + onPressed: () => _copyToClipboard(_createdInvitation!['code'] ?? ''), + icon: Icon( + Icons.copy, + color: Color(0xFF6A4C93), + ), + tooltip: 'Copy to clipboard', + ), + ], + ), + ), + + SizedBox(height: 16), + + // Details + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + _buildDetailRow( + 'ID:', + _createdInvitation!['id']?.toString() ?? 'N/A', + ), + SizedBox(height: 8), + _buildDetailRow( + 'Created:', + _formatDate(_createdInvitation!['createdDate'] ?? ''), + ), + SizedBox(height: 8), + _buildDetailRow( + 'Expires:', + _formatDate(_createdInvitation!['expirationDate'] ?? ''), + ), + SizedBox(height: 8), + _buildDetailRow( + 'Status:', + _createdInvitation!['expired'] == false ? 'Active' : 'Expired', + valueColor: _createdInvitation!['expired'] == false ? Colors.green : Colors.red, + ), + ], + ), + ), + + SizedBox(height: 20), + + // Copy full details button + Container( + width: double.infinity, + child: OutlinedButton.icon( + onPressed: () { + final details = ''' +Invitation Code: ${_createdInvitation!['code']} +ID: ${_createdInvitation!['id']} +Created: ${_formatDate(_createdInvitation!['createdDate'] ?? '')} +Expires: ${_formatDate(_createdInvitation!['expirationDate'] ?? '')} +Status: ${_createdInvitation!['expired'] == false ? 'Active' : 'Expired'} +'''; + _copyToClipboard(details); + }, + icon: Icon(Icons.copy_all), + label: Text('Copy All Details'), + style: OutlinedButton.styleFrom( + side: BorderSide(color: Color(0xFF6A4C93)), + foregroundColor: Color(0xFF6A4C93), + ), + ), + ), + ], + ), + ), + ], + ], + ), + ), + ), + ), + ); + } + + Widget _buildDetailRow(String label, String value, {Color? valueColor}) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + fontWeight: FontWeight.w500, + ), + ), + Text( + value, + style: TextStyle( + fontSize: 14, + color: valueColor ?? Colors.black87, + fontWeight: FontWeight.w600, + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/frontend/lib/screens/pages/profile_page.dart b/frontend/lib/screens/pages/profile_page.dart index 77f07be..1be4e0b 100644 --- a/frontend/lib/screens/pages/profile_page.dart +++ b/frontend/lib/screens/pages/profile_page.dart @@ -13,6 +13,7 @@ import '../edit_profile_screen.dart'; import '../support_screen.dart'; import '../information_screen.dart'; import '../notifications_settings_screen.dart'; +import '../create_invitation_screen.dart'; class ProfilePage extends StatefulWidget { @override @@ -752,9 +753,11 @@ class _SettingsPageState extends State { width: double.infinity, height: 56, child: ElevatedButton.icon( - onPressed: () => _showCreateAccountDialog(context), - icon: Icon(Icons.person_add, size: 20), - label: Text('Create an Account'), + onPressed: () => Navigator.of(context).push( + MaterialPageRoute(builder: (context) => CreateInvitationScreen()), + ), + icon: Icon(Icons.confirmation_number_outlined, size: 20), + label: Text('Create Invitation Code'), style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF6A4C93), foregroundColor: Colors.white,