chore: أرشفة تطبيقات ولوحات تريبز التي سيحلّ محلها كود سيرو

استكمال قرار 2026-07-27 (كوميت fff76c9 أرشف الباك إند):
  apps/rider          → apps/rider-archive-cubit
  apps/driver         → apps/driver-archive-cubit
  dashboards/admin-web    → dashboards/admin-web-archive
  dashboards/service-web  → dashboards/service-web-archive

إعادة تسمية بلا تعديل محتوى. rider/driver كانا إعادة بناء Cubit مبنية على
عقد NestJS المؤرشف. admin-web و service-web نموذجان بملف HTML واحد (164 و
128 سطراً) — يحلّ محلهما siro_admin و siro_service المكتملان.

يُحمل من المؤرشف ولا يُعاد اكتشافه: عقد map-saas المثبَّت بالتجربة في
apps/rider-archive-cubit/lib/core/api/ (x-api-key ترويسة، reverse مصفوفة،
route في points) ودرس مسار FCM.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-27 05:12:04 +03:00
co-authored by Claude Opus 5
parent fff76c949f
commit 9909d9b4c1
809 changed files with 0 additions and 0 deletions
@@ -0,0 +1,46 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import '../data/auth_repository.dart';
import 'auth_state.dart';
class AuthCubit extends Cubit<AuthState> {
final AuthRepository _repo;
AuthCubit(this._repo) : super(const AuthState());
Future<void> checkAuth() async {
final user = await _repo.me();
emit(state.copyWith(
status: user != null ? AuthStatus.authenticated : AuthStatus.unauthenticated,
user: user,
));
}
Future<void> sendOtp(String phone) async {
if (phone.trim().isEmpty) {
emit(state.copyWith(status: AuthStatus.error, error: 'أدخل رقم الهاتف'));
return;
}
try {
emit(state.copyWith(status: AuthStatus.authenticating, phone: phone.trim()));
await _repo.sendOtp(phone.trim());
emit(state.copyWith(status: AuthStatus.codeSent, phone: phone.trim()));
} catch (_) {
emit(state.copyWith(status: AuthStatus.error, error: 'تعذّر إرسال الرمز'));
}
}
Future<void> verify(String code) async {
try {
emit(state.copyWith(status: AuthStatus.authenticating));
final user = await _repo.verify(state.phone, code.trim());
emit(state.copyWith(status: AuthStatus.authenticated, user: user));
} catch (_) {
emit(state.copyWith(status: AuthStatus.error, error: 'رمز غير صحيح'));
}
}
Future<void> logout() async {
await _repo.logout();
emit(const AuthState(status: AuthStatus.unauthenticated));
}
}
@@ -0,0 +1,42 @@
import 'package:equatable/equatable.dart';
import '../data/auth_repository.dart';
enum AuthStatus {
unknown,
unauthenticated,
codeSent,
authenticating,
authenticated,
error,
}
class AuthState extends Equatable {
final AuthStatus status;
final String phone;
final AppUser? user;
final String? error;
const AuthState({
this.status = AuthStatus.unknown,
this.phone = '',
this.user,
this.error,
});
AuthState copyWith({
AuthStatus? status,
String? phone,
AppUser? user,
String? error,
}) {
return AuthState(
status: status ?? this.status,
phone: phone ?? this.phone,
user: user ?? this.user,
error: error,
);
}
@override
List<Object?> get props => [status, phone, user, error];
}
@@ -0,0 +1,53 @@
import 'package:dio/dio.dart';
import '../../../core/api/api_client.dart';
import '../../../core/storage/token_store.dart';
class AppUser {
final String id;
final String phone;
final String? name;
final String role;
AppUser({required this.id, required this.phone, this.name, required this.role});
factory AppUser.fromJson(Map<String, dynamic> j) => AppUser(
id: j['id'] as String,
phone: (j['phone'] ?? '') as String,
name: j['name'] as String?,
role: (j['role'] ?? 'driver') as String,
);
}
class AuthRepository {
final ApiClient _api;
final TokenStore _store;
AuthRepository(this._api, this._store);
Future<void> sendOtp(String phone) async {
await _api.dio.post('/auth/send-otp', data: {'phone': phone});
}
Future<AppUser> verify(String phone, String code) async {
final r = await _api.dio.post(
'/auth/verify-otp',
data: {'phone': phone, 'code': code},
);
await _store.save(
r.data['access_token'] as String,
r.data['refresh_token'] as String,
);
return AppUser.fromJson(Map<String, dynamic>.from(r.data['user'] as Map));
}
Future<AppUser?> me() async {
try {
final r = await _api.dio.get('/users/me');
return AppUser.fromJson(Map<String, dynamic>.from(r.data as Map));
} on DioException {
return null;
}
}
Future<void> logout() => _store.clear();
}
@@ -0,0 +1,78 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:driver/core/l10n/app_localizations.dart';
import '../cubit/auth_cubit.dart';
import '../cubit/auth_state.dart';
import '../../../core/ui/tripz_scaffold.dart';
import '../../../core/ui/tripz_button.dart';
import '../../../core/ui/tripz_text_field.dart';
class OtpScreen extends StatefulWidget {
const OtpScreen({super.key});
@override
State<OtpScreen> createState() => _OtpScreenState();
}
class _OtpScreenState extends State<OtpScreen> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return TripzScaffold(
showBackButton: true,
onBack: () => context.go('/phone'),
child: BlocConsumer<AuthCubit, AuthState>(
listener: (context, state) {
if (state.status == AuthStatus.error && state.error != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.error!)),
);
}
},
builder: (context, state) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 24),
Text(
l10n.otpTitle,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
l10n.otpSentTo(state.phone),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 28),
TripzTextField(
controller: _controller,
label: l10n.otpLabel,
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
maxLength: 6,
),
const SizedBox(height: 20),
TripzButton(
label: l10n.confirm,
loading: state.status == AuthStatus.authenticating,
onPressed: () =>
context.read<AuthCubit>().verify(_controller.text),
),
],
);
},
),
);
}
}
@@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:driver/core/l10n/app_localizations.dart';
import '../cubit/auth_cubit.dart';
import '../cubit/auth_state.dart';
import '../../../core/ui/tripz_scaffold.dart';
import '../../../core/ui/tripz_button.dart';
import '../../../core/ui/tripz_text_field.dart';
class PhoneScreen extends StatefulWidget {
const PhoneScreen({super.key});
@override
State<PhoneScreen> createState() => _PhoneScreenState();
}
class _PhoneScreenState extends State<PhoneScreen> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return TripzScaffold(
safeAreaTop: true,
padding: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Spacer(flex: 2),
Text(
l10n.appName,
style: Theme.of(context).textTheme.headlineLarge,
),
const SizedBox(height: 8),
Text(
l10n.phoneTitle,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const Spacer(),
TripzTextField(
controller: _controller,
label: l10n.phoneLabel,
hint: l10n.phoneHint,
keyboardType: TextInputType.phone,
),
const SizedBox(height: 20),
BlocConsumer<AuthCubit, AuthState>(
listener: (context, state) {
if (state.status == AuthStatus.error && state.error != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.error!)),
);
}
},
builder: (context, state) {
return TripzButton(
label: l10n.sendCode,
loading: state.status == AuthStatus.authenticating,
onPressed: () =>
context.read<AuthCubit>().sendOtp(_controller.text),
);
},
),
const Spacer(flex: 2),
],
),
);
}
}