From 0f450c884f018a472f3c0377adca5a8e3b7924a5 Mon Sep 17 00:00:00 2001 From: eld_master Date: Tue, 17 Dec 2024 00:55:48 +0900 Subject: [PATCH] =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85,=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8,=20id=20pw=20=EC=B0=BE=EA=B8=B0=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flutter_01.png | Bin 0 -> 14475 bytes lib/id_finding_page.dart | 353 ++++++++++++++---- lib/login_page.dart | 103 ++++- lib/pw_finding_page.dart | 129 ++++++- lib/signup_page.dart | 314 +++++++++------- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 167 ++++++++- pubspec.yaml | 3 + 8 files changed, 858 insertions(+), 213 deletions(-) create mode 100644 flutter_01.png diff --git a/flutter_01.png b/flutter_01.png new file mode 100644 index 0000000000000000000000000000000000000000..b1008f16f16560fe9b12501f4f5b8941f0801a75 GIT binary patch literal 14475 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 U8rCbPEXZG#QM{CPLI2PH0_~$>F8}}l literal 0 HcmV?d00001 diff --git a/lib/id_finding_page.dart b/lib/id_finding_page.dart index 47e984e..72d6eda 100644 --- a/lib/id_finding_page.dart +++ b/lib/id_finding_page.dart @@ -1,11 +1,190 @@ import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import 'dart:convert' show utf8; import 'login_page.dart'; import 'pw_finding_page.dart'; import 'signup_page.dart'; -class IdFindingPage extends StatelessWidget { +class IdFindingPage extends StatefulWidget { const IdFindingPage({Key? key}) : super(key: key); + @override + _IdFindingPageState createState() => _IdFindingPageState(); +} + +class _IdFindingPageState extends State { + final TextEditingController nicknameController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); + String nicknameErrorMessage = ''; + String emailErrorMessage = ''; + String foundIdMessage = ''; + String authId = ''; + + Future _findId(String nickname, String email) async { + // 로딩 인디케이터 표시 + showDialog( + context: context, + barrierDismissible: false, // 바깥 클릭으로 닫지 않도록 설정 + builder: (BuildContext context) { + return const Center(child: CircularProgressIndicator()); + }, + ); + + try { + final response = await http.post( + Uri.parse('https://eldsoft.com:8097/user/find/id'), + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode({ + 'nickname': nickname, + 'user_email': email, + }), + ).timeout(const Duration(seconds: 10)); // 10초 타임아웃 설정 + + String responseBody = utf8.decode(response.bodyBytes); + + Navigator.of(context).pop(); // 로딩 인디케이터 닫기 + + if (response.statusCode == 200) { + final Map jsonResponse = jsonDecode(responseBody); + print('ID 찾기 성공: $jsonResponse'); + + // 초기화 + setState(() { + nicknameErrorMessage = ''; + emailErrorMessage = ''; + foundIdMessage = ''; // ID 메시지 초기화 + }); + + if (jsonResponse['response_info']['msg_title'] == '닉네임 확인') { + setState(() { + nicknameErrorMessage = '닉네임을 다시 확인해주세요'; // 닉네임 오류 메시지 설정 + }); + } else if (jsonResponse['response_info']['msg_title'] == '이메일 확인') { + setState(() { + emailErrorMessage = '이메일을 다시 확인해주세요'; // 이메일 오류 메시지 설정 + }); + } else if (jsonResponse['result'] == 'OK') { + // ID 찾기 성공 시 처리 + setState(() { + foundIdMessage = '당신의 ID는 ${jsonResponse['data']['user_id']} 입니다'; // ID 메시지 설정 + authId = jsonResponse['data']['auth']; // auth_id 값 저장 + }); + } else { + _showErrorDialog(jsonResponse['response_info']['msg_title'], jsonResponse['response_info']['msg_content'], 'STAY'); + } + } else { + // 요청이 실패했을 때 모달 창 띄우기 + _showErrorDialog('오류', '요청이 실패했습니다. 관리자에게 문의해주세요.', 'STAY'); + } + } catch (e) { + Navigator.of(context).pop(); // 로딩 인디케이터 닫기 + _showErrorDialog('오류', '요청이 실패했습니다. 관리자에게 문의해주세요.', 'STAY'); + } + } + + Future _findAllId() async { + // ID 전체 찾기 요청 처리 + print('ID 전체 찾기 요청 $authId'); // 요청 시 출력 + + // 로딩 인디케이터 표시 + showDialog( + context: context, + barrierDismissible: false, // 바깥 클릭으로 닫지 않도록 설정 + builder: (BuildContext context) { + return const Center(child: CircularProgressIndicator()); + }, + ); + + try { + final response = await http.post( + Uri.parse('https://eldsoft.com:8097/user/find/id/full'), + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode({ + 'auth': authId, // authId 값 포함 + }), + ).timeout(const Duration(seconds: 10)); // 10초 타임아웃 설정 + + String responseBody = utf8.decode(response.bodyBytes); // UTF-8 디코딩 + + Navigator.of(context).pop(); // 로딩 인디케이터 닫기 + + if (response.statusCode == 200) { + final Map jsonResponse = jsonDecode(responseBody); + print('ID 전체 찾기 성공: $jsonResponse'); + + if (jsonResponse['result'] == 'OK') { + // 성공 시 모달 창 띄우기 + _showSuccessDialog('이메일로 전체 ID를 발송했습니다.'); + } else { + // 실패 시 모달 창 띄우기 + _showErrorDialog(jsonResponse['response_info']['msg_title'], jsonResponse['response_info']['msg_content'], 'STAY'); + } + } else { + // 요청이 실패했을 때 모달 창 띄우기 + _showErrorDialog('오류', '요청이 실패했습니다. 관리자에게 문의해주세요.', 'STAY'); + } + } catch (e) { + Navigator.of(context).pop(); // 로딩 인디케이터 닫기 + _showErrorDialog('오류', '요청이 실패했습니다. 관리자에게 문의해주세요.', 'STAY'); + } + } + + void _showErrorDialog(String title, String content, String action) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: Colors.white, // 모달 배경색을 흰색으로 설정 + title: Text(title, style: const TextStyle(color: Colors.black)), + content: Text(content, style: const TextStyle(color: Colors.black)), + actions: [ + Center( + child: TextButton( + style: TextButton.styleFrom( + backgroundColor: Colors.black, + foregroundColor: Colors.white, // 텍스트 색상 + ), + child: const Text('확인'), + onPressed: () { + Navigator.of(context).pop(); // 모달 닫기 + if (action == 'LOGIN') { + Navigator.of(context).pop(); // 로그인 페이지로 이동 + } + }, + ), + ), + ], + ); + }, + ); + } + + void _showSuccessDialog(String message) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('성공'), + content: Text(message), + actions: [ + TextButton( + child: const Text('확인'), + onPressed: () { + Navigator.of(context).pop(); // 모달 닫기 + Navigator.of(context).pop(); // 로그인 페이지로 이동 + }, + ), + ], + ); + }, + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -16,76 +195,116 @@ class IdFindingPage extends StatelessWidget { ), body: Padding( padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'ID 찾기', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), - const SizedBox(height: 32), - TextField( - decoration: InputDecoration( - labelText: '닉네임', - labelStyle: const TextStyle(color: Colors.black), - border: OutlineInputBorder(), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide(color: Colors.black, width: 2.0), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (foundIdMessage.isEmpty) ...[ + const Text( + 'ID 찾기', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.black, + ), ), - ), - ), - const SizedBox(height: 16), - TextField( - decoration: InputDecoration( - labelText: '이메일', - labelStyle: const TextStyle(color: Colors.black), - border: OutlineInputBorder(), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide(color: Colors.black, width: 2.0), + const SizedBox(height: 32), + TextField( + controller: nicknameController, + decoration: InputDecoration( + labelText: '닉네임', + labelStyle: const TextStyle(color: Colors.black), + border: OutlineInputBorder(), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.black, width: 2.0), + ), + ), ), + if (nicknameErrorMessage.isNotEmpty) // 닉네임 오류 메시지 표시 + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + nicknameErrorMessage, + style: const TextStyle(color: Colors.red), + ), + ), + const SizedBox(height: 16), + TextField( + controller: emailController, + decoration: InputDecoration( + labelText: '이메일', + labelStyle: const TextStyle(color: Colors.black), + border: OutlineInputBorder(), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.black, width: 2.0), + ), + ), + ), + if (emailErrorMessage.isNotEmpty) // 이메일 오류 메시지 표시 + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + emailErrorMessage, + style: const TextStyle(color: Colors.red), + ), + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () { + _findId(nicknameController.text, emailController.text); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.black, + foregroundColor: Colors.white, + ), + child: const Text('ID 찾기'), + ), + ] else ...[ + // ID 찾기 성공 시 메시지 표시 + Text( + foundIdMessage, + style: const TextStyle(fontSize: 20, color: Colors.black), + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () { + _findAllId(); // ID 전체 찾기 버튼 클릭 시 처리 + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.black, + foregroundColor: Colors.white, + ), + child: const Text('ID 전체 찾기'), + ), + ], + const SizedBox(height: 16), + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('로그인', style: TextStyle(color: Colors.black)), ), - ), - const SizedBox(height: 16), - ElevatedButton( - onPressed: () { - // ID 찾기 로직 추가 - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.black, - foregroundColor: Colors.white, + TextButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const PwFindingPage()), + ); + }, + child: const Text('PW 찾기', style: TextStyle(color: Colors.black)), ), - child: const Text('ID 찾기'), - ), - const SizedBox(height: 16), - TextButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const PwFindingPage()), - ); - }, - child: const Text('PW 찾기', style: TextStyle(color: Colors.black)), - ), - TextButton( - onPressed: () { - Navigator.pop(context); // 로그인 페이지로 이동 - }, - child: const Text('로그인', style: TextStyle(color: Colors.black)), - ), - TextButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const SignUpPage()), - ); - }, - child: const Text('회원가입', style: TextStyle(color: Colors.black)), - ), - ], + TextButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const SignUpPage()), + ); + }, + child: const Text('회원가입', style: TextStyle(color: Colors.black)), + ), + ], + ), ), ), ); diff --git a/lib/login_page.dart b/lib/login_page.dart index 5177935..199e43f 100644 --- a/lib/login_page.dart +++ b/lib/login_page.dart @@ -1,4 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import 'dart:convert' show utf8; +import 'package:crypto/crypto.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'id_finding_page.dart'; import 'pw_finding_page.dart'; import 'signup_page.dart'; @@ -11,7 +16,80 @@ class LoginPage extends StatefulWidget { } class _LoginPageState extends State { - bool _isChecked = false; + final TextEditingController idController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + bool autoLogin = false; + String loginErrorMessage = ''; + + Future _login() async { + String id = idController.text; + String password = passwordController.text; + String autoLoginStatus = autoLogin ? 'Y' : 'N'; + + var bytes = utf8.encode(password); + var digest = sha256.convert(bytes); + + try { + final response = await http.post( + Uri.parse('https://eldsoft.com:8097/user/login'), + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode({ + 'user_id': id, + 'user_pw': digest.toString(), + }), + ).timeout(const Duration(seconds: 10)); + + String responseBody = utf8.decode(response.bodyBytes); + + if (response.statusCode == 200) { + final Map jsonResponse = jsonDecode(responseBody); + + if (jsonResponse['result'] == 'OK') { + print('로그인 성공'); + SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setString('auth_token', jsonResponse['auth']['token']); + await prefs.setBool('auto_login', autoLogin); + } else if (jsonResponse['response_info']['msg_title'] == '로그인 실패') { + setState(() { + loginErrorMessage = '회원정보를 다시 확인해주세요.'; + }); + } + } else { + _showDialog('오류', '로그인에 실패했습니다. 관리자에게 문의해주세요.'); + } + } catch (e) { + _showDialog('오류', '요청이 실패했습니다. 관리자에게 문의해주세요.'); + } + } + + void _showDialog(String title, String content) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: Colors.white, + title: Text(title, style: const TextStyle(color: Colors.black)), + content: Text(content, style: const TextStyle(color: Colors.black)), + actions: [ + Center( + child: TextButton( + style: TextButton.styleFrom( + backgroundColor: Colors.black, + foregroundColor: Colors.white, + ), + child: const Text('확인'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + ], + ); + }, + ); + } @override Widget build(BuildContext context) { @@ -36,6 +114,7 @@ class _LoginPageState extends State { ), const SizedBox(height: 32), TextField( + controller: idController, decoration: InputDecoration( labelText: 'ID', labelStyle: const TextStyle(color: Colors.black), @@ -47,6 +126,8 @@ class _LoginPageState extends State { ), const SizedBox(height: 16), TextField( + controller: passwordController, + obscureText: true, decoration: InputDecoration( labelText: 'PW', labelStyle: const TextStyle(color: Colors.black), @@ -55,15 +136,23 @@ class _LoginPageState extends State { borderSide: const BorderSide(color: Colors.black, width: 2.0), ), ), - obscureText: true, ), + if (loginErrorMessage.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + loginErrorMessage, + style: const TextStyle(color: Colors.red), + ), + ), Row( + mainAxisAlignment: MainAxisAlignment.start, children: [ Checkbox( - value: _isChecked, - onChanged: (value) { + value: autoLogin, + onChanged: (bool? value) { setState(() { - _isChecked = value ?? false; + autoLogin = value ?? false; }); }, ), @@ -72,9 +161,7 @@ class _LoginPageState extends State { ), const SizedBox(height: 16), ElevatedButton( - onPressed: () { - // 로그인 로직 추가 - }, + onPressed: _login, style: ElevatedButton.styleFrom( backgroundColor: Colors.black, foregroundColor: Colors.white, diff --git a/lib/pw_finding_page.dart b/lib/pw_finding_page.dart index 0a84aa3..1a0fa96 100644 --- a/lib/pw_finding_page.dart +++ b/lib/pw_finding_page.dart @@ -1,11 +1,118 @@ import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import 'dart:convert' show utf8; import 'login_page.dart'; // 로그인 페이지 임포트 추가 import 'signup_page.dart'; // 회원가입 페이지 임포트 추가 import 'id_finding_page.dart'; // ID 찾기 페이지 임포트 추가 -class PwFindingPage extends StatelessWidget { +class PwFindingPage extends StatefulWidget { const PwFindingPage({Key? key}) : super(key: key); + @override + _PwFindingPageState createState() => _PwFindingPageState(); +} + +class _PwFindingPageState extends State { + final TextEditingController idController = TextEditingController(); // ID 입력 컨트롤러 + final TextEditingController emailController = TextEditingController(); // 이메일 입력 컨트롤러 + String emailErrorMessage = ''; // 이메일 오류 메시지 + String idErrorMessage = ''; // ID 오류 메시지 + + Future _findPassword(String id, String email) async { + // PW 찾기 요청 처리 + print('PW 찾기 요청: ID: $id, 이메일: $email'); // 요청 시 출력 + + // 로딩 인디케이터 표시 + showDialog( + context: context, + barrierDismissible: false, // 바깥 클릭으로 닫지 않도록 설정 + builder: (BuildContext context) { + return const Center(child: CircularProgressIndicator()); + }, + ); + + try { + final response = await http.post( + Uri.parse('https://eldsoft.com:8097/user/find/password'), + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode({ + 'user_id': id, + 'user_email': email, + }), + ).timeout(const Duration(seconds: 10)); // 10초 타임아웃 설정 + + String responseBody = utf8.decode(response.bodyBytes); // UTF-8 디코딩 + + Navigator.of(context).pop(); // 로딩 인디케이터 닫기 + + if (response.statusCode == 200) { + final Map jsonResponse = jsonDecode(responseBody); + print('PW 찾기 성공: $jsonResponse'); + + // 추기화 + setState(() { + emailErrorMessage = ''; + idErrorMessage = ''; + }); + + if (jsonResponse['response_info']['msg_title'] == '아이디 확인') { + setState(() { + idErrorMessage = '아이디를 다시 확인해주세요'; // ID 오류 메시지 설정 + }); + } else if (jsonResponse['response_info']['msg_title'] == '이메일 확인') { + setState(() { + emailErrorMessage = '이메일을 다시 확인해주세요'; // 이메일 오류 메시지 설정 + }); + } else if (jsonResponse['result'] == 'OK') { + // 성공 시 모달 창 띄우기 + _showDialog('비밀번호 찾기 안내', '임시 비밀번호가 입력하신 이메일로 발송되었습니다.', 'LOGIN'); + } else { + // 실패 시 모달 창 띄우기 + _showDialog(jsonResponse['response_info']['msg_title'], jsonResponse['response_info']['msg_content'], 'STAY'); + } + } else { + // 요청이 실패했을 때 모달 창 띄우기 + _showDialog('오류', '요청이 실패했습니다. 관리자에게 문의해주세요.', 'STAY'); + } + } catch (e) { + Navigator.of(context).pop(); // 로딩 인디케이터 닫기 + _showDialog('오류', '요청이 실패했습니다. 관리자에게 문의해주세요.', 'STAY'); + } + } + + void _showDialog(String title, String content, String action) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: Colors.white, // 모달 배경색을 흰색으로 설정 + title: Text(title, style: const TextStyle(color: Colors.black)), + content: Text(content, style: const TextStyle(color: Colors.black)), + actions: [ + Center( + child: TextButton( + style: TextButton.styleFrom( + backgroundColor: Colors.black, + foregroundColor: Colors.white, // 텍스트 색상 + ), + child: const Text('확인'), + onPressed: () { + Navigator.of(context).pop(); // 모달 닫기 + if (action == 'LOGIN') { + Navigator.of(context).pop(); // 로그인 페이지로 이동 + } + }, + ), + ), + ], + ); + }, + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -29,6 +136,7 @@ class PwFindingPage extends StatelessWidget { ), const SizedBox(height: 32), TextField( + controller: idController, // ID 입력 필드 decoration: InputDecoration( labelText: 'ID', labelStyle: const TextStyle(color: Colors.black), @@ -38,8 +146,17 @@ class PwFindingPage extends StatelessWidget { ), ), ), + if (idErrorMessage.isNotEmpty) // ID 오류 메시지 표시 + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + idErrorMessage, + style: const TextStyle(color: Colors.red), + ), + ), const SizedBox(height: 16), TextField( + controller: emailController, // 이메일 입력 필드 decoration: InputDecoration( labelText: '이메일', labelStyle: const TextStyle(color: Colors.black), @@ -49,10 +166,18 @@ class PwFindingPage extends StatelessWidget { ), ), ), + if (emailErrorMessage.isNotEmpty) // 이메일 오류 메시지 표시 + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + emailErrorMessage, + style: const TextStyle(color: Colors.red), + ), + ), const SizedBox(height: 16), ElevatedButton( onPressed: () { - // PW 찾기 로직 추가 + _findPassword(idController.text, emailController.text); // PW 찾기 요청 }, style: ElevatedButton.styleFrom( backgroundColor: Colors.black, diff --git a/lib/signup_page.dart b/lib/signup_page.dart index 50d4d39..3b360b3 100644 --- a/lib/signup_page.dart +++ b/lib/signup_page.dart @@ -1,5 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; // http 패키지 임포트 +import 'dart:convert'; // JSON 변환을 위해 임포트 import 'login_page.dart'; // 로그인 페이지 임포트 추가 +import 'dart:convert' show utf8; // UTF-8 디코딩을 위해 임포트 +import 'package:crypto/crypto.dart'; // crypto 패키지 임포트 class SignUpPage extends StatefulWidget { const SignUpPage({Key? key}) : super(key: key); @@ -9,27 +13,170 @@ class SignUpPage extends StatefulWidget { } class _SignUpPageState extends State { + // 상태 변수 bool _isAgreed = false; // 개인정보 수집 동의 체크박스 상태 - String _username = ''; // 아이디 입력값 - String? _usernameError; // 아이디 오류 메시지 + String _username = '', _password = '', _confirmPassword = '', _nickname = '', _email = ''; + String _department = '', _introduceMyself = ''; // 소속 및 자기소개 + String? _usernameError, _passwordError, _confirmPasswordError, _nicknameError, _emailError; - // 아이디 패턴 검증 - bool _isUsernameValid(String username) { - final RegExp regex = RegExp(r'^(?![0-9])[A-Za-z0-9]{6,20}$'); - return regex.hasMatch(username); - } + // 유효성 검사 + bool _isUsernameValid(String username) => RegExp(r'^(?![0-9])[A-Za-z0-9]{6,20}$').hasMatch(username); + bool _isPasswordValidPattern(String password) => RegExp(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,20}$').hasMatch(password); + bool _isEmailValid(String email) => RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email); + bool _isNicknameValid(String nickname) => RegExp(r'^[A-Za-z가-힣0-9]{2,20}$').hasMatch(nickname); - void _validateUsername(String value) { + // 입력값 검증 + void _validateInput(String label) { setState(() { - _username = value; - if (_isUsernameValid(value)) { - _usernameError = null; // 유효한 경우 오류 메시지 제거 - } else { - _usernameError = '* 아이디는 6~20자 영문 대소문자와 숫자 조합이어야 하며, 숫자로 시작할 수 없습니다.'; + if (label == '아이디') { + _usernameError = _isUsernameValid(_username) ? null : '아이디는 6~20자 영문 대소문자와 숫자 조합이어야 하며, 숫자로 시작할 수 없습니다.'; + } else if (label == '비밀번호') { + _passwordError = _isPasswordValidPattern(_password) ? null : '비밀번호는 8~20자 영문과 숫자가 반드시 포함된 조합이어야 합니다.'; + } else if (label == '비밀번호 확인') { + _confirmPasswordError = _password == _confirmPassword ? null : '비밀번호가 일치하지 않습니다.'; + } else if (label == '닉네임') { + _nicknameError = _isNicknameValid(_nickname) ? null : '* 닉네임은 2~20자 영문, 한글, 숫자만 사용할 수 있습니다.'; + } else if (label == '이메일') { + _emailError = _isEmailValid(_email) ? null : (_email.isNotEmpty ? '올바른 이메일 형식을 입력해주세요.' : null); } }); } + // 회원가입 요청 + Future _signUp() async { + final url = 'https://eldsoft.com:8097/user/signup'; + + // 체크박스 상태에 따라 mandatory_terms_yn 값 설정 + final mandatoryTermsYn = _isAgreed ? 'Y' : 'N'; + + // 비밀번호를 SHA-256으로 해싱 + final hashedPassword = _hashPassword(_password); + + final body = { + "user_id": _username, + "user_pw": hashedPassword, // 해싱된 비밀번호 사용 + "nickname": _nickname, + "user_email": _email, + "department": _department, + "introduce_myself": _introduceMyself, + "mandatory_terms_yn": mandatoryTermsYn // 체크박스 상태에 따라 값 설정 + }; + + try { + final response = await http.post( + Uri.parse(url), + headers: {'Content-Type': 'application/json'}, + body: json.encode(body), + ); + + // 응답 body를 UTF-8로 디코딩하여 변수에 저장 + final resBody = json.decode(utf8.decode(response.bodyBytes)); + + if (response.statusCode == 200) { + // result가 OK이어야만 성공여부 판단 가능 + print(resBody); + if (resBody['result'] == 'OK') { + if (resBody['response_info']['msg_type'] == 'OK') { + _showDialog('회원가입 성공', '회원가입이 완료되었습니다.'); + } else { + _showDialog('회원가입 실패', '${resBody['response_info']['msg_content']}'); + } + } else { + _showDialog('회원가입 실패', '${resBody['response_info']['msg_content']}'); + } + } else { + final errorData = json.decode(response.body); + _showDialog('회원가입 실패', errorData['message'] ?? '회원가입 실패'); + } + } catch (error) { + _showDialog('네트워크 오류', '네트워크 오류: $error'); + } + } + + // 모밀번호 해싱 함수 + String _hashPassword(String password) { + final bytes = utf8.encode(password); // 비밀번호를 바이트로 변환 + final digest = sha256.convert(bytes); // SHA-256 해싱 + return digest.toString(); // 해싱된 비밀번호를 문자열로 반환 + } + + // 모달 창 표시 + void _showDialog(String title, String message) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: Colors.white, // 배경색 + title: Text( + title, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.black, // 제목 색상 + ), + ), + content: Column( + mainAxisSize: MainAxisSize.min, // 내용 크기에 맞게 조정 + children: [ + Text( + message, + style: const TextStyle( + fontSize: 16, + color: Colors.black, // 내용 색상 + ), + ), + const SizedBox(height: 20), // 여백 추가 + TextButton( + onPressed: () { + Navigator.of(context).pop(); // 모달 닫기 + if (title == '회원가입 성공') { + Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => LoginPage())); // 로그인 페이지로 이동 + } + // '회원가입 성공'이 아닐 경우 아무 동작도 하지 않음 + }, + style: TextButton.styleFrom( + foregroundColor: Colors.white, // 버튼 텍스트 색상 + backgroundColor: Colors.black, // 버튼 배경색 + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), // 버튼 패딩 + ), + child: const Text('확인'), + ), + ], + ), + ); + }, + ); + } + + // 입력 필드 위젯 + Widget _buildTextField(String label, Function(String) onChanged, {bool obscureText = false, String? errorText}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + onChanged: (value) { + onChanged(value); + _validateInput(label); // label에 따라 유효성 검사 수행 + }, + obscureText: obscureText, + decoration: InputDecoration( + labelText: label, + labelStyle: const TextStyle(color: Colors.black), + border: OutlineInputBorder(), + focusedBorder: OutlineInputBorder(borderSide: const BorderSide(color: Colors.black, width: 2.0)), + errorStyle: const TextStyle(color: Colors.red, fontSize: 12), + ), + ), + if (errorText != null) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text(errorText, style: const TextStyle(color: Colors.red, fontSize: 12)), + ), + const SizedBox(height: 16), + ], + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -39,136 +186,36 @@ class _SignUpPageState extends State { backgroundColor: Colors.black, leading: IconButton( icon: const Icon(Icons.chevron_left, color: Colors.white), - onPressed: () { - Navigator.pop(context); // 로그인 페이지로 이동 - }, + onPressed: () => Navigator.pop(context), ), ), body: Padding( padding: const EdgeInsets.all(16.0), - child: SingleChildScrollView( // 스크롤 가능하게 설정 + child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ - const Text( - '회원가입', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), + const Text('회원가입', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black)), const SizedBox(height: 32), - TextField( - onChanged: _validateUsername, // 아이디 입력 시 검증 - decoration: InputDecoration( - labelText: '아이디', - labelStyle: const TextStyle(color: Colors.black), - border: OutlineInputBorder(), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide(color: Colors.black, width: 2.0), - ), - ), - ), - if (_usernameError != null) // 오류 메시지 표시 - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - _usernameError!, - style: const TextStyle(color: Colors.red, fontSize: 12), // 폰트 크기 줄임 - ), - ), + _buildTextField('아이디', (value) => _username = value, errorText: _usernameError), + _buildTextField('비밀번호', (value) => _password = value, obscureText: true, errorText: _passwordError), + _buildTextField('비밀번호 확인', (value) => _confirmPassword = value, obscureText: true, errorText: _confirmPasswordError), + _buildTextField('닉네임', (value) => _nickname = value, errorText: _nicknameError), + _buildTextField('이메일', (value) => _email = value, errorText: _emailError), + _buildTextField('소속(선택사항)', (value) => _department = value), + _buildTextField('자기소개(선택사항)', (value) => _introduceMyself = value), const SizedBox(height: 16), - TextField( - decoration: InputDecoration( - labelText: '비밀번호', - labelStyle: const TextStyle(color: Colors.black), - border: OutlineInputBorder(), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide(color: Colors.black, width: 2.0), - ), - ), - obscureText: true, - ), - const SizedBox(height: 16), - TextField( - decoration: InputDecoration( - labelText: '비밀번호 확인', - labelStyle: const TextStyle(color: Colors.black), - border: OutlineInputBorder(), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide(color: Colors.black, width: 2.0), - ), - ), - obscureText: true, - ), - const SizedBox(height: 16), - TextField( - decoration: InputDecoration( - labelText: '닉네임', - labelStyle: const TextStyle(color: Colors.black), - border: OutlineInputBorder(), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide(color: Colors.black, width: 2.0), - ), - ), - ), - const SizedBox(height: 16), - TextField( - decoration: InputDecoration( - labelText: '이메일', - labelStyle: const TextStyle(color: Colors.black), - border: OutlineInputBorder(), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide(color: Colors.black, width: 2.0), - ), - ), - ), - const SizedBox(height: 16), - TextField( - decoration: InputDecoration( - labelText: '소속', - labelStyle: const TextStyle(color: Colors.black), - border: OutlineInputBorder(), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide(color: Colors.black, width: 2.0), - ), - ), - ), - const SizedBox(height: 16), - TextField( - decoration: InputDecoration( - labelText: '자기소개', - labelStyle: const TextStyle(color: Colors.black), - border: OutlineInputBorder(), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide(color: Colors.black, width: 2.0), - ), - ), - maxLines: 3, // 여러 줄 입력 가능 - ), - const SizedBox(height: 16), - const Text( - '개인정보 수집 및 이용 동의서', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), + const Text('개인정보 수집 및 이용 동의서', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black)), const SizedBox(height: 8), Container( - height: 200, // 스크롤 가능한 영역의 높이 설정 - decoration: BoxDecoration( - border: Border.all(color: Colors.black.withOpacity(0.5)), // 부드러운 테두리 - borderRadius: BorderRadius.circular(8), // 모서리 둥글게 - ), - child: Scrollbar( // 스크롤바 추가 - thickness: 5, // 스크롤바 두께 - radius: Radius.circular(5), // 스크롤바 둥글게 + height: 200, + decoration: BoxDecoration(border: Border.all(color: Colors.black.withOpacity(0.5)), borderRadius: BorderRadius.circular(8)), + child: Scrollbar( + thickness: 5, + radius: const Radius.circular(5), child: SingleChildScrollView( child: const Padding( - padding: EdgeInsets.all(15.0), // 여백 추가 + padding: EdgeInsets.all(15.0), child: Text( '올스코어(이하 "회사"라 합니다)는 이용자의 개인정보를 중요시하며, ' '「개인정보 보호법」 등 관련 법령을 준수하고 있습니다. ' @@ -183,7 +230,7 @@ class _SignUpPageState extends State { '부정 이용 방지 및 비인가 사용 방지\n' '서비스 이용에 따른 문의 사항 처리\n' '서비스 제공\n' - '게임 방 생성 및 참여 등 기본 서비스 제공\n' + '게임 생성 및 참여 등 기본 서비스 제공\n' '통계 및 순위 제공 등 부가 서비스 제공\n' '고객 지원 및 공지사항 전달\n' '서비스 관련 중요한 공지사항 전달\n' @@ -237,10 +284,7 @@ class _SignUpPageState extends State { ), const SizedBox(height: 16), ElevatedButton( - onPressed: () { - // 회원가입 로직 추가 - Navigator.pop(context); // 로그인 페이지로 이동 - }, + onPressed: _signUp, // 회원가입 로직 추가 style: ElevatedButton.styleFrom( backgroundColor: Colors.black, foregroundColor: Colors.white, diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..724bb2a 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 538c560..5bd61fa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.0" + crypto: + dependency: "direct dev" + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" cupertino_icons: dependency: "direct main" description: @@ -57,6 +65,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -75,6 +99,27 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: "direct dev" + description: + name: http + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + url: "https://pub.dev" + source: hosted + version: "1.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360" + url: "https://pub.dev" + source: hosted + version: "4.1.1" leak_tracker: dependency: transitive description: @@ -139,6 +184,102 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + shared_preferences: + dependency: "direct dev" + description: + name: shared_preferences + sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sky_engine: dependency: transitive description: flutter @@ -192,6 +333,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.3" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -208,6 +357,22 @@ packages: url: "https://pub.dev" source: hosted version: "14.3.0" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" sdks: dart: ">=3.6.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index a60365f..73f06c3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,6 +38,9 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + http: ^1.2.2 + crypto: ^3.0.1 + shared_preferences: ^2.0.6 # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is