Compare commits
No commits in common. "00c506372346538500c9c5080688373c94857506" and "ecd216409d0b2eba6ad0f5f7390a8770687ac0a7" have entirely different histories.
00c5063723
...
ecd216409d
|
|
@ -57,7 +57,6 @@ linked_*.ds
|
|||
unlinked.ds
|
||||
unlinked_spec.ds
|
||||
|
||||
|
||||
# Android related
|
||||
**/android/**/gradle-wrapper.jar
|
||||
.gradle/
|
||||
|
|
@ -145,8 +144,8 @@ dist/
|
|||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
#lib/
|
||||
# lib64/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
|
|
|
|||
|
|
@ -4,12 +4,6 @@
|
|||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Flutter",
|
||||
"type": "dart",
|
||||
"request": "launch",
|
||||
"program": "lib/main.dart"
|
||||
},
|
||||
{
|
||||
"name": "complycore_flutter",
|
||||
"cwd": "frontend\\complycore_flutter",
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import 'screens/splash_screen.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await Supabase.initialize(
|
||||
url: const String.fromEnvironment(
|
||||
'SUPABASE_URL',
|
||||
defaultValue: 'https://YOUR-SUPABASE-URL.supabase.co',
|
||||
),
|
||||
anonKey: const String.fromEnvironment(
|
||||
'SUPABASE_ANON_KEY',
|
||||
defaultValue: 'YOUR-SUPABASE-ANON-KEY',
|
||||
),
|
||||
);
|
||||
|
||||
runApp(const ComplyCoreApp());
|
||||
}
|
||||
|
||||
class ComplyCoreApp extends StatelessWidget {
|
||||
const ComplyCoreApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => MaterialApp(
|
||||
title: 'ComplyCore',
|
||||
theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
|
||||
home: const SplashScreen(),
|
||||
);
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class HomeScreen extends StatelessWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) =>
|
||||
const Scaffold(body: Center(child: Text('Welcome to ComplyCore!')));
|
||||
}
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
// lib/screens/login_screen.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import 'register_screen.dart'; // <-- this must already exist in lib/screens/
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
const LoginScreen({super.key});
|
||||
|
||||
@override
|
||||
State<LoginScreen> createState() => _LoginScreenState();
|
||||
}
|
||||
|
||||
class _LoginScreenState extends State<LoginScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _emailCtrl = TextEditingController();
|
||||
final _passCtrl = TextEditingController();
|
||||
|
||||
bool _loading = false;
|
||||
String? _error;
|
||||
|
||||
Future<void> _signIn() async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
|
||||
setState(() {
|
||||
_loading = true;
|
||||
_error = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final res = await Supabase.instance.client.auth.signInWithPassword(
|
||||
email: _emailCtrl.text.trim(),
|
||||
password: _passCtrl.text,
|
||||
);
|
||||
if (res.session == null) {
|
||||
setState(() => _error = 'Invalid email or password');
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() => _error = e.toString());
|
||||
} finally {
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
minimum: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'ComplyCore Login',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// E-mail
|
||||
TextFormField(
|
||||
controller: _emailCtrl,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: const InputDecoration(labelText: 'Email'),
|
||||
validator: (v) => v != null && v.contains('@')
|
||||
? null
|
||||
: 'Enter a valid email',
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Password
|
||||
TextFormField(
|
||||
controller: _passCtrl,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(labelText: 'Password'),
|
||||
validator: (v) =>
|
||||
v != null && v.length >= 6 ? null : 'Min 6 characters',
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
ElevatedButton(
|
||||
onPressed: _loading ? null : _signIn,
|
||||
child: _loading
|
||||
? const CircularProgressIndicator()
|
||||
: const Text('Sign In'),
|
||||
),
|
||||
|
||||
if (_error != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
Text(_error!, style: const TextStyle(color: Colors.red)),
|
||||
],
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// ------------- NEW LINK -------------
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text("Don't have an account?"),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const RegisterScreen(),
|
||||
),
|
||||
),
|
||||
child: const Text('Register'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
// lib/screens/profile_screen.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class ProfileScreen extends StatefulWidget {
|
||||
const ProfileScreen({super.key});
|
||||
|
||||
@override
|
||||
State<ProfileScreen> createState() => _ProfileScreenState();
|
||||
}
|
||||
|
||||
class _ProfileScreenState extends State<ProfileScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
final _fullNameController = TextEditingController();
|
||||
final _companyController = TextEditingController();
|
||||
|
||||
bool _loading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchProfile();
|
||||
}
|
||||
|
||||
Future<void> _fetchProfile() async {
|
||||
setState(() => _loading = true);
|
||||
|
||||
final userId = Supabase.instance.client.auth.currentUser?.id;
|
||||
if (userId == null) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('No active session.')));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab profile row
|
||||
final Map<String, dynamic>? profile = await Supabase.instance.client
|
||||
.from('profiles')
|
||||
.select('*')
|
||||
.eq('id', userId)
|
||||
.maybeSingle();
|
||||
|
||||
if (!mounted) return; // ← guard BuildContext after await
|
||||
|
||||
if (profile != null) {
|
||||
_fullNameController.text = profile['full_name'] ?? '';
|
||||
_companyController.text = profile['company'] ?? '';
|
||||
} else {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Profile not found.')));
|
||||
}
|
||||
|
||||
setState(() => _loading = false);
|
||||
}
|
||||
|
||||
Future<void> _updateProfile() async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
|
||||
setState(() => _loading = true);
|
||||
|
||||
final user = Supabase.instance.client.auth.currentUser;
|
||||
if (user == null) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('No active session.')));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final updates = {
|
||||
'id': user.id,
|
||||
'full_name': _fullNameController.text.trim(),
|
||||
'company': _companyController.text.trim(),
|
||||
'updated_at': DateTime.now().toIso8601String(),
|
||||
};
|
||||
|
||||
try {
|
||||
await Supabase.instance.client.from('profiles').upsert(updates);
|
||||
if (!mounted) return;
|
||||
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Profile updated!')));
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Update failed: $e')));
|
||||
} finally {
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _signOut() async {
|
||||
await Supabase.instance.client.auth.signOut();
|
||||
if (!mounted) return;
|
||||
Navigator.of(context).pushReplacementNamed('/login');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_fullNameController.dispose();
|
||||
_companyController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('My Profile'),
|
||||
actions: [
|
||||
IconButton(icon: const Icon(Icons.logout), onPressed: _signOut),
|
||||
],
|
||||
),
|
||||
body: _loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _fullNameController,
|
||||
decoration: const InputDecoration(labelText: 'Full name'),
|
||||
validator: (v) =>
|
||||
v == null || v.isEmpty ? 'Required' : null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _companyController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Company name',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
ElevatedButton(
|
||||
onPressed: _updateProfile,
|
||||
child: const Text('Save changes'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class RegisterScreen extends StatefulWidget {
|
||||
const RegisterScreen({super.key}); // ← super-parameter ✔
|
||||
|
||||
@override
|
||||
State<RegisterScreen> createState() => _RegisterScreenState();
|
||||
}
|
||||
|
||||
class _RegisterScreenState extends State<RegisterScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
final _nameController = TextEditingController();
|
||||
final _emailController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
|
||||
bool _loading = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_emailController.dispose();
|
||||
_passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _signUp() async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
|
||||
setState(() => _loading = true);
|
||||
|
||||
try {
|
||||
await Supabase.instance.client.auth.signUp(
|
||||
email: _emailController.text.trim(),
|
||||
password: _passwordController.text,
|
||||
data: {'full_name': _nameController.text.trim()},
|
||||
);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Registration successful! Confirm your email, then sign in.',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Navigator.of(context).pushReplacementNamed('/login');
|
||||
} on AuthException catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(e.message)));
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Sign-up failed: $e')));
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('ComplyCore • Register')),
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _nameController,
|
||||
decoration: const InputDecoration(labelText: 'Full name'),
|
||||
validator: (v) => (v == null || v.trim().isEmpty)
|
||||
? 'Enter your name'
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
decoration: const InputDecoration(labelText: 'Email'),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (v) => (v == null || !v.contains('@'))
|
||||
? 'Enter a valid email'
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _passwordController,
|
||||
decoration: const InputDecoration(labelText: 'Password'),
|
||||
obscureText: true,
|
||||
validator: (v) =>
|
||||
(v == null || v.length < 6) ? 'Min 6 characters' : null,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
_loading
|
||||
? const CircularProgressIndicator()
|
||||
: ElevatedButton(
|
||||
onPressed: _signUp,
|
||||
child: const Text('Register'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context).pushReplacementNamed('/login'),
|
||||
child: const Text('Already have an account? Sign in'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import 'home_screen.dart';
|
||||
import 'login_screen.dart';
|
||||
|
||||
class SplashScreen extends StatefulWidget {
|
||||
const SplashScreen({super.key});
|
||||
|
||||
@override
|
||||
State<SplashScreen> createState() => _SplashScreenState();
|
||||
}
|
||||
|
||||
class _SplashScreenState extends State<SplashScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Listen for auth changes first
|
||||
Supabase.instance.client.auth.onAuthStateChange.listen((data) {
|
||||
final session = data.session;
|
||||
_routeAccordingToSession(session);
|
||||
});
|
||||
|
||||
// Fallback: check once after short delay
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
if (!mounted) return;
|
||||
_routeAccordingToSession(Supabase.instance.client.auth.currentSession);
|
||||
});
|
||||
}
|
||||
|
||||
void _routeAccordingToSession(Session? session) {
|
||||
final target = session == null ? const LoginScreen() : const HomeScreen();
|
||||
Navigator.of(
|
||||
context,
|
||||
).pushReplacement(MaterialPageRoute(builder: (_) => target));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) =>
|
||||
const Scaffold(body: Center(child: CircularProgressIndicator()));
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class AuthService {
|
||||
static final _supabase = Supabase.instance.client;
|
||||
|
||||
Future<AuthResponse> signIn(String email, String password) =>
|
||||
_supabase.auth.signInWithPassword(email: email, password: password);
|
||||
|
||||
Future<AuthResponse> signUp(String email, String password) =>
|
||||
_supabase.auth.signUp(email: email, password: password);
|
||||
|
||||
Future<void> signOut() => _supabase.auth.signOut();
|
||||
|
||||
User? get currentUser => _supabase.auth.currentUser;
|
||||
}
|
||||
Loading…
Reference in New Issue