first commit

This commit is contained in:
2026-02-07 15:57:09 +07:00
commit 157096f164
1153 changed files with 415766 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:http/http.dart' as http;
import '../../../../Const/api_config.dart';
import '../../../../Repository/constant_functions.dart';
import '../../../../currency.dart';
class SignUpRepo {
Future<dynamic> signUp({required String name, required String email, required String password, required BuildContext context}) async {
final url = Uri.parse('${APIConfig.url}/sign-up');
final body = {
'name': name,
'email': email,
'password': password,
};
final headers = {
'Accept': 'application/json',
};
try {
final response = await http.post(url, headers: headers, body: body);
final responseData = jsonDecode(response.body);
EasyLoading.dismiss();
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
final token = responseData['token'];
if (token != null) {
await saveUserData(token: token);
return responseData['token'];
}
return true;
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
}
} catch (error) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Network error: Please try again')));
} finally {}
return false;
}
Future<bool> verifyOTP({required String email, required String otp, required BuildContext context}) async {
final url = Uri.parse('${APIConfig.url}/submit-otp');
final body = {
'email': email,
'otp': otp,
};
final headers = {
'Accept': 'application/json',
};
try {
final response = await http.post(url, headers: headers, body: body);
final responseData = jsonDecode(response.body);
EasyLoading.dismiss();
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
String? token = responseData['token'];
if (responseData['currency'] != null) {
await CurrencyMethods()
.saveCurrencyDataInLocalDatabase(selectedCurrencySymbol: responseData['currency']['symbol'], selectedCurrencyName: responseData['currency']['name']);
}
if (token != null) {
await saveUserData(token: responseData['token']);
}
return true;
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['error'])));
}
} catch (error) {
print('Error: $error');
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error: $error')));
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Server error: Please try again')));
}
return false;
}
Future<bool> resendOTP({required String email, required BuildContext context}) async {
final url = Uri.parse('${APIConfig.url}/resend-otp');
final body = {
'email': email,
};
final headers = {
'Accept': 'application/json',
};
try {
final response = await http.post(url, headers: headers, body: body);
final responseData = jsonDecode(response.body);
EasyLoading.dismiss();
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
return true;
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['error'])));
}
} catch (error) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Network error: Please try again')));
} finally {}
return false;
}
}

View File

