1
0
forked from baron/baron-sso

flutter 상위 구조로 포함. infra 분리 리팩토링

This commit is contained in:
Lectom C Han
2025-12-23 17:20:27 +09:00
commit 48589dca5d
85 changed files with 3120 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
class LoginScreen extends ConsumerStatefulWidget {
const LoginScreen({super.key});
@override
ConsumerState<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends ConsumerState<LoginScreen>
with SingleTickerProviderStateMixin {
late TabController _tabController;
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final TextEditingController _phoneController = TextEditingController();
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
_emailController.dispose();
_passwordController.dispose();
_phoneController.dispose();
super.dispose();
}
void _handleEmailLogin() {
// TODO: Implement Descope Email/Password Flow
debugPrint("Email Login: ${_emailController.text}");
}
void _handleSmsLogin() {
// TODO: Implement Descope SMS Enchanted Link Flow
debugPrint("SMS Login: ${_phoneController.text}");
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 400),
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Baron SSO",
style: GoogleFonts.outfit(
fontSize: 32,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 40),
// Tab Bar
TabBar(
controller: _tabController,
tabs: const [
Tab(text: "Email"),
Tab(text: "Phone (SMS)"),
],
),
const SizedBox(height: 24),
// Tab View Content
SizedBox(
height: 300, // Slightly increased height for content
child: TabBarView(
controller: _tabController,
children: [
// Email/Password Form
Column(
children: [
TextField(
controller: _emailController,
decoration: const InputDecoration(
labelText: "Email",
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.email_outlined),
),
),
const SizedBox(height: 16),
TextField(
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(
labelText: "Password",
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.lock_outline),
),
),
const SizedBox(height: 24),
FilledButton(
onPressed: _handleEmailLogin,
style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight(50),
),
child: const Text("Sign In"),
),
],
),
// Phone/SMS Form
Column(
children: [
TextField(
controller: _phoneController,
decoration: const InputDecoration(
labelText: "Phone Number",
hintText: "+82 10-1234-5678",
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.phone_android),
),
),
const SizedBox(height: 24),
FilledButton(
onPressed: _handleSmsLogin,
style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight(50),
),
child: const Text("Send Login Link"),
),
],
),
],
),
),
],
),
),
),
);
}
}

70
frontend/lib/main.dart Normal file
View File

@@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:descope/descope.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'features/auth/presentation/login_screen.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Load Env (Handling error if missing for now)
try {
await dotenv.load(fileName: ".env");
} catch (e) {
debugPrint("Warning: .env file not found.");
}
// Initialize Descope
final projectId = dotenv.env['DESCOPE_PROJECT_ID'] ?? 'your-project-id';
Descope.projectId = projectId;
// Load saved session if any
await Descope.sessionManager.loadSession();
runApp(const ProviderScope(child: BaronSSOApp()));
}
// Router Configuration
final _router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(path: '/', builder: (context, state) => const LoginScreen()),
GoRoute(
path: '/dashboard',
builder: (context, state) =>
const Scaffold(body: Center(child: Text("Dashboard Placeholder"))),
),
],
redirect: (context, state) {
final isLoggedIn =
Descope.sessionManager.session?.refreshToken.isExpired == false;
final isLoggingIn = state.uri.toString() == '/';
if (!isLoggedIn && !isLoggingIn) return '/';
if (isLoggedIn && isLoggingIn) return '/dashboard';
return null;
},
);
class BaronSSOApp extends StatelessWidget {
const BaronSSOApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Baron SSO',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF1A1F2C), // Dark Navy/Black base
brightness: Brightness.light,
),
useMaterial3: true,
textTheme: GoogleFonts.interTextTheme(),
),
routerConfig: _router,
);
}
}