first app design

This commit is contained in:
root
2026-05-24 02:35:37 -04:00
parent c842eed601
commit d04c8ce437
138 changed files with 9672 additions and 0 deletions
@@ -0,0 +1,164 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/providers/auth_provider.dart';
class EmployeeLoginScreen extends ConsumerStatefulWidget {
const EmployeeLoginScreen({super.key});
@override
ConsumerState<EmployeeLoginScreen> createState() =>
_EmployeeLoginScreenState();
}
class _EmployeeLoginScreenState extends ConsumerState<EmployeeLoginScreen> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _obscurePassword = true;
bool _loading = false;
String? _error;
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
Future<void> _login() async {
if (!_formKey.currentState!.validate()) return;
setState(() {
_loading = true;
_error = null;
});
try {
await ref.read(authProvider.notifier).loginEmployee(
_emailController.text.trim(),
_passwordController.text,
);
} catch (e) {
setState(() {
_error = 'Invalid email or password. Please try again.';
});
} finally {
if (mounted) setState(() => _loading = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 32),
IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.arrow_back),
padding: EdgeInsets.zero,
),
const SizedBox(height: 24),
const Text(
'Employee Login',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Color(0xFF111928),
),
),
const SizedBox(height: 8),
const Text(
'Sign in to your company dashboard',
style: TextStyle(fontSize: 15, color: Color(0xFF6B7280)),
),
const SizedBox(height: 40),
if (_error != null) ...[
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFFDE8E8),
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: [
const Icon(Icons.error_outline,
color: Color(0xFFE02424), size: 18),
const SizedBox(width: 8),
Expanded(
child: Text(
_error!,
style: const TextStyle(
color: Color(0xFF9B1C1C),
fontSize: 13,
),
),
),
],
),
),
const SizedBox(height: 20),
],
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
decoration: const InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email_outlined),
),
validator: (v) => (v == null || !v.contains('@'))
? 'Enter a valid email'
: null,
),
const SizedBox(height: 16),
TextFormField(
controller: _passwordController,
obscureText: _obscurePassword,
textInputAction: TextInputAction.done,
onFieldSubmitted: (_) => _login(),
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: const Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(_obscurePassword
? Icons.visibility_outlined
: Icons.visibility_off_outlined),
onPressed: () => setState(
() => _obscurePassword = !_obscurePassword),
),
),
validator: (v) => (v == null || v.isEmpty)
? 'Password is required'
: null,
),
const SizedBox(height: 32),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _loading ? null : _login,
child: _loading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor:
AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: const Text('Sign In'),
),
),
],
),
),
),
),
);
}
}
@@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../../l10n/app_localizations.dart';
import '../../../widgets/preferences_bar.dart';
class LandingScreen extends StatelessWidget {
const LandingScreen({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold(
backgroundColor: const Color(0xFF0F2140),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 28),
child: Column(
children: [
const SizedBox(height: 8),
// Preferences bar (language + theme)
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: const [PreferencesBar(onDark: true)],
),
const Spacer(flex: 2),
// Logo
Hero(
tag: 'app_logo',
child: Image.asset(
'assets/images/logo.jpeg',
width: 160,
height: 160,
fit: BoxFit.contain,
),
),
const SizedBox(height: 28),
Text(
l10n.appName,
style: const TextStyle(
color: Colors.white,
fontSize: 32,
fontWeight: FontWeight.bold,
letterSpacing: 0.5,
),
),
const SizedBox(height: 10),
Text(
l10n.tagline,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white.withValues(alpha: 0.55),
fontSize: 15,
height: 1.5,
),
),
const Spacer(flex: 3),
// Find your car
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => context.go('/client'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFF6B00),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 18),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
elevation: 0,
textStyle: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
letterSpacing: 0.3,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.search_rounded, size: 22),
const SizedBox(width: 10),
Text(l10n.findYourCar),
],
),
),
),
const SizedBox(height: 16),
TextButton(
onPressed: () => context.push('/login/employee'),
style: TextButton.styleFrom(
foregroundColor: Colors.white.withValues(alpha: 0.5),
),
child: Text(
l10n.companySignIn,
style: const TextStyle(fontSize: 13),
),
),
const SizedBox(height: 16),
],
),
),
),
);
}
}
+138
View File
@@ -0,0 +1,138 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class RoleScreen extends StatelessWidget {
const RoleScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 40),
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: const Color(0xFF1A56DB),
borderRadius: BorderRadius.circular(16),
),
child: const Icon(Icons.directions_car,
size: 32, color: Colors.white),
),
const SizedBox(height: 24),
const Text(
'Welcome to\nCar Rental',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Color(0xFF111928),
height: 1.2,
),
),
const SizedBox(height: 12),
const Text(
'How would you like to continue?',
style: TextStyle(fontSize: 16, color: Color(0xFF6B7280)),
),
const SizedBox(height: 48),
_RoleCard(
icon: Icons.dashboard_rounded,
title: 'Company Dashboard',
subtitle: 'Manage fleet, reservations & customers',
color: const Color(0xFF1A56DB),
onTap: () => context.push('/login/employee'),
),
const SizedBox(height: 16),
_RoleCard(
icon: Icons.search_rounded,
title: 'Browse & Reserve',
subtitle: 'Find and book a vehicle',
color: const Color(0xFF0E9F6E),
onTap: () => context.go('/client'),
),
],
),
),
),
);
}
}
class _RoleCard extends StatelessWidget {
final IconData icon;
final String title;
final String subtitle;
final Color color;
final VoidCallback onTap;
const _RoleCard({
required this.icon,
required this.title,
required this.subtitle,
required this.color,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFFE5E7EB)),
),
child: Row(
children: [
Container(
width: 52,
height: 52,
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 26),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Color(0xFF111928),
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: const TextStyle(
fontSize: 13,
color: Color(0xFF6B7280),
),
),
],
),
),
Icon(Icons.arrow_forward_ios_rounded,
size: 16, color: color),
],
),
),
),
);
}
}
@@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/providers/auth_provider.dart';
class SplashScreen extends ConsumerStatefulWidget {
const SplashScreen({super.key});
@override
ConsumerState<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends ConsumerState<SplashScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(authProvider.notifier).init();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFF0F2140),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
'assets/images/logo.jpeg',
width: 130,
height: 130,
fit: BoxFit.contain,
),
const SizedBox(height: 20),
const Text(
'RentalDriveGo',
style: TextStyle(
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.bold,
letterSpacing: 0.5,
),
),
const SizedBox(height: 6),
Text(
'Find & Reserve Your Car',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.6),
fontSize: 14,
),
),
const SizedBox(height: 52),
const CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFFFF6B00)),
strokeWidth: 2.5,
),
],
),
),
);
}
}