@@ -0,0 +1,260 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:mobile_pos/Screens/Authentication/Sign%20Up/repo/sign_up_repo.dart';
import 'package:mobile_pos/Screens/Authentication/Sign%20Up/verify_email.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import '../../../GlobalComponents/button_global.dart';
import '../../../GlobalComponents/glonal_popup.dart';
import '../../../constant.dart';
import '../Wedgets/check_email_for_otp_popup.dart';
import '../profile_setup_screen.dart';
class SignUpScreen extends StatefulWidget {
const SignUpScreen({Key? key}) : super(key: key);
@override
State<SignUpScreen> createState() => _SignUpScreenState();
}
class _SignUpScreenState extends State<SignUpScreen> {
///__________Variables________________________________
bool showPassword = true;
bool isClicked = false;
///________Key_______________________________________
GlobalKey<FormState> key = GlobalKey<FormState>();
///___________Controllers______________________________
TextEditingController nameTextController = TextEditingController();
TextEditingController passwordTextController = TextEditingController();
TextEditingController emailTextController = TextEditingController();
///________Dispose____________________________________
@override
void dispose() {
super.dispose();
nameTextController.dispose();
passwordTextController.dispose();
emailTextController.dispose();
}
@override
Widget build(BuildContext context) {
TextTheme textTheme = Theme.of(context).textTheme;
final _theme = Theme.of(context);
return GlobalPopup(
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: kWhite,
titleSpacing: 16,
centerTitle: true,
surfaceTintColor: kWhite,
title: Text(
lang.S.of(context).signUp,
//'Sign Up',
),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 20.0, 16.0, 0.0),
child: Form(
key: key,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(
height: 24,
),
const NameWithLogo(),
const SizedBox(
height: 24,
),
Text(
lang.S.of(context).createAFreeAccount,
//'Create A Free Account',
style: textTheme.titleMedium?.copyWith(fontSize: 24.0, fontWeight: FontWeight.w600),
),
Text(
lang.S.of(context).pleaseEnterYourDetails,
//'Please enter your details',
style: textTheme.bodyMedium?.copyWith(color: kGreyTextColor, fontSize: 16),
),
const SizedBox(height: 24.0),
///____________Name______________________________________________
TextFormField(
controller: nameTextController,
keyboardType: TextInputType.name,
decoration: InputDecoration(
//labelText: 'Full Name',
labelText: lang.S.of(context).fullName,
//hintText: 'Enter your full name',
hintText: lang.S.of(context).enterYourFullName,
),
validator: (value) {
if (value == null || value.isEmpty) {
//return 'name can\'n be empty';
return lang.S.of(context).nameCanNotBeEmpty;
}
return null;
},
),
const SizedBox(height: 20.0),
///__________Email______________________________________________
TextFormField(
controller: emailTextController,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
// border: OutlineInputBorder(),
// labelText: 'email',
labelText: lang.S.of(context).lableEmail,
//hintText: 'Enter email address',
hintText: lang.S.of(context).hintEmail,
),
validator: (value) {
if (value == null || value.isEmpty) {
//return 'Email can\'n be empty';
return lang.S.of(context).emailCannotBeEmpty;
} else if (!value.contains('@')) {
//return 'Please enter a valid email';
return lang.S.of(context).pleaseEnterAValidEmail;
}
return null;
},
),
const SizedBox(height: 20.0),
///___________Password_____________________________________________
TextFormField(
controller: passwordTextController,
keyboardType: TextInputType.text,
obscureText: showPassword,
decoration: InputDecoration(
//labelText: 'Password',
labelText: lang.S.of(context).lablePassword,
// hintText: 'Enter password',
hintText: lang.S.of(context).hintPassword,
suffixIcon: IconButton(
onPressed: () {
setState(() {
showPassword = !showPassword;
});
},
icon: Icon(
showPassword ? FeatherIcons.eyeOff : FeatherIcons.eye,
color: kGreyTextColor,
size: 18,
),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
//return 'Password can\'t be empty';
return lang.S.of(context).passwordCannotBeEmpty;
} else if (value.length < 6) {
//return 'Please enter a bigger password';
return lang.S.of(context).pleaseEnterABiggerPassword;
}
return null;
},
),
const SizedBox(height: 24.0),
///________Button___________________________________________________
ElevatedButton(
style: OutlinedButton.styleFrom(
maximumSize: const Size(double.infinity, 48),
minimumSize: const Size(double.infinity, 48),
disabledBackgroundColor: _theme.colorScheme.primary.withValues(alpha: 0.15),
),
onPressed: () async {
if (isClicked) {
return;
}
if (key.currentState?.validate() ?? false) {
isClicked = true;
EasyLoading.show();
SignUpRepo repo = SignUpRepo();
final result = await repo.signUp(name: nameTextController.text, email: emailTextController.text, password: passwordTextController.text, context: context);
if (result is bool && result) {
if (result) {
if (await checkEmailForCodePupUp(email: emailTextController.text, context: context, textTheme: textTheme)) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => VerifyEmail(
email: emailTextController.text,
isFormForgotPass: false,
),
),
);
}
} else {
isClicked = false;
}
} else if (result is String) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProfileSetup(),
),
);
} else {
isClicked = false;
}
}
},
child: Text(
lang.S.of(context).signUp,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: _theme.textTheme.bodyMedium?.copyWith(
color: _theme.colorScheme.primaryContainer,
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
),
const SizedBox(
height: 20,
),
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
highlightColor: kMainColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(3.0),
onTap: () => Navigator.pop(context),
hoverColor: kMainColor.withOpacity(0.1),
child: RichText(
text: TextSpan(
text: lang.S.of(context).alreadyHaveAnAccount,
//'Already have an account? ',
style: textTheme.bodyMedium?.copyWith(color: kGreyTextColor),
children: [
TextSpan(
text: lang.S.of(context).signIn,
//'Sign In',
style: textTheme.bodyMedium?.copyWith(color: kMainColor, fontWeight: FontWeight.bold),
)
],
),
),
),
],
)
],
),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,251 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:mobile_pos/Screens/Authentication/Sign%20Up/repo/sign_up_repo.dart';
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import 'package:pinput/pinput.dart' as p;
import '../../../GlobalComponents/glonal_popup.dart';
import '../Repo/otp_settings_repo.dart';
import '../forgot password/repo/forgot_pass_repo.dart';
import '../forgot password/set_new_password.dart';
import '../profile_setup_screen.dart';
class VerifyEmail extends StatefulWidget {
const VerifyEmail({super.key, required this.email, required this.isFormForgotPass});
final String email;
final bool isFormForgotPass;
@override
State<VerifyEmail> createState() => _VerifyEmailNewState();
}
class _VerifyEmailNewState extends State<VerifyEmail> {
bool isClicked = false;
Timer? _timer;
int _start = 180; // default fallback
bool _isButtonEnabled = false;
final pinController = TextEditingController();
final focusNode = FocusNode();
final _pinputKey = GlobalKey<FormState>();
@override
void initState() {
super.initState();
_loadOtpSettings();
}
Future<void> _loadOtpSettings() async {
EasyLoading.show(status: lang.S.of(context).loadingOtpSetting);
final settings = await OtpSettingsRepo().fetchOtpSettings();
print(settings?.otpExpirationTime);
EasyLoading.dismiss();
if (settings != null) {
int durationInSec = int.parse(settings.otpExpirationTime);
if (settings.otpDurationType.toLowerCase().contains("minute")) {
durationInSec *= 60;
} else if (settings.otpDurationType.toLowerCase().contains("hour")) {
durationInSec *= 3600;
}
setState(() {
_start = durationInSec;
});
}
startTimer();
}
void startTimer() {
_isButtonEnabled = false;
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
setState(() {
if (_start > 0) {
_start--;
} else {
_isButtonEnabled = true;
_timer?.cancel();
}
});
});
}
@override
void dispose() {
pinController.dispose();
focusNode.dispose();
_timer?.cancel();
super.dispose();
}
static const focusedBorderColor = kMainColor;
static const fillColor = Color(0xFFF3F3F3);
final defaultPinTheme = p.PinTheme(
width: 45,
height: 52,
textStyle: const TextStyle(
fontSize: 20,
color: kTitleColor,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: kBorderColor),
),
);
@override
Widget build(BuildContext context) {
TextTheme textTheme = Theme.of(context).textTheme;
return GlobalPopup(
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: kWhite,
surfaceTintColor: kWhite,
centerTitle: true,
titleSpacing: 16,
title: Text(lang.S.of(context).verityEmail),
),
body: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 20.0, 16.0, 0.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
lang.S.of(context).verityEmail,
style: textTheme.titleMedium?.copyWith(fontSize: 24.0),
),
const SizedBox(height: 8.0),
RichText(
textAlign: TextAlign.center,
text: TextSpan(
text: lang.S.of(context).digits,
style: textTheme.bodyMedium?.copyWith(color: kGreyTextColor, fontSize: 16),
children: [
TextSpan(
text: widget.email,
style:
textTheme.bodyMedium?.copyWith(color: kTitleColor, fontWeight: FontWeight.bold, fontSize: 16),
)
],
),
),
const SizedBox(height: 24.0),
Form(
key: _pinputKey,
child: p.Pinput(
length: 6,
controller: pinController,
focusNode: focusNode,
defaultPinTheme: defaultPinTheme,
separatorBuilder: (index) => const SizedBox(width: 11),
validator: (value) {
if ((value?.length ?? 0) < 6) {
return lang.S.of(context).enterValidOTP;
}
return null;
},
focusedPinTheme: defaultPinTheme.copyWith(
decoration: defaultPinTheme.decoration!.copyWith(
color: kMainColor.withOpacity(0.1),
border: Border.all(color: focusedBorderColor),
),
),
submittedPinTheme: defaultPinTheme.copyWith(
decoration: defaultPinTheme.decoration!.copyWith(
color: fillColor,
border: Border.all(color: kTitleColor),
),
),
errorPinTheme: defaultPinTheme.copyBorderWith(
border: Border.all(color: Colors.redAccent),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(top: 11, bottom: 11),
child: Text(
_isButtonEnabled
? lang.S.of(context).youCanNowResendYourOtp
: lang.S.of(context).resendOtpSeconds(_start),
),
),
const SizedBox(width: 20),
Visibility(
visible: _isButtonEnabled,
child: TextButton(
onPressed: _isButtonEnabled
? () async {
EasyLoading.show();
SignUpRepo repo = SignUpRepo();
if (await repo.resendOTP(email: widget.email, context: context)) {
_loadOtpSettings();
}
}
: null,
child: Text(
lang.S.of(context).resendOTP,
style: TextStyle(color: kMainColor),
),
),
),
],
),
const SizedBox(height: 24.0),
ElevatedButton(
onPressed: widget.isFormForgotPass
? () async {
if (isClicked) return;
focusNode.unfocus();
if (_pinputKey.currentState?.validate() ?? false) {
isClicked = true;
EasyLoading.show();
ForgotPassRepo repo = ForgotPassRepo();
if (await repo.verifyOTPForgotPass(
email: widget.email, otp: pinController.text, context: context)) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => SetNewPassword(email: widget.email),
),
);
} else {
isClicked = false;
}
}
}
: () async {
if (isClicked) return;
focusNode.unfocus();
if (_pinputKey.currentState?.validate() ?? false) {
isClicked = true;
EasyLoading.show();
SignUpRepo repo = SignUpRepo();
if (await repo.verifyOTP(email: widget.email, otp: pinController.text, context: context)) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProfileSetup(),
),
);
} else {
isClicked = false;
}
}
},
child: Text(lang.S.of(context).continueE),
),
],
),
),
),
);
}
}