Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions frontend/ongi/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import 'package:flutter/material.dart';
import 'package:ongi/screens/parent_init_screen.dart';
import 'package:ongi/screens/health_log_screen.dart';
import 'package:ongi/screens/home_screen.dart';
import 'package:ongi/screens/login_screen.dart';
import 'package:ongi/screens/login/login_pw_screen.dart';
import 'package:ongi/screens/start_screen.dart';
import 'package:ongi/screens/signup/password_screen.dart';
import 'package:ongi/screens/bottom_nav.dart';

void main() {
runApp(const OngiApp());
Expand All @@ -27,7 +23,7 @@ class OngiApp extends StatelessWidget {
home: const StartScreen(),

routes: {
// '/login': (context) => const LoginScreen(),
'/login': (context) => const LoginPwScreen(),
'/signup': (context) => const PasswordScreen(),
}
);
Expand Down
175 changes: 175 additions & 0 deletions frontend/ongi/lib/screens/login/login_pw_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import 'package:flutter/material.dart';
import 'package:ongi/core/app_colors.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:ongi/services/login_service.dart';
import 'package:ongi/screens/login/login_ready_screen.dart';

class LoginPwScreen extends StatefulWidget {
const LoginPwScreen({super.key});

@override
State<LoginPwScreen> createState() => _LoginPwScreenState();
}

class _LoginPwScreenState extends State<LoginPwScreen> {
final TextEditingController _passwordCtrl = TextEditingController();
final LoginService _loginService = LoginService();
bool _isLoading = false;

void _showErrorSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
message,
style: const TextStyle(color: AppColors.ongiOrange),
),
backgroundColor: Colors.white,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
duration: const Duration(seconds: 2),
),
);
}

@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 30, right: 30, top: 150),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
const Text(
'비밀번호를',
style: TextStyle(
fontSize: 60,
fontWeight: FontWeight.w800,
height: 1.2,
color: AppColors.ongiOrange,
),
),
const Text(
'입력해주세요',
style: TextStyle(
fontSize: 60,
fontWeight: FontWeight.w200,
height: 1.2,
color: AppColors.ongiOrange,
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(left: 40, right: 40, top: 40),
child: Text(
'반갑습니다!',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w300,
color: Colors.grey
)
)
),
Padding(
padding: const EdgeInsets.only(left: 40, right: 40, top: 10),
child: TextField(
controller: _passwordCtrl,
keyboardType: TextInputType.visiblePassword,
style: const TextStyle(fontSize: 25, color: AppColors.ongiOrange),
decoration: InputDecoration(
hintText: 'PASSWORD',
hintStyle:
TextStyle(color: Colors.grey),
contentPadding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 13),
filled: true,
fillColor: Colors.transparent,
enabledBorder: OutlineInputBorder(
borderSide:
const BorderSide(color: AppColors.ongiOrange, width: 1),
borderRadius: BorderRadius.circular(20),
),
focusedBorder: OutlineInputBorder(
borderSide:
const BorderSide(color: AppColors.ongiOrange, width: 1),
borderRadius: BorderRadius.circular(20),
),
),
),
Comment on lines +83 to +106
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

비밀번호 필드에 보안 설정을 추가하세요

