1
0
forked from baron/baron-sso

내정보 페이지 사용성개선, adminFront user 정보 연동.

This commit is contained in:
Lectom C Han
2026-01-30 13:42:41 +09:00
parent 1cb5115f2a
commit 35552943d7
29 changed files with 1586 additions and 472 deletions

View File

@@ -2,7 +2,6 @@ import 'package:descope/descope.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../../../core/notifiers/auth_notifier.dart';
import '../../../../core/services/auth_token_store.dart';
import '../../../../core/ui/layout_breakpoints.dart';
@@ -28,6 +27,10 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
TextEditingController? _phoneController;
TextEditingController? _departmentController;
TextEditingController? _codeController;
final FocusNode _nameFocus = FocusNode();
final FocusNode _departmentFocus = FocusNode();
final FocusNode _phoneFocus = FocusNode();
final FocusNode _phoneCodeFocus = FocusNode();
String _initialPhone = '';
bool _isPhoneChanged = false;
@@ -41,6 +44,10 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
_phoneController?.dispose();
_departmentController?.dispose();
_codeController?.dispose();
_nameFocus.dispose();
_departmentFocus.dispose();
_phoneFocus.dispose();
_phoneCodeFocus.dispose();
super.dispose();
}
@@ -153,7 +160,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
}
}
Future<void> _verifyCode() async {
Future<void> _verifyCode(UserProfile profile) async {
final phone = _phoneController?.text ?? '';
final code = _codeController?.text ?? '';
if (code.isEmpty) return;
@@ -170,6 +177,9 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
const SnackBar(content: Text('인증되었습니다.')),
);
}
if (_editingField == 'phone') {
await _saveField(profile);
}
} catch (e) {
setState(() => _isVerifying = false);
if (mounted) {
@@ -180,6 +190,19 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
}
}
void _autoSaveIfEditing(UserProfile profile, String field) {
if (_editingField != field) return;
if (_isVerifying) return;
_saveField(profile);
}
void _handlePhoneFocusChange(UserProfile profile) {
if (_editingField != 'phone') return;
if (_isVerifying) return;
if (_phoneFocus.hasFocus || _phoneCodeFocus.hasFocus) return;
_saveField(profile);
}
Future<void> _saveField(UserProfile profile) async {
if (_editingField == null) return;
@@ -431,28 +454,24 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
children: [
Text(label, style: const TextStyle(fontWeight: FontWeight.w600)),
const SizedBox(height: 8),
TextField(
controller: controller,
decoration: InputDecoration(
border: const OutlineInputBorder(),
hintText: label,
Focus(
focusNode: field == 'name' ? _nameFocus : _departmentFocus,
onFocusChange: (hasFocus) {
if (!hasFocus) {
_autoSaveIfEditing(profile, field);
}
},
child: TextField(
controller: controller,
focusNode: field == 'name' ? _nameFocus : _departmentFocus,
textInputAction: TextInputAction.done,
onSubmitted: (_) => _autoSaveIfEditing(profile, field),
decoration: InputDecoration(
border: const OutlineInputBorder(),
hintText: label,
),
),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: isUpdating ? null : () => _cancelEditing(profile),
child: const Text('취소'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: isUpdating ? null : () => _saveField(profile),
child: const Text('확인'),
),
],
),
],
);
}
@@ -482,17 +501,28 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: TextField(
controller: _phoneController,
keyboardType: TextInputType.phone,
decoration: InputDecoration(
border: const OutlineInputBorder(),
hintText: '01012345678',
suffixIcon: _isPhoneVerified
? const Icon(Icons.check_circle, color: Colors.green)
: null,
child: Focus(
focusNode: _phoneFocus,
onFocusChange: (hasFocus) {
if (!hasFocus) {
_handlePhoneFocusChange(profile);
}
},
child: TextField(
controller: _phoneController,
focusNode: _phoneFocus,
keyboardType: TextInputType.phone,
textInputAction: TextInputAction.done,
onSubmitted: (_) => _autoSaveIfEditing(profile, 'phone'),
decoration: InputDecoration(
border: const OutlineInputBorder(),
hintText: '01012345678',
suffixIcon: _isPhoneVerified
? const Icon(Icons.check_circle, color: Colors.green)
: null,
),
enabled: !_isPhoneVerified,
),
enabled: !_isPhoneVerified,
),
),
const SizedBox(width: 8),
@@ -509,18 +539,29 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: TextField(
controller: _codeController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: '인증번호 6자리',
child: Focus(
focusNode: _phoneCodeFocus,
onFocusChange: (hasFocus) {
if (!hasFocus) {
_handlePhoneFocusChange(profile);
}
},
child: TextField(
controller: _codeController,
focusNode: _phoneCodeFocus,
keyboardType: TextInputType.number,
textInputAction: TextInputAction.done,
onSubmitted: (_) => _verifyCode(profile),
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: '인증번호 6자리',
),
),
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: _isVerifying ? null : _verifyCode,
onPressed: _isVerifying ? null : () => _verifyCode(profile),
child: const Text('확인'),
),
],
@@ -534,21 +575,6 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
style: TextStyle(color: Colors.orange, fontSize: 12),
),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: isUpdating ? null : () => _cancelEditing(profile),
child: const Text('취소'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: isUpdating ? null : () => _saveField(profile),
child: const Text('확인'),
),
],
),
],
);
}
@@ -556,59 +582,69 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
Widget _buildContent(UserProfile profile, bool isUpdating) {
return RefreshIndicator(
onRefresh: () => ref.read(profileProvider.notifier).loadProfile(),
child: ListView(
padding: const EdgeInsets.all(24.0),
children: [
_buildHeaderCard(profile),
const SizedBox(height: 28),
_buildSectionTitle('기본 정보', '계정 기본 정보를 관리합니다.'),
const SizedBox(height: 12),
_buildCard(
Column(
children: [
_buildEditableTile(
field: 'name',
label: '이름',
value: profile.name,
profile: profile,
isUpdating: isUpdating,
controller: _nameController!,
),
const Divider(height: 24),
_buildReadOnlyTile('이메일', profile.email),
const Divider(height: 24),
_buildPhoneEditor(profile, isUpdating),
],
),
),
const SizedBox(height: 28),
_buildSectionTitle('조직 정보', '소속 및 구분 정보입니다.'),
const SizedBox(height: 12),
_buildCard(
Column(
children: [
_buildEditableTile(
field: 'department',
label: '소속',
value: profile.department,
profile: profile,
isUpdating: isUpdating,
controller: _departmentController!,
),
const Divider(height: 24),
_buildReadOnlyTile('구분', profile.affiliationType),
if (profile.companyCode.isNotEmpty) ...[
const Divider(height: 24),
_buildReadOnlyTile('회사코드', profile.companyCode),
child: LayoutBuilder(
builder: (context, constraints) {
return Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 1200),
child: ListView(
padding: const EdgeInsets.all(24.0),
children: [
_buildHeaderCard(profile),
const SizedBox(height: 28),
_buildSectionTitle('기본 정보', '계정 기본 정보를 관리합니다.'),
const SizedBox(height: 12),
_buildCard(
Column(
children: [
_buildEditableTile(
field: 'name',
label: '이름',
value: profile.name,
profile: profile,
isUpdating: isUpdating,
controller: _nameController!,
),
const Divider(height: 24),
_buildReadOnlyTile('이메일', profile.email),
const Divider(height: 24),
_buildPhoneEditor(profile, isUpdating),
],
),
),
const SizedBox(height: 28),
_buildSectionTitle('조직 정보', '소속 및 구분 정보입니다.'),
const SizedBox(height: 12),
_buildCard(
Column(
children: [
_buildEditableTile(
field: 'department',
label: '소속',
value: profile.department,
profile: profile,
isUpdating: isUpdating,
controller: _departmentController!,
),
const Divider(height: 24),
_buildReadOnlyTile('구분', profile.affiliationType),
if (profile.companyCode.isNotEmpty) ...[
const Divider(height: 24),
_buildReadOnlyTile('회사코드', profile.companyCode),
],
],
),
),
if (isUpdating || _isVerifying) ...[
const SizedBox(height: 24),
const Center(child: CircularProgressIndicator()),
],
],
],
),
),
),
if (isUpdating || _isVerifying) ...[
const SizedBox(height: 24),
const Center(child: CircularProgressIndicator()),
],
],
);
},
),
);
}
@@ -652,7 +688,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
appBar: AppBar(
title: Text(
'Baron 통합로그인',
style: GoogleFonts.outfit(fontWeight: FontWeight.bold),
style: TextStyle(fontWeight: FontWeight.bold),
),
elevation: 0,
backgroundColor: _surface,