feat: استيراد كود سيرو إلى تريبز (سيرو @ecfe7568) — بلا تعديل
قرار المالك 2026-07-27: باك إند سيرو PHP هو المعتمد، وتطبيقاته المجرّبة ميدانياً تحل محل إعادة البناء المؤرشفة. سيرو نفسه لم يُمسّ. الخريطة: backend · payment_server · loction_server · ride_server · passenger_server · docker · dashboard · stress_test → الجذر siro_rider → apps/rider siro_driver → apps/driver siro_admin → dashboards/admin siro_service → dashboards/service android_bot → apps/android_bot socialBot → apps/socialBot نُسخ المتعقَّب في git سيرو فقط عبر `git archive` (3,198 ملفاً / ~169 م.ب) لا `cp -r` — فاستُثنيت مخلفات البناء تلقائياً. بلا أي تعديل محتوى عمداً: كل ما يلي يصير فرقاً مقروءاً مقابل المصدر. لم يُستورد وسببه: siromove.com (الموقع التسويقي يبقى marketing/ في تريبز، سيرو فيه 8 ملفات) · docs و planning (تريبز له docs/ الخاص) · deploy.sh (ليس نشراً على سيرفر بل `git add . && git push origin --all` — فخّ في مستودع آخر) · transit_dashboard (بانتظار قرار مصير backend-transit و dashboards/transit-web). ⚠️ لا يبني بعد — ثلاثة نواقص متوقعة ومقصودة: 1. `.env` و `lib/env/env.g.dart` غير متعقَّبين في سيرو (أسرار لكل مستأجر): كل تطبيق فلاتر يحتاج .env خاصاً ثم توليد env.g.dart بـ build_runner. 2. إعدادات Firebase (9 ملفات google-services.json و GoogleService-Info.plist) يستبعدها .gitignore تريبز — ولكل مستأجر مشروع Firebase خاص أصلاً. 3. apps/driver في سيرو يشير إلى `../../Intaleq/packages/get` خارج المستودع → يجب ضمّ الحزم داخله أسوة بـ apps/rider. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9909d9b4c1
commit
4d8414c96b
@@ -0,0 +1,572 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../controller/auth/otp_helper.dart';
|
||||
import '../widgets/snackbar.dart';
|
||||
import '../admin/admin_home_page.dart';
|
||||
|
||||
// ─── Colors (نفس نظام الألوان المستخدم في التطبيق) ──────────────────────────
|
||||
class _C {
|
||||
static const bg = Color(0xFF0A0D14);
|
||||
static const card = Color(0xFF161D2E);
|
||||
static const border = Color(0xFF1F2D4A);
|
||||
static const accent = Color(0xFF00E5FF);
|
||||
static const accentGlow = Color(0x2200E5FF);
|
||||
static const textPrimary = Color(0xFFE8F0FE);
|
||||
static const textSec = Color(0xFF7A8BAA);
|
||||
static const inputBg = Color(0xFF0C1120);
|
||||
}
|
||||
|
||||
class AdminLoginPage extends StatefulWidget {
|
||||
const AdminLoginPage({super.key});
|
||||
|
||||
@override
|
||||
State<AdminLoginPage> createState() => _AdminLoginPageState();
|
||||
}
|
||||
|
||||
class _AdminLoginPageState extends State<AdminLoginPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final _phoneController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
bool _isLoading = false;
|
||||
|
||||
late final AnimationController _glowCtrl;
|
||||
late final Animation<double> _glowAnim;
|
||||
|
||||
Future<void> _submit() async {
|
||||
final password = _passwordController.text.trim();
|
||||
final phone = _phoneController.text.trim();
|
||||
|
||||
if (password.isEmpty) {
|
||||
mySnackbarError('يرجى إدخال كلمة المرور');
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
final otpHelper = Get.isRegistered<OtpHelper>()
|
||||
? Get.find<OtpHelper>()
|
||||
: Get.put(OtpHelper(), permanent: true);
|
||||
bool success = await otpHelper.loginWithPassword(password, phone);
|
||||
|
||||
if (success) {
|
||||
Get.offAll(() => const AdminHomePage());
|
||||
}
|
||||
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeToken();
|
||||
|
||||
_glowCtrl = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(seconds: 4),
|
||||
)..repeat(reverse: true);
|
||||
_glowAnim = Tween<double>(begin: 0.3, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _glowCtrl, curve: Curves.easeInOut),
|
||||
);
|
||||
}
|
||||
|
||||
void _initializeToken() async {
|
||||
// await CRUD().getJWT();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_phoneController.dispose();
|
||||
_glowCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// ─── Build ─────────────────────────────────────────────────────────────────
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(OtpHelper());
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: _C.bg,
|
||||
body: Stack(
|
||||
children: [
|
||||
// ── Ambient glow top-right ──────────────────────────────────────────
|
||||
Positioned(
|
||||
top: -150,
|
||||
right: -100,
|
||||
child: AnimatedBuilder(
|
||||
animation: _glowAnim,
|
||||
builder: (_, __) => Opacity(
|
||||
opacity: _glowAnim.value * 0.18,
|
||||
child: Container(
|
||||
width: 400,
|
||||
height: 400,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: RadialGradient(
|
||||
colors: [Color(0xFF00E5FF), Colors.transparent],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// ── Ambient glow bottom-left ────────────────────────────────────────
|
||||
Positioned(
|
||||
bottom: -120,
|
||||
left: -80,
|
||||
child: AnimatedBuilder(
|
||||
animation: _glowAnim,
|
||||
builder: (_, __) => Opacity(
|
||||
opacity: (1 - _glowAnim.value) * 0.15,
|
||||
child: Container(
|
||||
width: 340,
|
||||
height: 340,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: RadialGradient(
|
||||
colors: [Color(0xFF7C4DFF), Colors.transparent],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ── Main content ───────────────────────────────────────────────────
|
||||
SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 440),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// ── Logo / Icon ─────────────────────────────────────
|
||||
_buildLogo(),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// ── Title ───────────────────────────────────────────
|
||||
const Text(
|
||||
'لوحة الإدارة',
|
||||
style: TextStyle(
|
||||
color: _C.textPrimary,
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'أدخل كلمة المرور للمتابعة',
|
||||
style: TextStyle(
|
||||
color: _C.textSec,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// ── Card ────────────────────────────────────────────
|
||||
Container(
|
||||
padding: const EdgeInsets.all(28),
|
||||
decoration: BoxDecoration(
|
||||
color: _C.card,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(
|
||||
color: _C.accent.withOpacity(0.18), width: 1.2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.4),
|
||||
blurRadius: 32,
|
||||
offset: const Offset(0, 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// ── Field label (Phone) ─────────────────────────────
|
||||
const Row(
|
||||
children: [
|
||||
Icon(Icons.phone_android_rounded,
|
||||
color: _C.accent, size: 16),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'رقم الهاتف (لأول دخول فقط)',
|
||||
style: TextStyle(
|
||||
color: _C.textSec,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// ── Phone field ─────────────────────────────
|
||||
TextFormField(
|
||||
controller: _phoneController,
|
||||
keyboardType: TextInputType.phone,
|
||||
style: const TextStyle(
|
||||
color: _C.textPrimary,
|
||||
fontSize: 16,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: '07XXXXXXXX',
|
||||
hintStyle: const TextStyle(color: _C.textSec),
|
||||
filled: true,
|
||||
fillColor: _C.inputBg,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: const BorderSide(
|
||||
color: _C.border, width: 1),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: const BorderSide(
|
||||
color: _C.accent, width: 1.5),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 16),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// ── Field label (Password) ─────────────────────────────
|
||||
const Row(
|
||||
children: [
|
||||
Icon(Icons.lock_outline_rounded,
|
||||
color: _C.accent, size: 16),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'كلمة المرور',
|
||||
style: TextStyle(
|
||||
color: _C.textSec,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// ── Password field ─────────────────────────────
|
||||
TextFormField(
|
||||
controller: _passwordController,
|
||||
obscureText: true,
|
||||
style: const TextStyle(
|
||||
color: _C.textPrimary,
|
||||
fontSize: 16,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: '••••••••',
|
||||
hintStyle: const TextStyle(color: _C.textSec),
|
||||
filled: true,
|
||||
fillColor: _C.inputBg,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: const BorderSide(
|
||||
color: _C.border, width: 1),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: const BorderSide(
|
||||
color: _C.accent, width: 1.5),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 16),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'الرجاء إدخال كلمة المرور';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
if (Get.find<OtpHelper>().isFirstRun) ...[
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// ── Country Dropdown ───────────────────────────
|
||||
const Row(
|
||||
children: [
|
||||
Icon(Icons.public_rounded,
|
||||
color: _C.accent, size: 16),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'الدولة',
|
||||
style: TextStyle(
|
||||
color: _C.textSec,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Obx(() {
|
||||
final otpHelper = Get.find<OtpHelper>();
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: _C.inputBg,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: _C.border, width: 1),
|
||||
),
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<String>(
|
||||
value: otpHelper.selectedCountry.value,
|
||||
icon: const Icon(Icons.arrow_drop_down, color: _C.accent),
|
||||
isExpanded: true,
|
||||
dropdownColor: _C.card,
|
||||
style: const TextStyle(color: _C.textPrimary, fontSize: 16),
|
||||
items: ['Jordan', 'Egypt', 'Syria'].map((String country) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: country,
|
||||
child: Text(country.tr),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (String? val) {
|
||||
if (val != null) {
|
||||
otpHelper.changeCountry(val);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 28),
|
||||
],
|
||||
|
||||
// ── Submit button ────────────────────────────
|
||||
_isLoading
|
||||
? const Center(
|
||||
child: SizedBox(
|
||||
width: 32,
|
||||
height: 32,
|
||||
child: CircularProgressIndicator(
|
||||
color: _C.accent,
|
||||
strokeWidth: 2.5,
|
||||
),
|
||||
),
|
||||
)
|
||||
: _SubmitButton(onPressed: () {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
_submit();
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// ── Register Button ─────────────────────────────────
|
||||
TextButton(
|
||||
onPressed: () => Get.toNamed('/register'),
|
||||
child: const Text(
|
||||
'ليس لديك حساب؟ طلب انضمام',
|
||||
style: TextStyle(
|
||||
color: _C.accent,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ── Footer ──────────────────────────────────────────
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _C.accent,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: _C.accentGlow,
|
||||
blurRadius: 6,
|
||||
spreadRadius: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'وصول مقيّد للمشرفين فقط',
|
||||
style: TextStyle(
|
||||
color: _C.textSec,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// ── Register Link ───────────────────────────────────────────
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'ليس لديك حساب مشرف؟',
|
||||
style: TextStyle(color: _C.textSec, fontSize: 14),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.toNamed('/register'); // أو الانتقال المباشر للصفحة
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: _C.accent,
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
child: const Text('تسجيل حساب جديد'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Logo Widget ─────────────────────────────────────────────────────────
|
||||
Widget _buildLogo() {
|
||||
return AnimatedBuilder(
|
||||
animation: _glowAnim,
|
||||
builder: (_, child) => Container(
|
||||
width: 90,
|
||||
height: 90,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _C.card,
|
||||
border: Border.all(
|
||||
color: _C.accent.withOpacity(0.3 + _glowAnim.value * 0.3),
|
||||
width: 1.5,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: _C.accentGlow.withOpacity(_glowAnim.value * 0.6),
|
||||
blurRadius: 30,
|
||||
spreadRadius: 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.admin_panel_settings_rounded,
|
||||
color: _C.accent,
|
||||
size: 42,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Submit Button ─────────────────────────────────────────────────────────────
|
||||
class _SubmitButton extends StatefulWidget {
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const _SubmitButton({required this.onPressed});
|
||||
|
||||
@override
|
||||
State<_SubmitButton> createState() => _SubmitButtonState();
|
||||
}
|
||||
|
||||
class _SubmitButtonState extends State<_SubmitButton>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _ctrl;
|
||||
late final Animation<double> _scale;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_ctrl = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 100));
|
||||
_scale = Tween<double>(begin: 1.0, end: 0.96)
|
||||
.animate(CurvedAnimation(parent: _ctrl, curve: Curves.easeOut));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ctrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTapDown: (_) => _ctrl.forward(),
|
||||
onTapUp: (_) => _ctrl.reverse(),
|
||||
onTapCancel: () => _ctrl.reverse(),
|
||||
onTap: widget.onPressed,
|
||||
child: AnimatedBuilder(
|
||||
animation: _scale,
|
||||
builder: (_, child) =>
|
||||
Transform.scale(scale: _scale.value, child: child),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 17),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF00B4D8), Color(0xFF00E5FF)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0x3300E5FF),
|
||||
blurRadius: 20,
|
||||
spreadRadius: 1,
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.send_rounded, color: Colors.white, size: 18),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
'تسجيل الدخول',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_admin/controller/auth/register_controller.dart';
|
||||
|
||||
class _C {
|
||||
static const bg = Color(0xFF0A0D14);
|
||||
static const card = Color(0xFF161D2E);
|
||||
static const accent = Color(0xFF00E5FF);
|
||||
static const textPrimary = Color(0xFFE8F0FE);
|
||||
static const textSec = Color(0xFF7A8BAA);
|
||||
static const inputBg = Color(0xFF0C1120);
|
||||
}
|
||||
|
||||
class AdminRegisterPage extends StatelessWidget {
|
||||
const AdminRegisterPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.put(AdminRegisterController());
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: _C.bg,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios, color: _C.textPrimary),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 440),
|
||||
child: Form(
|
||||
key: controller.formKey,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.admin_panel_settings, size: 80, color: _C.accent),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
'تسجيل مشرف جديد',
|
||||
style: TextStyle(
|
||||
color: _C.textPrimary,
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'أدخل بياناتك لتقديم طلب الإشراف',
|
||||
style: TextStyle(color: _C.textSec, fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(28),
|
||||
decoration: BoxDecoration(
|
||||
color: _C.card,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(color: _C.accent.withOpacity(0.18), width: 1.2),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildLabel('الاسم الكامل', Icons.person),
|
||||
const SizedBox(height: 10),
|
||||
_buildTextField(
|
||||
controller: controller.nameCtrl,
|
||||
hint: 'أحمد محمود',
|
||||
icon: Icons.person_outline,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildLabel('رقم الهاتف', Icons.phone_android),
|
||||
const SizedBox(height: 10),
|
||||
_buildTextField(
|
||||
controller: controller.phoneCtrl,
|
||||
hint: '07XXXXXXXX',
|
||||
keyboardType: TextInputType.phone,
|
||||
icon: Icons.phone_outlined,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildLabel('كلمة المرور', Icons.lock_outline),
|
||||
const SizedBox(height: 10),
|
||||
_buildTextField(
|
||||
controller: controller.passCtrl,
|
||||
hint: '••••••••',
|
||||
obscureText: true,
|
||||
icon: Icons.lock_outline,
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
Obx(() => MaterialButton(
|
||||
onPressed: controller.isLoading.value ? null : () => controller.register(),
|
||||
color: _C.accent,
|
||||
height: 55,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: controller.isLoading.value
|
||||
? const CircularProgressIndicator(color: _C.bg)
|
||||
: const Text(
|
||||
'تقديم الطلب',
|
||||
style: TextStyle(color: _C.bg, fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLabel(String text, IconData icon) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(icon, color: _C.accent, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
text,
|
||||
style: const TextStyle(color: _C.textSec, fontSize: 13, fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTextField({
|
||||
required TextEditingController controller,
|
||||
required String hint,
|
||||
required IconData icon,
|
||||
bool obscureText = false,
|
||||
TextInputType keyboardType = TextInputType.text,
|
||||
}) {
|
||||
return TextFormField(
|
||||
controller: controller,
|
||||
obscureText: obscureText,
|
||||
keyboardType: keyboardType,
|
||||
style: const TextStyle(color: _C.textPrimary, fontSize: 16),
|
||||
decoration: InputDecoration(
|
||||
hintText: hint,
|
||||
hintStyle: const TextStyle(color: _C.textSec),
|
||||
filled: true,
|
||||
fillColor: _C.inputBg,
|
||||
prefixIcon: Icon(icon, color: _C.textSec),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(14), borderSide: BorderSide.none),
|
||||
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: _C.accent, width: 1.5)),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
),
|
||||
validator: (val) => (val == null || val.isEmpty) ? 'هذا الحقل مطلوب' : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user