feat: consolidate backend and docker-compose setup

This commit is contained in:
Rafhan Mazaya Fathurrahman committed 2026-06-30 21:09:08 +07:00
commit ff3753a745
306 files changed
+35450

No files matched your search

+147
View File
@@ -0,0 +1,147 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../config/app_config.dart';
import 'auth_provider.dart';
class LoginScreen extends ConsumerStatefulWidget {
const LoginScreen({super.key});
@override
ConsumerState<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends ConsumerState<LoginScreen> {
final _idController = TextEditingController();
final _passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
void _handleLogin() async {
if (!_formKey.currentState!.validate()) return;
final id = _idController.text.trim();
final pwd = _passwordController.text.trim();
setState(() => _isLoading = true);
try {
await ref.read(authProvider.notifier).login(id, pwd);
if (mounted) {
context.go('/camera');
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
e.toString().replaceAll(RegExp(r'DioException.*: '), ''),
style: const TextStyle(color: Colors.white),
),
backgroundColor: AppConfig.errorColor,
),
);
}
} finally {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(32.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Image.asset(
'assets/logo.png',
width: 120,
height: 120,
errorBuilder: (context, error, stackTrace) => const Icon(
Icons.storefront,
size: 80,
color: AppConfig.primaryColor
),
),
const SizedBox(height: 24),
Text(
'Welcome Back',
style: AppConfig.h1,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Sign in to sync your documents to the central server',
style: AppConfig.bodyMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 48),
TextFormField(
controller: _idController,
decoration: const InputDecoration(
labelText: 'Admin Provided ID',
prefixIcon: Icon(Icons.person_outline),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Username/ID cannot be empty';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock_outline),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Password cannot be empty';
}
if (value.trim().length < 6) {
return 'Password must be at least 6 characters';
}
return null;
},
),
const SizedBox(height: 32),
SizedBox(
height: 56,
child: ElevatedButton(
onPressed: _isLoading ? null : _handleLogin,
child: _isLoading
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(color: Colors.white, strokeWidth: 3)
)
: const Text('Login'),
),
),
const SizedBox(height: 24),
Text(
'Please contact the central admin for credentials.',
style: AppConfig.bodySmall,
textAlign: TextAlign.center,
),
],
),
),
),
),
),
);
}
}