diff --git a/Frontend/patient_manager/images/Logo-loading.gif b/Frontend/patient_manager/images/Logo-loading.gif new file mode 100644 index 00000000..65609221 Binary files /dev/null and b/Frontend/patient_manager/images/Logo-loading.gif differ diff --git a/Frontend/patient_manager/lib/Authentication/authCheck.dart b/Frontend/patient_manager/lib/Authentication/authCheck.dart index 76014df0..191ce15a 100644 --- a/Frontend/patient_manager/lib/Authentication/authCheck.dart +++ b/Frontend/patient_manager/lib/Authentication/authCheck.dart @@ -1,33 +1,47 @@ import 'package:flutter/material.dart'; import 'package:patient_manager/components/signInOrRegister.dart'; -import 'package:patient_manager/main.dart'; +//import 'package:patient_manager/main.dart'; import 'package:patient_manager/pages/home.dart'; -import 'package:supabase_flutter/supabase_flutter.dart'; +//import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:supertokens_flutter/supertokens.dart'; class AuthCheck extends StatelessWidget { const AuthCheck({super.key}); + Future doesSessionExist() async { + return await SuperTokens.doesSessionExist(); + } + @override Widget build(BuildContext context) { - return StreamBuilder( - stream: client.auth.onAuthStateChange.distinct(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.active) { - final user = snapshot.data?.session; - if (user == null) { - // User not authenticated, show login screen - return const SignInOrRegister(); - //Navigator.of(context).pushNamed('/signin'); - } else { - // User authenticated, show home screen + return FutureBuilder( + future: doesSessionExist(), + builder: (context, snapshot) { + if (snapshot.data == true) { return const Home(); - //Navigator.of(context).pushNamed('/homme'); + } else { + return const SignInOrRegister(); } - } + }); + // return StreamBuilder( + // stream: client.auth.onAuthStateChange.distinct(), + // builder: (context, snapshot) { + // if (snapshot.connectionState == ConnectionState.active) { + // final user = snapshot.data?.session; + // if (user == null) { + // // User not authenticated, show login screen + // return const SignInOrRegister(); + // //Navigator.of(context).pushNamed('/signin'); + // } else { + // // User authenticated, show home screen + // return const Home(); + // //Navigator.of(context).pushNamed('/homme'); + // } + // } - // Connection state not active, show loading indicator - return const CircularProgressIndicator(); - }, - ); + // // Connection state not active, show loading indicator + // return const CircularProgressIndicator(); + // }, + // ); } } diff --git a/Frontend/patient_manager/lib/components/homeAppDrawer.dart b/Frontend/patient_manager/lib/components/homeAppDrawer.dart index 7f2ed592..62fb0928 100644 --- a/Frontend/patient_manager/lib/components/homeAppDrawer.dart +++ b/Frontend/patient_manager/lib/components/homeAppDrawer.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:patient_manager/main.dart'; +import 'package:supertokens_flutter/supertokens.dart'; class HomeAppDrawer extends StatefulWidget { final String userEmail; @@ -17,6 +18,11 @@ class _HomeAppDrawerState extends State { super.initState(); } + Future signOut() async { + await SuperTokens.signOut(); + return true; + } + @override Widget build(BuildContext context) { //print(MzanziInnovationHub.of(context)?.theme.mode); @@ -55,9 +61,11 @@ class _HomeAppDrawerState extends State { ), ], ), - onTap: () { - client.auth.signOut(); - Navigator.of(context).pushNamed('/'); + onTap: () async { + //client.auth.signOut(); + if (await signOut()) { + Navigator.of(context).pushNamed('/'); + } }, ) ], diff --git a/Frontend/patient_manager/lib/components/homeTileGrid.dart b/Frontend/patient_manager/lib/components/homeTileGrid.dart index 1886cf65..aca18ada 100644 --- a/Frontend/patient_manager/lib/components/homeTileGrid.dart +++ b/Frontend/patient_manager/lib/components/homeTileGrid.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:patient_manager/components/homeTile.dart'; +import 'package:patient_manager/env/env.dart'; import 'package:patient_manager/main.dart'; class HomeTileGrid extends StatefulWidget { @@ -13,6 +14,42 @@ class HomeTileGrid extends StatefulWidget { class _HomeTileGridState extends State { late List> tileList; + List> setApps() { + if (AppEnviroment.getEnv() == "Dev") { + return [ + [ + Icons.medication, + "Patient Manager", + () { + // Navigator.of(context) + // .pushNamed('/patient-manager', arguments: widget.userEmail); + Navigator.popAndPushNamed(context, '/patient-manager', + arguments: widget.userEmail); + } + ], + [Icons.abc, "Test 1", () {}], + [Icons.abc, "Test 2", () {}], + [Icons.abc, "Test 3", () {}], + [Icons.abc, "Test 4", () {}], + [Icons.abc, "Test 5", () {}], + [Icons.abc, "Test 6", () {}], + ]; + } else { + return [ + [ + Icons.medication, + "Patient Manager", + () { + // Navigator.of(context) + // .pushNamed('/patient-manager', arguments: widget.userEmail); + Navigator.popAndPushNamed(context, '/patient-manager', + arguments: widget.userEmail); + } + ], + ]; + } + } + @override void initState() { //print("Home tile gird widget: ${widget.userEmail}"); diff --git a/Frontend/patient_manager/lib/components/myErrorMessage.dart b/Frontend/patient_manager/lib/components/myErrorMessage.dart index 4eb2d4b1..d2b4f59a 100644 --- a/Frontend/patient_manager/lib/components/myErrorMessage.dart +++ b/Frontend/patient_manager/lib/components/myErrorMessage.dart @@ -123,6 +123,206 @@ class _MyErrorMessageState extends State { ); } + void setUserExistsError() { + messageTypes["User Exists"] = Stack( + children: [ + Container( + padding: const EdgeInsets.all(10.0), + width: 500, + height: 450, + decoration: BoxDecoration( + color: MzanziInnovationHub.of(context)!.theme.primaryColor(), + borderRadius: BorderRadius.circular(25.0), + border: Border.all( + color: MzanziInnovationHub.of(context)!.theme.errorColor(), + width: 5.0), + ), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.warning_amber_rounded, + size: 100, + color: MzanziInnovationHub.of(context)!.theme.errorColor(), + ), + SizedBox(height: 15), + Text( + "Email Already Exists", + textAlign: TextAlign.center, + style: TextStyle( + color: MzanziInnovationHub.of(context)!.theme.errorColor(), + fontSize: 25.0, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + Padding( + padding: EdgeInsets.symmetric(horizontal: 25.0), + child: Text( + "An account is already registered with this email address. Please try logging in or use a different email.", + style: TextStyle( + color: MzanziInnovationHub.of(context)! + .theme + .secondaryColor(), + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 15), + Padding( + padding: EdgeInsets.symmetric(horizontal: 25.0), + child: Text( + "Here are some things to keep in mind:", + style: TextStyle( + color: MzanziInnovationHub.of(context)! + .theme + .secondaryColor(), + fontSize: 20.0, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 10), + Padding( + padding: EdgeInsets.symmetric(horizontal: 25.0), + child: Text( + "1) Are you sure you're using the correct email address associated with your account?\n2) Is your caps lock key on? Passwords are case-sensitive.\n3) If you've forgotten your password, no worries! Click on \"Forgot Password?\" to reset it.", + textAlign: TextAlign.left, + style: TextStyle( + color: MzanziInnovationHub.of(context)! + .theme + .secondaryColor(), + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ), + Positioned( + top: 5, + right: 5, + width: 50, + height: 50, + child: IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: Icon( + Icons.close, + color: MzanziInnovationHub.of(context)!.theme.errorColor(), + size: 35, + ), + ), + ), + ], + ); + } + + void setPwdMatchError() { + messageTypes["Password"] = Stack( + children: [ + Container( + padding: const EdgeInsets.all(10.0), + width: 500, + height: 450, + decoration: BoxDecoration( + color: MzanziInnovationHub.of(context)!.theme.primaryColor(), + borderRadius: BorderRadius.circular(25.0), + border: Border.all( + color: MzanziInnovationHub.of(context)!.theme.errorColor(), + width: 5.0), + ), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.warning_amber_rounded, + size: 100, + color: MzanziInnovationHub.of(context)!.theme.errorColor(), + ), + SizedBox(height: 15), + Text( + "Passwords Don't Match", + textAlign: TextAlign.center, + style: TextStyle( + color: MzanziInnovationHub.of(context)!.theme.errorColor(), + fontSize: 25.0, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + Padding( + padding: EdgeInsets.symmetric(horizontal: 25.0), + child: Text( + "The password and confirm password fields do not match. Please make sure they are identical.", + style: TextStyle( + color: MzanziInnovationHub.of(context)! + .theme + .secondaryColor(), + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 15), + Padding( + padding: EdgeInsets.symmetric(horizontal: 25.0), + child: Text( + "Here are some things to keep in mind:", + style: TextStyle( + color: MzanziInnovationHub.of(context)! + .theme + .secondaryColor(), + fontSize: 20.0, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 10), + Padding( + padding: EdgeInsets.symmetric(horizontal: 25.0), + child: Text( + "1) Are you sure you're using the correct email address associated with your account?\n2) Is your caps lock key on? Passwords are case-sensitive.\n3) If you've forgotten your password, no worries! Click on \"Forgot Password?\" to reset it.", + textAlign: TextAlign.left, + style: TextStyle( + color: MzanziInnovationHub.of(context)! + .theme + .secondaryColor(), + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ), + Positioned( + top: 5, + right: 5, + width: 50, + height: 50, + child: IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: Icon( + Icons.close, + color: MzanziInnovationHub.of(context)!.theme.errorColor(), + size: 35, + ), + ), + ), + ], + ); + } + void setinvalidCredError() { messageTypes["Invalid Credentials"] = Stack( children: [ @@ -347,6 +547,8 @@ class _MyErrorMessageState extends State { setInputError(); setinvalidCredError(); setInternetError(); + setUserExistsError(); + setPwdMatchError(); //print(size); // setState(() { // width = size.width; diff --git a/Frontend/patient_manager/lib/components/mySuccessMessage.dart b/Frontend/patient_manager/lib/components/mySuccessMessage.dart index 99179084..629e4602 100644 --- a/Frontend/patient_manager/lib/components/mySuccessMessage.dart +++ b/Frontend/patient_manager/lib/components/mySuccessMessage.dart @@ -54,13 +54,16 @@ class _MySuccessMessageState extends State { const SizedBox(height: 10), Padding( padding: const EdgeInsets.symmetric(horizontal: 25.0), - child: Text( - message, - style: TextStyle( - color: - MzanziInnovationHub.of(context)!.theme.secondaryColor(), - fontSize: 15.0, - fontWeight: FontWeight.bold, + child: Center( + child: Text( + message, + style: TextStyle( + color: MzanziInnovationHub.of(context)! + .theme + .secondaryColor(), + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), ), ), ), diff --git a/Frontend/patient_manager/lib/components/patManAppDrawer.dart b/Frontend/patient_manager/lib/components/patManAppDrawer.dart index 4e9901ce..3f1a9ab8 100644 --- a/Frontend/patient_manager/lib/components/patManAppDrawer.dart +++ b/Frontend/patient_manager/lib/components/patManAppDrawer.dart @@ -5,6 +5,7 @@ import 'package:patient_manager/env/env.dart'; import 'package:patient_manager/main.dart'; import 'package:http/http.dart' as http; import 'package:patient_manager/objects/appUser.dart'; +import 'package:supertokens_flutter/supertokens.dart'; class PatManAppDrawer extends StatefulWidget { final String userEmail; @@ -33,6 +34,11 @@ class _PatManAppDrawerState extends State { } } + Future signOut() async { + await SuperTokens.signOut(); + return true; + } + @override void initState() { signedInUser = getUserDetails(); @@ -203,10 +209,10 @@ class _PatManAppDrawerState extends State { ), ], ), - onTap: () { - client.auth.signOut(); - Navigator.popAndPushNamed(context, '/'); - //Navigator.of(context).pushNamed('/'); + onTap: () async { + if (await signOut()) { + Navigator.of(context).pushNamed('/'); + } }, ) ], diff --git a/Frontend/patient_manager/lib/components/patientFiles.dart b/Frontend/patient_manager/lib/components/patientFiles.dart index 1c520742..b80a57ad 100644 --- a/Frontend/patient_manager/lib/components/patientFiles.dart +++ b/Frontend/patient_manager/lib/components/patientFiles.dart @@ -16,6 +16,7 @@ import 'package:patient_manager/objects/appUser.dart'; import 'package:patient_manager/objects/files.dart'; import 'package:http/http.dart' as http; +import 'package:supertokens_flutter/supertokens.dart'; import '../objects/patients.dart'; @@ -56,6 +57,7 @@ class _PatientFilesState extends State { late String userEmail = ""; late AppUser appUser; late PlatformFile selected; + final baseAPI = AppEnviroment.baseApiUrl; Future generateMedCert() async { //start loading circle @@ -183,11 +185,12 @@ class _PatientFilesState extends State { } Future getUserEmail() async { - final res = await client.auth.getUser(); - if (res.user!.email != null) { - //print("emai not null"); - userEmail = res.user!.email!; - //print(userEmail); + // Add method to get user email + var uid = await SuperTokens.getUserId(); + var response = await http.get(Uri.parse("$baseAPI/user/$uid")); + if (response.statusCode == 200) { + var user = jsonDecode(response.body); + userEmail = user["email"]; } } diff --git a/Frontend/patient_manager/lib/main.dart b/Frontend/patient_manager/lib/main.dart index 11c9adf7..bc960b3f 100644 --- a/Frontend/patient_manager/lib/main.dart +++ b/Frontend/patient_manager/lib/main.dart @@ -2,9 +2,6 @@ import 'package:flutter/material.dart'; import 'package:patient_manager/env/env.dart'; import 'package:patient_manager/router/routeGenerator.dart'; import 'package:patient_manager/theme/mihTheme.dart'; -import 'package:supabase_flutter/supabase_flutter.dart'; - -final client = Supabase.instance.client; class MzanziInnovationHub extends StatefulWidget { const MzanziInnovationHub({ diff --git a/Frontend/patient_manager/lib/main_dev.dart b/Frontend/patient_manager/lib/main_dev.dart index fb8e7eaa..18ead771 100644 --- a/Frontend/patient_manager/lib/main_dev.dart +++ b/Frontend/patient_manager/lib/main_dev.dart @@ -1,16 +1,17 @@ import 'package:flutter/material.dart'; import 'package:patient_manager/env/env.dart'; import 'package:patient_manager/main.dart'; -import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:supertokens_flutter/supertokens.dart'; void main() async { AppEnviroment.setupEnv(Enviroment.dev); - WidgetsFlutterBinding.ensureInitialized(); - await Supabase.initialize( - url: "https://stzluvsyhbwtfbztarmu.supabase.co", - anonKey: - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InN0emx1dnN5aGJ3dGZienRhcm11Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTIwNzUyMTMsImV4cCI6MjAyNzY1MTIxM30.a7VHlk63JJcAotvsqtoqiKwjNK4EbnNgKilAqt1iRio", + SuperTokens.init( + apiDomain: AppEnviroment.baseApiUrl, + apiBasePath: "/auth", ); - //print(AppEnviroment.baseApiUrl); runApp(const MzanziInnovationHub()); } + +Future doesSessionExist() async { + return await SuperTokens.doesSessionExist(); +} diff --git a/Frontend/patient_manager/lib/main_prod.dart b/Frontend/patient_manager/lib/main_prod.dart index aa7c3cba..c7921006 100644 --- a/Frontend/patient_manager/lib/main_prod.dart +++ b/Frontend/patient_manager/lib/main_prod.dart @@ -1,16 +1,13 @@ import 'package:flutter/material.dart'; import 'package:patient_manager/env/env.dart'; import 'package:patient_manager/main.dart'; -import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:supertokens_flutter/supertokens.dart'; void main() async { AppEnviroment.setupEnv(Enviroment.prod); - WidgetsFlutterBinding.ensureInitialized(); - await Supabase.initialize( - url: "https://stzluvsyhbwtfbztarmu.supabase.co", - anonKey: - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InN0emx1dnN5aGJ3dGZienRhcm11Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTIwNzUyMTMsImV4cCI6MjAyNzY1MTIxM30.a7VHlk63JJcAotvsqtoqiKwjNK4EbnNgKilAqt1iRio", + SuperTokens.init( + apiDomain: AppEnviroment.baseApiUrl, + apiBasePath: "/auth", ); - //print(AppEnviroment.baseApiUrl); runApp(const MzanziInnovationHub()); } diff --git a/Frontend/patient_manager/lib/objects/appUser.dart b/Frontend/patient_manager/lib/objects/appUser.dart index 9b59aff1..26cafdaa 100644 --- a/Frontend/patient_manager/lib/objects/appUser.dart +++ b/Frontend/patient_manager/lib/objects/appUser.dart @@ -2,28 +2,31 @@ class AppUser { final int idusers; final String email; - final int docOffice_id; + final int docOffice_ID; final String fname; final String lname; final String title; + final String app_id; const AppUser( this.idusers, this.email, - this.docOffice_id, + this.docOffice_ID, this.fname, this.lname, this.title, + this.app_id, ); factory AppUser.fromJson(dynamic json) { return AppUser( json['idusers'], json['email'], - json['docOffice_id'], + json['docOffice_ID'], json['fname'], json['lname'], json['title'], + json['app_id'], ); } } diff --git a/Frontend/patient_manager/lib/objects/sessionST.dart b/Frontend/patient_manager/lib/objects/sessionST.dart new file mode 100644 index 00000000..f7157fad --- /dev/null +++ b/Frontend/patient_manager/lib/objects/sessionST.dart @@ -0,0 +1,23 @@ +class SessionST { + final String status; + final bool exists; + + const SessionST({ + required this.status, + required this.exists, + }); + + factory SessionST.fromJson(Map json) { + return switch (json) { + { + 'status': String status, + 'exists': bool exists, + } => + SessionST( + status: status, + exists: exists, + ), + _ => throw const FormatException('Failed to load SessionST.'), + }; + } +} diff --git a/Frontend/patient_manager/lib/pages/home.dart b/Frontend/patient_manager/lib/pages/home.dart index 1fd5011f..76717d7a 100644 --- a/Frontend/patient_manager/lib/pages/home.dart +++ b/Frontend/patient_manager/lib/pages/home.dart @@ -1,8 +1,12 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; -import 'package:patient_manager/main.dart'; +import 'package:patient_manager/env/env.dart'; import 'package:patient_manager/components/homeTileGrid.dart'; import 'package:patient_manager/components/myAppBar.dart'; import 'package:patient_manager/components/homeAppDrawer.dart'; +import 'package:supertokens_flutter/supertokens.dart'; +import 'package:supertokens_flutter/http.dart' as http; class Home extends StatefulWidget { const Home({ @@ -15,13 +19,14 @@ class Home extends StatefulWidget { class _HomeState extends State { String useremail = ""; + final baseAPI = AppEnviroment.baseApiUrl; Future getUserEmail() async { - final res = await client.auth.getUser(); - if (res.user!.email != null) { - //print("emai not null"); - useremail = res.user!.email!; - //print("Home Page: $useremail"); + var uid = await SuperTokens.getUserId(); + var response = await http.get(Uri.parse("$baseAPI/user/$uid")); + if (response.statusCode == 200) { + var user = jsonDecode(response.body); + useremail = user["email"]; } } diff --git a/Frontend/patient_manager/lib/pages/patientAdd.dart b/Frontend/patient_manager/lib/pages/patientAdd.dart index a42f2bb1..a64508a6 100644 --- a/Frontend/patient_manager/lib/pages/patientAdd.dart +++ b/Frontend/patient_manager/lib/pages/patientAdd.dart @@ -50,7 +50,7 @@ class _AddPatientState extends State { var decodedData = jsonDecode(body); AppUser u = AppUser.fromJson(decodedData as Map); setState(() { - futureDocOfficeId = u.docOffice_id; + futureDocOfficeId = u.docOffice_ID; //print(futureDocOfficeId); }); } else { diff --git a/Frontend/patient_manager/lib/pages/patientEdit.dart b/Frontend/patient_manager/lib/pages/patientEdit.dart index eb744586..494cf77f 100644 --- a/Frontend/patient_manager/lib/pages/patientEdit.dart +++ b/Frontend/patient_manager/lib/pages/patientEdit.dart @@ -57,7 +57,7 @@ class _EditPatientState extends State { var decodedData = jsonDecode(body); AppUser u = AppUser.fromJson(decodedData as Map); setState(() { - futureDocOfficeId = u.docOffice_id; + futureDocOfficeId = u.docOffice_ID; //print(futureDocOfficeId); }); } else { diff --git a/Frontend/patient_manager/lib/pages/patientManager.dart b/Frontend/patient_manager/lib/pages/patientManager.dart index 0c9eac4c..a99f818b 100644 --- a/Frontend/patient_manager/lib/pages/patientManager.dart +++ b/Frontend/patient_manager/lib/pages/patientManager.dart @@ -240,27 +240,24 @@ class _PatientManagerState extends State { return Scaffold( appBar: const MyAppBar(barTitle: "Patient Manager"), drawer: PatManAppDrawer(userEmail: widget.userEmail), - floatingActionButtonLocation: FloatingActionButtonLocation.endTop, - floatingActionButton: Padding( - padding: const EdgeInsets.only(top: 65, right: 5), - child: FloatingActionButton.extended( - label: Text( - "Add Patient", - style: TextStyle( - fontWeight: FontWeight.bold, - color: MzanziInnovationHub.of(context)!.theme.primaryColor(), - ), - ), - //backgroundColor: Colors.blueAccent, - onPressed: () { - Navigator.of(context) - .pushNamed('/patient-manager/add', arguments: widget.userEmail); - }, - icon: Icon( - Icons.add, + //floatingActionButtonLocation: FloatingActionButtonLocation., + floatingActionButton: FloatingActionButton.extended( + label: Text( + "Add Patient", + style: TextStyle( + fontWeight: FontWeight.bold, color: MzanziInnovationHub.of(context)!.theme.primaryColor(), ), ), + //backgroundColor: Colors.blueAccent, + onPressed: () { + Navigator.of(context) + .pushNamed('/patient-manager/add', arguments: widget.userEmail); + }, + icon: Icon( + Icons.add, + color: MzanziInnovationHub.of(context)!.theme.primaryColor(), + ), ), body: patientSearch(screenWidth, screenHeight), ); diff --git a/Frontend/patient_manager/lib/pages/register.dart b/Frontend/patient_manager/lib/pages/register.dart index 863c272f..4f4f5371 100644 --- a/Frontend/patient_manager/lib/pages/register.dart +++ b/Frontend/patient_manager/lib/pages/register.dart @@ -1,9 +1,19 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:patient_manager/components/myErrorMessage.dart'; import 'package:patient_manager/components/myPassInput.dart'; +import 'package:patient_manager/components/mySuccessMessage.dart'; import 'package:patient_manager/components/myTextInput.dart'; import 'package:patient_manager/components/mybutton.dart'; +import 'package:patient_manager/env/env.dart'; import 'package:patient_manager/main.dart'; +//import 'package:patient_manager/objects/sessionST.dart'; import 'package:supabase_auth_ui/supabase_auth_ui.dart'; +//import 'package:supertokens_flutter/supertokens.dart'; +import 'package:supertokens_flutter/http.dart' as http; +import 'package:supertokens_flutter/supertokens.dart'; class Register extends StatefulWidget { final Function()? onTap; @@ -18,20 +28,125 @@ class _RegisterState extends State { final passwordController = TextEditingController(); final confirmPasswordController = TextEditingController(); final officeID = TextEditingController(); - + final baseAPI = AppEnviroment.baseApiUrl; + final FocusNode _focusNode = FocusNode(); bool _obscureText = true; + + bool successfulSignUp = false; + + Future addUserAPICall(String email, String uid) async { + //await getOfficeIdByUser(docOfficeIdApiUrl + widget.userEmail); + //print(futureDocOfficeId.toString()); + var response = await http.post( + Uri.parse("$baseAPI/user/insert/"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + body: jsonEncode({ + "email": email, + "app_id": uid, + }), + ); + if (response.statusCode == 201) { + Navigator.of(context).pushNamed('/home'); + signUpSuccess(); + setState(() { + successfulSignUp = true; + }); + } else { + internetConnectionPopUp(); + } + } + + void internetConnectionPopUp() { + showDialog( + context: context, + builder: (context) { + return const MyErrorMessage(errorType: "Internet Connection"); + }, + ); + } + //sign user in Future signUserUp() async { if (passwordController.text != confirmPasswordController.text) { - loginError("Passwords do not match"); + passwordError(); } else { + var _backgroundColor = Colors.transparent; + + showDialog( + context: context, + barrierColor: _backgroundColor, + builder: (BuildContext dialogContext) { + return AlertDialog( + backgroundColor: _backgroundColor, + content: Container( + child: Center( + child: MzanziInnovationHub.of(context)! + .theme + .loadingImage(), // Put your gif into the assets folder + ), + ), + ); + }, + ); try { - final response = await client.auth.signUp( - email: emailController.text, - password: passwordController.text, - ); - if (response.session != null) { - Navigator.of(context).pushNamed('/homme'); + Uri uri = Uri.parse( + "$baseAPI/auth/emailpassword/email/exists?email=${emailController.text}"); + //print("Here"); + var response = await http.get(uri); + //print(response.body); + //print("response 1: ${response.statusCode}"); + if (response.statusCode == 200) { + var userExists = jsonDecode(response.body); + if (userExists["exists"]) { + signUpError(); + } else { + var response2 = await http.post( + Uri.parse("$baseAPI/auth/signup"), + body: + '{"formFields": [{"id": "email","value": "${emailController.text}"}, {"id": "password","value": "${passwordController.text}"}]}', + headers: { + 'Content-type': 'application/json', + 'Accept': 'application/json', + "Authorization": "leatucczyixqwkqqdrhayiwzeofkltds" + }, + ); + //print("response 2: ${response2.statusCode}"); + if (response2.statusCode == 200) { + //print(response2.body); + var userCreated = jsonDecode(response2.body); + //print(userCreated); + if (userCreated["status"] == "OK") { + //print("Here"); + //Creat user in db + var response2 = await http.post( + Uri.parse("$baseAPI/auth/signup"), + body: + '{"formFields": [{"id": "email","value": "${emailController.text}"}, {"id": "password","value": "${passwordController.text}"}]}', + headers: { + 'Content-type': 'application/json', + 'Accept': 'application/json', + "Authorization": "leatucczyixqwkqqdrhayiwzeofkltds" + }, + ); + String uid = await SuperTokens.getUserId(); + if (response2.statusCode == 200) { + addUserAPICall(emailController.text, uid); + } else { + internetConnectionPopUp(); + } + } + } + } + Navigator.of(context).pop(); + // final response = await client.auth.signUp( + // email: emailController.text, + // password: passwordController.text, + // ); + // if (response.session != null) { + // Navigator.of(context).pushNamed('/homme'); + // } } } on AuthException catch (error) { loginError(error.message); @@ -42,6 +157,36 @@ class _RegisterState extends State { } } + void signUpSuccess() { + showDialog( + context: context, + builder: (context) { + return const MySuccessMessage( + successType: "Success", + successMessage: + "Congratulations! Your account has been created successfully. You are log in and start exploring."); + }, + ); + } + + void signUpError() { + showDialog( + context: context, + builder: (context) { + return const MyErrorMessage(errorType: "User Exists"); + }, + ); + } + + void passwordError() { + showDialog( + context: context, + builder: (context) { + return const MyErrorMessage(errorType: "Password"); + }, + ); + } + void loginError(error) { showDialog( context: context, @@ -61,126 +206,154 @@ class _RegisterState extends State { @override Widget build(BuildContext context) { - return Scaffold( - //backgroundColor: Colors.white, - body: SafeArea( - child: Center( - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - //logo - Icon( - Icons.lock, - size: 100, - color: - MzanziInnovationHub.of(context)!.theme.secondaryColor(), - ), - //spacer - const SizedBox(height: 10), - //Heading - Text( - 'Register', - style: TextStyle( - fontSize: 25, - fontWeight: FontWeight.bold, + return KeyboardListener( + focusNode: _focusNode, + autofocus: true, + onKeyEvent: (event) async { + if (event is KeyDownEvent && + event.logicalKey == LogicalKeyboardKey.enter) { + if (emailController.text.isEmpty || passwordController.text.isEmpty) { + showDialog( + context: context, + builder: (context) { + return const MyErrorMessage(errorType: "Input Error"); + }, + ); + } else { + await signUserUp(); + if (successfulSignUp) { + Navigator.of(context).pushNamed('/home'); + } + } + } + }, + child: Scaffold( + //backgroundColor: Colors.white, + body: SafeArea( + child: Center( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + //logo + Icon( + Icons.lock, + size: 100, color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), ), - ), - //spacer - const SizedBox(height: 25), - //email input - SizedBox( - width: 500.0, - child: MyTextField( - controller: officeID, - hintText: 'OfficeID', - editable: true, - required: true, + //spacer + const SizedBox(height: 10), + //Heading + Text( + 'Register', + style: TextStyle( + fontSize: 25, + fontWeight: FontWeight.bold, + color: MzanziInnovationHub.of(context)! + .theme + .secondaryColor(), + ), ), - ), - //spacer - const SizedBox(height: 25), - //email input - SizedBox( - width: 500.0, - child: MyTextField( - controller: emailController, - hintText: 'Email', - editable: true, - required: true, + //spacer + const SizedBox(height: 25), + //email input + SizedBox( + width: 500.0, + child: MyTextField( + controller: emailController, + hintText: 'Email', + editable: true, + required: true, + ), ), - ), - //spacer - const SizedBox(height: 25), - //password input - SizedBox( - width: 500.0, - child: MyPassField( - controller: passwordController, - hintText: 'Password', - required: true, + //spacer + const SizedBox(height: 25), + //password input + SizedBox( + width: 500.0, + child: MyPassField( + controller: passwordController, + hintText: 'Password', + required: true, + ), ), - ), - //spacer - const SizedBox(height: 25), - //password input - SizedBox( - width: 500.0, - child: MyPassField( - controller: confirmPasswordController, - hintText: 'Confirm Password', - required: true, + //spacer + const SizedBox(height: 25), + //password input + SizedBox( + width: 500.0, + child: MyPassField( + controller: confirmPasswordController, + hintText: 'Confirm Password', + required: true, + ), ), - ), - //spacer - const SizedBox(height: 10), - // sign up button - SizedBox( - width: 500.0, - height: 100.0, - child: MyButton( - onTap: () {}, - buttonText: "Sign Up", - buttonColor: - MzanziInnovationHub.of(context)!.theme.secondaryColor(), - textColor: - MzanziInnovationHub.of(context)!.theme.primaryColor(), + //spacer + const SizedBox(height: 10), + // sign up button + SizedBox( + width: 500.0, + height: 100.0, + child: MyButton( + buttonText: "Sign Up", + buttonColor: MzanziInnovationHub.of(context)! + .theme + .secondaryColor(), + textColor: + MzanziInnovationHub.of(context)!.theme.primaryColor(), + onTap: () async { + if (emailController.text.isEmpty || + passwordController.text.isEmpty) { + showDialog( + context: context, + builder: (context) { + return const MyErrorMessage( + errorType: "Input Error"); + }, + ); + } else { + await signUserUp(); + if (successfulSignUp) { + Navigator.of(context).pushNamed('/homme'); + } + } + }, + ), ), - ), - //register text - SizedBox( - width: 450.0, - height: 100.0, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - const Text( - 'Already a User?', - style: TextStyle(fontSize: 18, color: Colors.grey), - ), - const SizedBox( - width: 6, - ), - GestureDetector( - onTap: widget.onTap, - child: Text( - 'Sign In', - style: TextStyle( - fontSize: 18, - color: MzanziInnovationHub.of(context)! - .theme - .secondaryColor(), - fontWeight: FontWeight.bold, - ), + //register text + SizedBox( + width: 450.0, + height: 100.0, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + const Text( + 'Already a User?', + style: TextStyle(fontSize: 18, color: Colors.grey), ), - ) - ], - ), - ) - ], + const SizedBox( + width: 6, + ), + GestureDetector( + onTap: widget.onTap, + child: Text( + 'Sign In', + style: TextStyle( + fontSize: 18, + color: MzanziInnovationHub.of(context)! + .theme + .secondaryColor(), + fontWeight: FontWeight.bold, + ), + ), + ) + ], + ), + ) + ], + ), ), ), ), diff --git a/Frontend/patient_manager/lib/pages/signin.dart b/Frontend/patient_manager/lib/pages/signin.dart index 49209429..a11ea17c 100644 --- a/Frontend/patient_manager/lib/pages/signin.dart +++ b/Frontend/patient_manager/lib/pages/signin.dart @@ -1,11 +1,15 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:patient_manager/components/myErrorMessage.dart'; import 'package:patient_manager/components/myPassInput.dart'; import 'package:patient_manager/components/myTextInput.dart'; import 'package:patient_manager/components/mybutton.dart'; +import 'package:patient_manager/env/env.dart'; import 'package:patient_manager/main.dart'; import 'package:supabase_auth_ui/supabase_auth_ui.dart'; +import 'package:supertokens_flutter/http.dart' as http; class SignIn extends StatefulWidget { final Function()? onTap; @@ -23,24 +27,54 @@ class _SignInState extends State { // focus node to capture keyboard events final FocusNode _focusNode = FocusNode(); + final baseAPI = AppEnviroment.baseApiUrl; + //sign user in Future signUserIn() async { + var _backgroundColor = Colors.transparent; + + showDialog( + context: context, + barrierColor: _backgroundColor, + builder: (BuildContext dialogContext) { + return AlertDialog( + backgroundColor: _backgroundColor, + content: Container( + child: Center( + child: MzanziInnovationHub.of(context)! + .theme + .loadingImage(), // Put your gif into the assets folder + ), + ), + ); + }, + ); try { - final response = await client.auth.signInWithPassword( - email: emailController.text, - password: passwordController.text, + var response = await http.post( + Uri.parse("$baseAPI/auth/signin"), + body: + '{"formFields": [{"id": "email","value": "${emailController.text}"}, {"id": "password","value": "${passwordController.text}"}]}', + headers: { + 'Content-type': 'application/json', + 'Accept': 'application/json', + "Authorization": "leatucczyixqwkqqdrhayiwzeofkltds" + }, ); - if (response.session != null) { - setState(() { - successfulSignIn = true; - }); - //Navigator.of(context).pushNamed('/homme'); + if (response.statusCode == 200) { + var userSignedin = jsonDecode(response.body); + if (userSignedin["status"] == "OK") { + setState(() { + successfulSignIn = true; + }); + } else { + loginError(); + } } } on AuthException { loginError(); - //emailController.clear(); passwordController.clear(); } + Navigator.of(context).pop(); } void loginError() { @@ -57,7 +91,7 @@ class _SignInState extends State { return KeyboardListener( focusNode: _focusNode, autofocus: true, - onKeyEvent: (event) { + onKeyEvent: (event) async { if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.enter) { if (emailController.text.isEmpty || passwordController.text.isEmpty) { @@ -68,11 +102,10 @@ class _SignInState extends State { }, ); } else { - signUserIn(); - } - - if (successfulSignIn) { - Navigator.of(context).pushNamed('/homme'); + await signUserIn(); + if (successfulSignIn) { + Navigator.of(context).pushNamed('/home'); + } } } }, @@ -141,7 +174,7 @@ class _SignInState extends State { .secondaryColor(), textColor: MzanziInnovationHub.of(context)!.theme.primaryColor(), - onTap: () { + onTap: () async { if (emailController.text.isEmpty || passwordController.text.isEmpty) { showDialog( @@ -152,11 +185,11 @@ class _SignInState extends State { }, ); } else { - signUserIn(); - } - - if (successfulSignIn) { - Navigator.of(context).pushNamed('/homme'); + await signUserIn(); + //print(successfulSignIn); + if (successfulSignIn) { + Navigator.of(context).pushNamed('/home'); + } } }, ), diff --git a/Frontend/patient_manager/lib/theme/mihTheme.dart b/Frontend/patient_manager/lib/theme/mihTheme.dart index a6d75424..72c8132c 100644 --- a/Frontend/patient_manager/lib/theme/mihTheme.dart +++ b/Frontend/patient_manager/lib/theme/mihTheme.dart @@ -7,6 +7,7 @@ class MyTheme { late int _succColor; late int _mesColor; late String mode; + late Image loading; // Options:- // f3f9d2 = Cream @@ -101,6 +102,21 @@ class MyTheme { return Color(_succColor); } + Image loadingImage() { + if (mode == "Dark") { + loading = Image.asset( + 'images/Logo-loading.gif', + width: 100, + ); + } else { + loading = Image.asset( + 'images/Logo-loading.gif', + width: 100, + ); + } + return loading; + } + Color primaryColor() { if (mode == "Dark") { _mainColor = 0XFF3A4454; diff --git a/Frontend/patient_manager/pubspec.lock b/Frontend/patient_manager/pubspec.lock index 3a3723f2..30b742ca 100644 --- a/Frontend/patient_manager/pubspec.lock +++ b/Frontend/patient_manager/pubspec.lock @@ -289,6 +289,22 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + dio: + dependency: transitive + description: + name: dio + sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714 + url: "https://pub.dev" + source: hosted + version: "5.5.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac" + url: "https://pub.dev" + source: hosted + version: "1.0.1" email_validator: dependency: transitive description: @@ -576,6 +592,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + mutex: + dependency: transitive + description: + name: mutex + sha256: "8827da25de792088eb33e572115a5eb0d61d61a3c01acbc8bcbe76ed78f1a1f2" + url: "https://pub.dev" + source: hosted + version: "3.1.0" mysql_client: dependency: "direct main" description: @@ -909,6 +933,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.5.6" + supertokens_flutter: + dependency: "direct main" + description: + name: supertokens_flutter + sha256: "147ad25ea5e001ce0edbcdf850c48f62222e936b2f5ce0902ef60e04c9d0605b" + url: "https://pub.dev" + source: hosted + version: "0.6.0" syncfusion_flutter_core: dependency: transitive description: diff --git a/Frontend/patient_manager/pubspec.yaml b/Frontend/patient_manager/pubspec.yaml index 9972bd89..38a9dd97 100644 --- a/Frontend/patient_manager/pubspec.yaml +++ b/Frontend/patient_manager/pubspec.yaml @@ -38,6 +38,7 @@ dependencies: syncfusion_flutter_pdfviewer: ^26.1.39 universal_html: ^2.2.4 file_picker: ^8.0.5 + supertokens_flutter: ^0.6.0 supabase_auth_ui: ^0.4.1 supabase_flutter: ^2.4.0 http: ^1.2.1 @@ -71,9 +72,9 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - images/Logo-loading.gif + # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware diff --git a/backend/main.py b/backend/main.py index fd71522c..ed550a1e 100644 --- a/backend/main.py +++ b/backend/main.py @@ -9,6 +9,14 @@ from supertokens_python.recipe.session.framework.fastapi import verify_session from supertokens_python.recipe.session import SessionContainer from fastapi import Depends +from supertokens_python.recipe.session.framework.fastapi import verify_session +from supertokens_python.recipe.thirdparty.asyncio import ( + get_user_by_id as get_user_by_id_thirdparty, +) +from supertokens_python.recipe.passwordless.asyncio import ( + get_user_by_id as get_user_by_id_passwordless, +) + origins = [ "http://localhost", "http://localhost:80", @@ -60,6 +68,18 @@ def read_root(): return {"Session id": user_id} +@app.post('/get_user_info_api') +async def get_user_info_api(session: SessionContainer = Depends(verify_session())): + user_id = session.get_user_id() + + thirdparty_user = await get_user_by_id_thirdparty(user_id) + if thirdparty_user is None: + passwordless_user = await get_user_by_id_passwordless(user_id) + if passwordless_user is not None: + print(passwordless_user) + else: + print(thirdparty_user) + def serverRunning(): return {"Status": "Server is Up and Running"} diff --git a/backend/routers/users.py b/backend/routers/users.py index edc263e2..158e4567 100644 --- a/backend/routers/users.py +++ b/backend/routers/users.py @@ -7,6 +7,10 @@ router = APIRouter() class userRequest(BaseModel): email: str DocOfficeID: int + +class userInsertRequest(BaseModel): + email: str + app_id: str #get user by email & doc Office ID @router.get("/users/profile/{email}", tags="users") @@ -50,4 +54,47 @@ async def read_all_users(): ] cursor.close() db.close() - return items \ No newline at end of file + return items + +# Get List of all files +@router.get("/user/{uid}", tags="users") +async def read_all_users(uid: str): + db = dbConnection.dbConnect() + cursor = db.cursor() + query = "SELECT * FROM users where app_id = %s" + cursor.execute(query, (uid,)) + items = [ + { + "idUser": item[0], + "email": item[1], + "docOffice_ID": item[2], + "fname": item[3], + "lname": item[4], + "title": item[5], + "app_id": item[6], + } + for item in cursor.fetchall() + ] + cursor.close() + db.close() + return items[0] + +# Insert Patient into table +@router.post("/user/insert/", tags="user", status_code=201) +async def insertPatient(itemRequest : userInsertRequest): + db = dbConnection.dbConnect() + cursor = db.cursor() + query = "insert into users " + query += "(email, app_id) " + query += "values (%s, %s)" + userData = (itemRequest.email, + itemRequest.app_id) + try: + cursor.execute(query, userData) + except Exception as error: + raise HTTPException(status_code=404, detail="Failed to Create Record") + #return {"message": "Failed to Create Record"} + db.commit() + cursor.close() + db.close() + return {"message": "Successfully Created Record"} \ No newline at end of file