Compare commits
No commits in common. "00c506372346538500c9c5080688373c94857506" and "ecd216409d0b2eba6ad0f5f7390a8770687ac0a7" have entirely different histories.
00c5063723
...
ecd216409d
|
|
@ -57,7 +57,6 @@ linked_*.ds
|
||||||
unlinked.ds
|
unlinked.ds
|
||||||
unlinked_spec.ds
|
unlinked_spec.ds
|
||||||
|
|
||||||
|
|
||||||
# Android related
|
# Android related
|
||||||
**/android/**/gradle-wrapper.jar
|
**/android/**/gradle-wrapper.jar
|
||||||
.gradle/
|
.gradle/
|
||||||
|
|
@ -145,8 +144,8 @@ dist/
|
||||||
downloads/
|
downloads/
|
||||||
eggs/
|
eggs/
|
||||||
.eggs/
|
.eggs/
|
||||||
#lib/
|
lib/
|
||||||
# lib64/
|
lib64/
|
||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,6 @@
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
|
||||||
"name": "Flutter",
|
|
||||||
"type": "dart",
|
|
||||||
"request": "launch",
|
|
||||||
"program": "lib/main.dart"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "complycore_flutter",
|
"name": "complycore_flutter",
|
||||||
"cwd": "frontend\\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