비밀번호 입력 필드가 평문으로 표시되고 있습니다. 보안을 위해 obscureText를 사용해야 합니다.

               child: TextField(
                 controller: _passwordCtrl,
-                keyboardType: TextInputType.visiblePassword,
+                obscureText: true,
+                keyboardType: TextInputType.text,
                 style: const TextStyle(fontSize: 25, color: AppColors.ongiOrange),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
child: TextField(
controller: _passwordCtrl,
keyboardType: TextInputType.visiblePassword,
style: const TextStyle(fontSize: 25, color: AppColors.ongiOrange),
decoration: InputDecoration(
hintText: 'PASSWORD',
hintStyle:
TextStyle(color: Colors.grey),
contentPadding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 13),
filled: true,
fillColor: Colors.transparent,
enabledBorder: OutlineInputBorder(
borderSide:
const BorderSide(color: AppColors.ongiOrange, width: 1),
borderRadius: BorderRadius.circular(20),
),
focusedBorder: OutlineInputBorder(
borderSide:
const BorderSide(color: AppColors.ongiOrange, width: 1),
borderRadius: BorderRadius.circular(20),
),
),
),
child: TextField(
controller: _passwordCtrl,
+ obscureText: true,
+ keyboardType: TextInputType.text,
style: const TextStyle(fontSize: 25, color: AppColors.ongiOrange),
decoration: InputDecoration(
hintText: 'PASSWORD',
hintStyle: TextStyle(color: Colors.grey),
contentPadding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 13),
filled: true,
fillColor: Colors.transparent,
enabledBorder: OutlineInputBorder(
borderSide:
const BorderSide(color: AppColors.ongiOrange, width: 1),
borderRadius: BorderRadius.circular(20),
),
focusedBorder: OutlineInputBorder(
borderSide:
const BorderSide(color: AppColors.ongiOrange, width: 1),
borderRadius: BorderRadius.circular(20),
),
),
),
🤖 Prompt for AI Agents
In frontend/ongi/lib/screens/login/login_pw_screen.dart between lines 83 and
106, the password input field currently displays the entered text in plain view,
which is a security risk. To fix this, add the property obscureText: true to the
TextField widget to mask the password input and enhance security.

),
Padding(
padding: const EdgeInsets.only(left: 40, right: 40, top: 20),
child: SizedBox(
width: double.infinity,
height: 65,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 10),
backgroundColor: AppColors.ongiOrange,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
onPressed: _isLoading
? null
: () async {
final password = _passwordCtrl.text.trim();
if (password.isEmpty) {
_showErrorSnackBar('비밀번호를 입력해주세요.');
return;
}

setState(() => _isLoading = true);

try {
final prefs = await SharedPreferences.getInstance();
final email = prefs.getString('signup_email') ?? '';
Comment on lines +133 to +134
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

SharedPreferences 키 이름을 명확하게 변경하세요

로그인 화면에서 'signup_email'이라는 키를 사용하는 것은 혼란을 줄 수 있습니다.

                             final prefs = await SharedPreferences.getInstance();
-                            final email = prefs.getString('signup_email') ?? '';
+                            final email = prefs.getString('user_email') ?? '';

또한 이메일이 없을 경우의 처리도 추가하는 것이 좋습니다:

if (email.isEmpty) {
  _showErrorSnackBar('이메일 정보가 없습니다. 다시 로그인해주세요.');
  return;
}
🤖 Prompt for AI Agents
In frontend/ongi/lib/screens/login/login_pw_screen.dart at lines 133-134, rename
the SharedPreferences key from 'signup_email' to a clearer name like
'login_email' to avoid confusion. Additionally, add a check after retrieving the
email to handle the case when it is empty by showing an error snackbar with a
message like '이메일 정보가 없습니다. 다시 로그인해주세요.' and return early to prevent further
processing.


final result = await _loginService.login(
email: email,
password: password,
);

if (!mounted) return;

Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => LoginReadyScreen(
username: result['userInfo']['name'] ?? '사용자',
),
),
);
} catch (e) {
_showErrorSnackBar('로그인에 실패했어요.T_T 비밀번호를 다시 확인해주세요.');
} finally {
setState(() => _isLoading = false);
}
},
child: _isLoading
? const CircularProgressIndicator(color: AppColors.ongiOrange)
: const Text(
'로그인',
style: TextStyle(
fontSize: 33,
fontWeight: FontWeight.w400,
color: Colors.white,
),
),
),
),
),
]
),
),
);
}
}
74 changes: 74 additions & 0 deletions frontend/ongi/lib/screens/login/login_ready_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:ongi/core/app_background.dart';
import 'package:ongi/screens/home_screen.dart';

