1
0
forked from baron/baron-sso

린트 적용

This commit is contained in:
2026-02-12 10:39:47 +09:00
parent 21b9594de5
commit 74884f6616
65 changed files with 26389 additions and 1583 deletions

View File

@@ -164,30 +164,39 @@ class _SignupScreenState extends State<SignupScreen> {
final email = _emailController.text.trim();
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(email)) {
setState(() => _emailError = tr(
'msg.userfront.signup.email.invalid',
fallback: '유효한 이메일 형식이 아닙니다.',
));
setState(
() => _emailError = tr(
'msg.userfront.signup.email.invalid',
fallback: '유효한 이메일 형식이 아닙니다.',
),
);
return;
}
setState(() { _isLoading = true; _emailError = null; });
setState(() {
_isLoading = true;
_emailError = null;
});
try {
final available = await AuthProxyService.checkEmailAvailability(email);
if (!available) {
setState(() => _emailError = tr(
'msg.userfront.signup.email.duplicate',
fallback: '이미 가입된 이메일입니다.',
));
setState(
() => _emailError = tr(
'msg.userfront.signup.email.duplicate',
fallback: '이미 가입된 이메일입니다.',
),
);
return;
}
await AuthProxyService.sendSignupCode(email, 'email');
_startTimer('email');
} catch (e) {
setState(() => _emailError = tr(
'msg.userfront.signup.email.send_failed',
fallback: '발송 실패: {{error}}',
params: {'error': e.toString()},
));
setState(
() => _emailError = tr(
'msg.userfront.signup.email.send_failed',
fallback: '발송 실패: {{error}}',
params: {'error': e.toString()},
),
);
} finally {
setState(() => _isLoading = false);
}
@@ -197,7 +206,11 @@ class _SignupScreenState extends State<SignupScreen> {
final code = _emailCodeController.text.trim();
if (code.length != 6) return;
try {
final success = await AuthProxyService.verifySignupCode(_emailController.text.trim(), 'email', code);
final success = await AuthProxyService.verifySignupCode(
_emailController.text.trim(),
'email',
code,
);
if (success) {
setState(() {
_isEmailVerified = true;
@@ -206,33 +219,42 @@ class _SignupScreenState extends State<SignupScreen> {
_emailError = null;
});
} else {
setState(() => _emailError = tr(
'msg.userfront.signup.email.code_mismatch',
fallback: '인증코드가 일치하지 않습니다.',
));
setState(
() => _emailError = tr(
'msg.userfront.signup.email.code_mismatch',
fallback: '인증코드가 일치하지 않습니다.',
),
);
}
} catch (e) {
setState(() => _emailError = tr(
'msg.userfront.signup.email.verify_failed',
fallback: '인증 실패: {{error}}',
params: {'error': e.toString()},
));
setState(
() => _emailError = tr(
'msg.userfront.signup.email.verify_failed',
fallback: '인증 실패: {{error}}',
params: {'error': e.toString()},
),
);
}
}
Future<void> _sendPhoneCode() async {
final phone = _phoneController.text.trim();
if (phone.isEmpty) return;
setState(() { _isLoading = true; _phoneError = null; });
setState(() {
_isLoading = true;
_phoneError = null;
});
try {
await AuthProxyService.sendSignupCode(phone, 'phone');
_startTimer('phone');
} catch (e) {
setState(() => _phoneError = tr(
'msg.userfront.signup.phone.send_failed',
fallback: '발송 실패: {{error}}',
params: {'error': e.toString()},
));
setState(
() => _phoneError = tr(
'msg.userfront.signup.phone.send_failed',
fallback: '발송 실패: {{error}}',
params: {'error': e.toString()},
),
);
} finally {
setState(() => _isLoading = false);
}
@@ -242,7 +264,11 @@ class _SignupScreenState extends State<SignupScreen> {
final code = _phoneCodeController.text.trim();
if (code.length != 6) return;
try {
final success = await AuthProxyService.verifySignupCode(_phoneController.text.trim(), 'phone', code);
final success = await AuthProxyService.verifySignupCode(
_phoneController.text.trim(),
'phone',
code,
);
if (success) {
setState(() {
_isPhoneVerified = true;
@@ -251,26 +277,32 @@ class _SignupScreenState extends State<SignupScreen> {
_phoneError = null;
});
} else {
setState(() => _phoneError = tr(
'msg.userfront.signup.phone.code_mismatch',
fallback: '인증코드가 일치하지 않습니다.',
));
setState(
() => _phoneError = tr(
'msg.userfront.signup.phone.code_mismatch',
fallback: '인증코드가 일치하지 않습니다.',
),
);
}
} catch (e) {
setState(() => _phoneError = tr(
'msg.userfront.signup.phone.verify_failed',
fallback: '인증 실패: {{error}}',
params: {'error': e.toString()},
));
setState(
() => _phoneError = tr(
'msg.userfront.signup.phone.verify_failed',
fallback: '인증 실패: {{error}}',
params: {'error': e.toString()},
),
);
}
}
Future<void> _handleSignup() async {
if (_passwordController.text != _confirmPasswordController.text) {
setState(() => _confirmPasswordError = tr(
'msg.userfront.signup.password.mismatch',
fallback: '비밀번호가 일치하지 않습니다.',
));
setState(
() => _confirmPasswordError = tr(
'msg.userfront.signup.password.mismatch',
fallback: '비밀번호가 일치하지 않습니다.',
),
);
return;
}
if (!_formKey.currentState!.validate()) return;
@@ -288,7 +320,9 @@ class _SignupScreenState extends State<SignupScreen> {
phone: _phoneController.text.trim(),
affiliationType: _affiliationType,
companyCode: _affiliationType == 'AFFILIATE' ? _companyCode : null,
department: _deptController.text.trim().isEmpty ? (_affiliationType == 'GENERAL' ? 'External' : '') : _deptController.text.trim(),
department: _deptController.text.trim().isEmpty
? (_affiliationType == 'GENERAL' ? 'External' : '')
: _deptController.text.trim(),
termsAccepted: true,
);
if (mounted) _showSuccessDialog();
@@ -394,11 +428,28 @@ class _SignupScreenState extends State<SignupScreen> {
children: [
CircleAvatar(
radius: 12,
backgroundColor: isDone ? Colors.green : (isCurrent ? Colors.black : Colors.grey[300]),
child: isDone ? const Icon(Icons.check, size: 14, color: Colors.white) : Text('$step', style: TextStyle(color: isCurrent ? Colors.white : Colors.black54, fontSize: 10)),
backgroundColor: isDone
? Colors.green
: (isCurrent ? Colors.black : Colors.grey[300]),
child: isDone
? const Icon(Icons.check, size: 14, color: Colors.white)
: Text(
'$step',
style: TextStyle(
color: isCurrent ? Colors.white : Colors.black54,
fontSize: 10,
),
),
),
const SizedBox(height: 4),
Text(label, style: TextStyle(fontSize: 9, color: isCurrent ? Colors.black : Colors.grey, fontWeight: isCurrent ? FontWeight.bold : FontWeight.normal)),
Text(
label,
style: TextStyle(
fontSize: 9,
color: isCurrent ? Colors.black : Colors.grey,
fontWeight: isCurrent ? FontWeight.bold : FontWeight.normal,
),
),
],
);
}
@@ -438,10 +489,7 @@ class _SignupScreenState extends State<SignupScreen> {
),
child: CheckboxListTile(
title: Text(
tr(
'ui.userfront.signup.agreement.all',
fallback: '모두 동의합니다',
),
tr('ui.userfront.signup.agreement.all', fallback: '모두 동의합니다'),
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
value: _termsAccepted && _privacyAccepted,
@@ -488,8 +536,10 @@ class _SignupScreenState extends State<SignupScreen> {
return Column(
children: [
CheckboxListTile(
title: Text(title,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
title: Text(
title,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
),
value: value,
onChanged: onChanged,
controlAffinity: ListTileControlAffinity.leading,
@@ -508,7 +558,11 @@ class _SignupScreenState extends State<SignupScreen> {
child: SingleChildScrollView(
child: Text(
content,
style: const TextStyle(fontSize: 12, color: Colors.grey, height: 1.5),
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
height: 1.5,
),
),
),
),
@@ -517,8 +571,8 @@ class _SignupScreenState extends State<SignupScreen> {
}
static String get _tosText => tr(
'msg.userfront.signup.tos_full',
fallback: """
'msg.userfront.signup.tos_full',
fallback: """
바론 소프트웨어 이용약관
제1장 총칙
@@ -589,11 +643,11 @@ class _SignupScreenState extends State<SignupScreen> {
부칙
본 약관은 2024년 10월 1일부터 시행됩니다.
""",
);
);
static String get _privacyText => tr(
'msg.userfront.signup.privacy_full',
fallback: """
'msg.userfront.signup.privacy_full',
fallback: """
개인정보 수집 및 이용 동의
바론서비스 개인정보처리방침
@@ -702,7 +756,7 @@ class _SignupScreenState extends State<SignupScreen> {
제8조 (기타)
본 방침에 명시되지 않은 사항은 회사의 내부 방침과 관련 법령에 따릅니다.
""",
);
);
Widget _buildStepAuth() {
return Column(
@@ -719,7 +773,10 @@ class _SignupScreenState extends State<SignupScreen> {
// 가족사 이메일 안내 문구
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(color: Colors.blue[50], borderRadius: BorderRadius.circular(6)),
decoration: BoxDecoration(
color: Colors.blue[50],
borderRadius: BorderRadius.circular(6),
),
child: Row(
children: [
const Icon(Icons.info_outline, size: 16, color: Colors.blue),
@@ -730,7 +787,11 @@ class _SignupScreenState extends State<SignupScreen> {
'msg.userfront.signup.auth.affiliate_notice',
fallback: '가족사 회원의 경우 반드시 회사 공식 이메일을 입력해주세요.',
),
style: const TextStyle(fontSize: 12, color: Colors.blue, fontWeight: FontWeight.w500),
style: const TextStyle(
fontSize: 12,
color: Colors.blue,
fontWeight: FontWeight.w500,
),
),
),
],
@@ -753,7 +814,7 @@ class _SignupScreenState extends State<SignupScreen> {
'ui.userfront.signup.auth.email.label',
fallback: '이메일 주소',
),
border: const OutlineInputBorder(),
border: const OutlineInputBorder(),
errorText: _emailError,
hintText: 'example@hanmaceng.co.kr',
),
@@ -764,8 +825,14 @@ class _SignupScreenState extends State<SignupScreen> {
SizedBox(
height: 55,
child: ElevatedButton(
onPressed: (_isEmailVerified || _isLoading) ? null : _sendEmailCode,
style: ElevatedButton.styleFrom(backgroundColor: Colors.grey[100], foregroundColor: Colors.black, elevation: 0),
onPressed: (_isEmailVerified || _isLoading)
? null
: _sendEmailCode,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[100],
foregroundColor: Colors.black,
elevation: 0,
),
child: Text(
_emailSeconds > 0
? tr('ui.common.resend', fallback: '재발송')
@@ -791,8 +858,13 @@ class _SignupScreenState extends State<SignupScreen> {
border: const OutlineInputBorder(),
),
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(6)],
onChanged: (val) { if(val.length == 6) _verifyEmailCode(); },
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(6),
],
onChanged: (val) {
if (val.length == 6) _verifyEmailCode();
},
),
],
if (_isEmailVerified)
@@ -837,8 +909,14 @@ class _SignupScreenState extends State<SignupScreen> {
SizedBox(
height: 55,
child: ElevatedButton(
onPressed: (_isPhoneVerified || _isLoading) ? null : _sendPhoneCode,
style: ElevatedButton.styleFrom(backgroundColor: Colors.grey[100], foregroundColor: Colors.black, elevation: 0),
onPressed: (_isPhoneVerified || _isLoading)
? null
: _sendPhoneCode,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[100],
foregroundColor: Colors.black,
elevation: 0,
),
child: Text(
_phoneSeconds > 0
? tr('ui.common.resend', fallback: '재발송')
@@ -864,8 +942,13 @@ class _SignupScreenState extends State<SignupScreen> {
border: const OutlineInputBorder(),
),
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(6)],
onChanged: (val) { if(val.length == 6) _verifyPhoneCode(); },
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(6),
],
onChanged: (val) {
if (val.length == 6) _verifyPhoneCode();
},
),
],
if (_isPhoneVerified)
@@ -903,10 +986,7 @@ class _SignupScreenState extends State<SignupScreen> {
controller: _nameController,
onChanged: (_) => setState(() {}),
decoration: InputDecoration(
labelText: tr(
'ui.userfront.signup.profile.name',
fallback: '이름',
),
labelText: tr('ui.userfront.signup.profile.name', fallback: '이름'),
border: const OutlineInputBorder(),
),
),
@@ -936,19 +1016,13 @@ class _SignupScreenState extends State<SignupScreen> {
DropdownMenuItem(
value: 'GENERAL',
child: Text(
tr(
'domain.affiliation.general',
fallback: '일반 사용자',
),
tr('domain.affiliation.general', fallback: '일반 사용자'),
),
),
DropdownMenuItem(
value: 'AFFILIATE',
child: Text(
tr(
'domain.affiliation.affiliate',
fallback: '가족사 임직원',
),
tr('domain.affiliation.affiliate', fallback: '가족사 임직원'),
),
),
],
@@ -985,39 +1059,27 @@ class _SignupScreenState extends State<SignupScreen> {
items: [
DropdownMenuItem(
value: 'HANMAC',
child: Text(
tr('domain.company.hanmac', fallback: '한맥'),
),
child: Text(tr('domain.company.hanmac', fallback: '한맥')),
),
DropdownMenuItem(
value: 'SAMAN',
child: Text(
tr('domain.company.saman', fallback: '삼안'),
),
child: Text(tr('domain.company.saman', fallback: '삼안')),
),
DropdownMenuItem(
value: 'PTC',
child: Text(
tr('domain.company.ptc', fallback: 'PTC'),
),
child: Text(tr('domain.company.ptc', fallback: 'PTC')),
),
DropdownMenuItem(
value: 'JANGHEON',
child: Text(
tr('domain.company.jangheon', fallback: '장헌'),
),
child: Text(tr('domain.company.jangheon', fallback: '장헌')),
),
DropdownMenuItem(
value: 'BARON',
child: Text(
tr('domain.company.baron', fallback: '바론'),
),
child: Text(tr('domain.company.baron', fallback: '바론')),
),
DropdownMenuItem(
value: 'HALLA',
child: Text(
tr('domain.company.halla', fallback: '한라'),
),
child: Text(tr('domain.company.halla', fallback: '한라')),
),
],
onChanged: _isAffiliateEmail
@@ -1038,7 +1100,7 @@ class _SignupScreenState extends State<SignupScreen> {
'ui.userfront.signup.profile.department_optional',
fallback: '소속 정보 (선택)',
),
border: const OutlineInputBorder()
border: const OutlineInputBorder(),
),
),
],
@@ -1076,36 +1138,16 @@ class _SignupScreenState extends State<SignupScreen> {
);
}
if (requiresUpper) {
parts.add(
tr(
'msg.userfront.signup.policy.uppercase',
fallback: '대문자',
),
);
parts.add(tr('msg.userfront.signup.policy.uppercase', fallback: '대문자'));
}
if (requiresLower) {
parts.add(
tr(
'msg.userfront.signup.policy.lowercase',
fallback: '소문자',
),
);
parts.add(tr('msg.userfront.signup.policy.lowercase', fallback: '소문자'));
}
if (requiresNumber) {
parts.add(
tr(
'msg.userfront.signup.policy.number',
fallback: '숫자',
),
);
parts.add(tr('msg.userfront.signup.policy.number', fallback: '숫자'));
}
if (requiresSymbol) {
parts.add(
tr(
'msg.userfront.signup.policy.symbol',
fallback: '특수문자',
),
);
parts.add(tr('msg.userfront.signup.policy.symbol', fallback: '특수문자'));
}
return tr(
@@ -1117,7 +1159,7 @@ class _SignupScreenState extends State<SignupScreen> {
Widget _buildStepPassword() {
String p = _passwordController.text;
// Default Policy Fallback
final minLength = (_policy?['minLength'] as int?) ?? 12;
final minTypes = (_policy?['minCharacterTypes'] as int?) ?? 0;
@@ -1152,7 +1194,10 @@ class _SignupScreenState extends State<SignupScreen> {
// 비밀번호 정책 안내 박스
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.blue[50], borderRadius: BorderRadius.circular(8)),
decoration: BoxDecoration(
color: Colors.blue[50],
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
const Icon(Icons.security, size: 18, color: Colors.blue),
@@ -1160,7 +1205,11 @@ class _SignupScreenState extends State<SignupScreen> {
Expanded(
child: Text(
_buildPolicyDescription(),
style: TextStyle(fontSize: 12, color: Colors.blue[800], fontWeight: FontWeight.w500),
style: TextStyle(
fontSize: 12,
color: Colors.blue[800],
fontWeight: FontWeight.w500,
),
),
),
],
@@ -1219,10 +1268,7 @@ class _SignupScreenState extends State<SignupScreen> {
),
if (requiresNumber)
_cryptoCheck(
tr(
'msg.userfront.signup.password.rule.number',
fallback: '숫자',
),
tr('msg.userfront.signup.password.rule.number', fallback: '숫자'),
hasDigit,
),
if (requiresSymbol)
@@ -1253,7 +1299,7 @@ class _SignupScreenState extends State<SignupScreen> {
labelText: tr(
'ui.userfront.signup.password.confirm_label',
fallback: '비밀번호 확인',
),
),
border: const OutlineInputBorder(),
errorText: _confirmPasswordError,
),
@@ -1266,9 +1312,19 @@ class _SignupScreenState extends State<SignupScreen> {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(isValid ? Icons.check_circle : Icons.circle_outlined, size: 14, color: isValid ? Colors.green : Colors.grey),
Icon(
isValid ? Icons.check_circle : Icons.circle_outlined,
size: 14,
color: isValid ? Colors.green : Colors.grey,
),
const SizedBox(width: 4),
Text(label, style: TextStyle(fontSize: 11, color: isValid ? Colors.green : Colors.grey)),
Text(
label,
style: TextStyle(
fontSize: 11,
color: isValid ? Colors.green : Colors.grey,
),
),
],
);
}
@@ -1276,8 +1332,10 @@ class _SignupScreenState extends State<SignupScreen> {
@override
Widget build(BuildContext context) {
bool canGoNext = false;
if (_currentStep == 1 && _termsAccepted && _privacyAccepted) canGoNext = true;
if (_currentStep == 2 && _isEmailVerified && _isPhoneVerified) canGoNext = true;
if (_currentStep == 1 && _termsAccepted && _privacyAccepted)
canGoNext = true;
if (_currentStep == 2 && _isEmailVerified && _isPhoneVerified)
canGoNext = true;
if (_currentStep == 3) {
final nameOk = _nameController.text.trim().isNotEmpty;
if (_affiliationType == 'GENERAL') {
@@ -1313,11 +1371,13 @@ class _SignupScreenState extends State<SignupScreen> {
padding: const EdgeInsets.all(24),
child: Form(
key: _formKey,
child: _currentStep == 1
? _buildStepAgreement()
: (_currentStep == 2
? _buildStepAuth()
: (_currentStep == 3 ? _buildStepInfo() : _buildStepPassword())),
child: _currentStep == 1
? _buildStepAgreement()
: (_currentStep == 2
? _buildStepAuth()
: (_currentStep == 3
? _buildStepInfo()
: _buildStepPassword())),
),
),
),
@@ -1329,7 +1389,10 @@ class _SignupScreenState extends State<SignupScreen> {
Expanded(
child: OutlinedButton(
onPressed: () => setState(() => _currentStep--),
style: OutlinedButton.styleFrom(minimumSize: const Size.fromHeight(55), side: const BorderSide(color: Colors.black)),
style: OutlinedButton.styleFrom(
minimumSize: const Size.fromHeight(55),
side: const BorderSide(color: Colors.black),
),
child: Text(
tr('ui.common.prev', fallback: '이전'),
style: const TextStyle(color: Colors.black),
@@ -1340,20 +1403,35 @@ class _SignupScreenState extends State<SignupScreen> {
],
Expanded(
child: FilledButton(
onPressed: _currentStep < 4
? (canGoNext ? () => setState(() => _currentStep++) : null)
: (_isLoading ? null : _handleSignup),
onPressed: _currentStep < 4
? (canGoNext
? () => setState(() => _currentStep++)
: null)
: (_isLoading ? null : _handleSignup),
style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight(55),
backgroundColor: Colors.black,
),
child: _isLoading
? const SizedBox(height: 20, width: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2))
: Text(
_currentStep < 4
? tr('ui.userfront.signup.next_step', fallback: '다음 단계')
: tr('ui.userfront.signup.complete', fallback: '가입 완료'),
),
child: _isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: Text(
_currentStep < 4
? tr(
'ui.userfront.signup.next_step',
fallback: '다음 단계',
)
: tr(
'ui.userfront.signup.complete',
fallback: '가입 완료',
),
),
),
),
],