class LoginReadyScreen extends StatefulWidget {
final String username;
const LoginReadyScreen({required this.username, super.key});

@override
State<LoginReadyScreen> createState() => _LoginReadyScreenState();
}

class _LoginReadyScreenState extends State<LoginReadyScreen> {
Timer? _timer;

@override
void initState() {
super.initState();

_timer = Timer(const Duration(seconds: 2), () {
if (!mounted) return;
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const HomeScreen()),
);
});
}

@override
void dispose() {
_timer?.cancel();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: AppBackground(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 30, right: 30, top: 150),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'반가워요',
style: TextStyle(
fontSize: 60,
fontWeight: FontWeight.w200,
height: 1.2,
color: Colors.white,
),
),
Text(
'${widget.username}님',
style: const TextStyle(
fontSize: 60,
fontWeight: FontWeight.w800,
height: 1.2,
color: Colors.white,
),
),
],
),
),
],
),
),
);
}
}
57 changes: 26 additions & 31 deletions frontend/ongi/lib/screens/signup/familycode_create_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:ongi/core/app_colors.dart';
import 'package:ongi/screens/signup/ready_screen.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:ongi/services/auth_service.dart';
import 'package:ongi/services/code_service.dart';

class FamilycodeCreateScreen extends StatefulWidget {
const FamilycodeCreateScreen({super.key});
Expand All @@ -13,44 +13,38 @@ class FamilycodeCreateScreen extends StatefulWidget {

class _FamilycodeCreateScreenState extends State<FamilycodeCreateScreen> {
final TextEditingController _familycodeCtrl = TextEditingController();
bool _isCodeGenerated = false;

Future<void> _handleSubmit() async {
final familyCode = _familycodeCtrl.text.trim();

if (_isCodeGenerated) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ReadyScreen()),
);
return;
}

try {
final prefs = await SharedPreferences.getInstance();
final email = prefs.getString('signup_email') ?? '';
final password = prefs.getString('signup_password') ?? '';
final name = prefs.getString('signup_username') ?? '';
final isParent = prefs.getString('user_mode') == 'parent';
final familyName = prefs.getString('family_name') ?? '';

final codeService = CodeService();
final response = await codeService.familyCreate(name: familyName);

if (email.isEmpty || password.isEmpty || name.isEmpty) {
final code = response['code'];
if (code != null) {
setState(() {
_familycodeCtrl.text = code;
_isCodeGenerated = true;
});
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('회원가입 정보가 올바르지 않습니다')),
const SnackBar(content: Text('코드 생성에 실패했습니다.')),
);
return;
}

final authService = AuthService();
await authService.register(
email: email,
password: password,
name: name,
isParent: isParent,
);

// 회원가입 성공 후 준비 완료 화면으로 이동
if (!mounted) return;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ReadyScreen(),
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('회원가입 실패: $e')),
SnackBar(content: Text('코드 생성 실패: $e')),
);
}
}
Expand Down Expand Up @@ -116,6 +110,7 @@ class _FamilycodeCreateScreenState extends State<FamilycodeCreateScreen> {
padding: const EdgeInsets.only(left: 40, right: 40, top: 40),
child: TextField(
controller: _familycodeCtrl,
readOnly: true,
keyboardType: TextInputType.text,
style: const TextStyle(
fontSize: 25,
Expand Down Expand Up @@ -159,9 +154,9 @@ class _FamilycodeCreateScreenState extends State<FamilycodeCreateScreen> {
),
),
onPressed: _handleSubmit,
child: const Text(
'생성하기',
style: TextStyle(
child: Text(
_isCodeGenerated ? '함께하기' : '생성하기',
style: const TextStyle(
fontSize: 33,
fontWeight: FontWeight.w400,
color: Colors.white,
Expand Down
Loading
Loading