forked from baron/baron-sso
Merge feature/i18n into dev (userfront only)
This commit is contained in:
@@ -174,7 +174,6 @@ class AuthTimelineNotifier extends Notifier<AuthTimelineState> {
|
||||
isLoadingMore: false,
|
||||
error: tr(
|
||||
'msg.userfront.dashboard.timeline.load_error',
|
||||
fallback: '접속이력을 불러오지 못했습니다.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import '../domain/providers/linked_rps_provider.dart';
|
||||
import '../../../../core/notifiers/auth_notifier.dart';
|
||||
import '../../../../core/services/auth_token_store.dart';
|
||||
import '../../../../core/services/http_client.dart';
|
||||
import '../../../../core/i18n/locale_utils.dart';
|
||||
import '../../../../core/widgets/language_selector.dart';
|
||||
import '../../../../core/ui/layout_breakpoints.dart';
|
||||
import '../../profile/domain/notifiers/profile_notifier.dart';
|
||||
import '../domain/dashboard_providers.dart';
|
||||
@@ -35,6 +37,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
bool _auditLoading = false;
|
||||
bool _auditLoadingMore = false;
|
||||
bool _isRevoking = false;
|
||||
bool _redirectingToSignin = false;
|
||||
|
||||
bool _showAllActivities = false;
|
||||
final Set<String> _revokedClientIds = {};
|
||||
@@ -43,7 +46,13 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_pageScrollController.addListener(_onPageScroll);
|
||||
_loadAuditLogs(reset: true);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!_isLoggedIn()) {
|
||||
_redirectToSignin();
|
||||
return;
|
||||
}
|
||||
_loadAuditLogs(reset: true);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -63,19 +72,18 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(
|
||||
tr('ui.userfront.dashboard.revoke.title', fallback: '연동 해지'),
|
||||
tr('ui.userfront.dashboard.revoke.title'),
|
||||
),
|
||||
content: Text(
|
||||
tr(
|
||||
'msg.userfront.dashboard.revoke.confirm',
|
||||
fallback: '{{app}} 앱과의 연동을 해지하시겠습니까?\\n해지하면 다음 로그인 시 다시 동의가 필요합니다.',
|
||||
params: {'app': appName},
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: Text(tr('ui.common.cancel', fallback: '취소')),
|
||||
child: Text(tr('ui.common.cancel')),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
@@ -83,7 +91,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
child: Text(
|
||||
tr(
|
||||
'ui.userfront.dashboard.revoke.confirm_button',
|
||||
fallback: '해지하기',
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -102,7 +109,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
content: Text(
|
||||
tr(
|
||||
'msg.userfront.dashboard.revoke.success',
|
||||
fallback: '{{app}} 연동이 해지되었습니다.',
|
||||
params: {'app': appName},
|
||||
),
|
||||
),
|
||||
@@ -120,7 +126,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
content: Text(
|
||||
tr(
|
||||
'msg.userfront.dashboard.revoke.error',
|
||||
fallback: '해지 실패: {{error}}',
|
||||
params: {'error': '$e'},
|
||||
),
|
||||
),
|
||||
@@ -163,7 +168,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
Text(
|
||||
tr(
|
||||
'ui.userfront.dashboard.scopes.title',
|
||||
fallback: '권한 (Scopes)',
|
||||
),
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
@@ -172,7 +176,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
Text(
|
||||
tr(
|
||||
'msg.userfront.dashboard.scopes.empty',
|
||||
fallback: '요청된 권한이 없습니다.',
|
||||
),
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
)
|
||||
@@ -198,7 +201,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
Text(
|
||||
tr(
|
||||
'ui.userfront.dashboard.status_history',
|
||||
fallback: '상태 이력',
|
||||
),
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
@@ -209,7 +211,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
Text(
|
||||
tr(
|
||||
'msg.userfront.dashboard.last_auth',
|
||||
fallback: '최근 인증: {{value}}',
|
||||
params: {'value': item.lastAuthAt},
|
||||
),
|
||||
),
|
||||
@@ -217,15 +218,13 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final statusLabel = item.status == 'active'
|
||||
? tr('ui.common.status.active', fallback: '활성')
|
||||
? tr('ui.common.status.active')
|
||||
: tr(
|
||||
'ui.userfront.dashboard.status.revoked',
|
||||
fallback: '해지됨',
|
||||
);
|
||||
return Text(
|
||||
tr(
|
||||
'msg.userfront.dashboard.current_status',
|
||||
fallback: '현재 상태: {{status}}',
|
||||
params: {'status': statusLabel},
|
||||
),
|
||||
style: TextStyle(
|
||||
@@ -244,7 +243,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(tr('ui.common.close', fallback: '닫기')),
|
||||
child: Text(tr('ui.common.close')),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -255,50 +254,60 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
|
||||
Widget _buildSideMenu(BuildContext context, {required bool closeOnTap}) {
|
||||
return SafeArea(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.home_outlined),
|
||||
title: Text(tr('ui.userfront.nav.dashboard', fallback: '대시보드')),
|
||||
selected: true,
|
||||
onTap: () {
|
||||
if (closeOnTap) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
context.go('/');
|
||||
},
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.home_outlined),
|
||||
title: Text(tr('ui.userfront.nav.dashboard')),
|
||||
selected: true,
|
||||
onTap: () {
|
||||
if (closeOnTap) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
context.go('/');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.person_outline),
|
||||
title: Text(tr('ui.userfront.nav.profile')),
|
||||
onTap: () {
|
||||
if (closeOnTap) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
context.push('/profile');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.qr_code_scanner),
|
||||
title: Text(tr('ui.userfront.nav.qr_scan')),
|
||||
onTap: () {
|
||||
if (closeOnTap) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
_onScanQR();
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.logout),
|
||||
title: Text(tr('ui.userfront.nav.logout')),
|
||||
onTap: () async {
|
||||
if (closeOnTap) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
await _logout();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.person_outline),
|
||||
title: Text(tr('ui.userfront.nav.profile', fallback: '내 정보')),
|
||||
onTap: () {
|
||||
if (closeOnTap) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
context.push('/profile');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.qr_code_scanner),
|
||||
title: Text(tr('ui.userfront.nav.qr_scan', fallback: 'QR 스캔')),
|
||||
onTap: () {
|
||||
if (closeOnTap) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
_onScanQR();
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.logout),
|
||||
title: Text(tr('ui.userfront.nav.logout', fallback: '로그아웃')),
|
||||
onTap: () async {
|
||||
if (closeOnTap) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
await _logout();
|
||||
},
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 16),
|
||||
child: LanguageSelector(compact: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -306,6 +315,10 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
}
|
||||
|
||||
Future<void> _refreshAll() async {
|
||||
if (!_isLoggedIn()) {
|
||||
_redirectToSignin();
|
||||
return;
|
||||
}
|
||||
await ref.read(profileProvider.notifier).loadProfile();
|
||||
setState(() {
|
||||
_revokedClientIds.clear();
|
||||
@@ -367,6 +380,9 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
}
|
||||
|
||||
Future<void> _loadAuditLogs({bool reset = false}) async {
|
||||
if (!_isLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
if (_auditLoading || _auditLoadingMore) {
|
||||
return;
|
||||
}
|
||||
@@ -443,15 +459,15 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
|
||||
String _authMethodLabel() {
|
||||
if (AuthTokenStore.usesCookie()) {
|
||||
return tr('ui.userfront.auth_method.ory', fallback: 'Ory 세션');
|
||||
return tr('ui.userfront.auth_method.ory');
|
||||
}
|
||||
final provider = AuthTokenStore.getProvider();
|
||||
if (provider == null || provider.isEmpty) {
|
||||
return tr('ui.userfront.auth_method.session', fallback: '세션');
|
||||
return tr('ui.userfront.auth_method.session');
|
||||
}
|
||||
final lower = provider.toLowerCase();
|
||||
if (lower.contains('ory')) {
|
||||
return tr('ui.userfront.auth_method.ory', fallback: 'Ory 세션');
|
||||
return tr('ui.userfront.auth_method.ory');
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
@@ -494,12 +510,10 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
final tooltip = [
|
||||
tr(
|
||||
'msg.userfront.dashboard.approved_device',
|
||||
fallback: '승인 기기: {{device}}',
|
||||
params: {'device': deviceLabel},
|
||||
),
|
||||
tr(
|
||||
'msg.userfront.dashboard.approved_ip',
|
||||
fallback: '승인 IP: {{ip}}',
|
||||
params: {'ip': approvedIp.isEmpty ? '-' : approvedIp},
|
||||
),
|
||||
].join('\n');
|
||||
@@ -522,21 +536,17 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
final tooltipLabel = isOidc
|
||||
? tr(
|
||||
'ui.userfront.dashboard.approved_session.userfront',
|
||||
fallback: '승인한 Userfront 세션 ID',
|
||||
)
|
||||
: tr(
|
||||
'ui.userfront.dashboard.approved_session.default',
|
||||
fallback: '승인한 세션 ID',
|
||||
);
|
||||
final tooltip = approvedSessionId.isEmpty
|
||||
? tr(
|
||||
'msg.userfront.dashboard.approved_session.none',
|
||||
fallback: '{{label}} 없음',
|
||||
params: {'label': tooltipLabel},
|
||||
)
|
||||
: tr(
|
||||
'msg.userfront.dashboard.approved_session.copy_click',
|
||||
fallback: '{{label}}: {{id}}\\n클릭하면 복사됩니다.',
|
||||
params: {'label': tooltipLabel, 'id': approvedSessionId},
|
||||
);
|
||||
return InkWell(
|
||||
@@ -550,7 +560,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
content: Text(
|
||||
tr(
|
||||
'msg.userfront.dashboard.session_id_copied',
|
||||
fallback: '세션 ID가 복사되었습니다.',
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -584,7 +593,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
return _selectableText(
|
||||
tr(
|
||||
'msg.userfront.dashboard.auth_method',
|
||||
fallback: '인증수단: {{method}}',
|
||||
params: {'method': authMethod},
|
||||
),
|
||||
);
|
||||
@@ -593,12 +601,10 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
final tooltip = [
|
||||
tr(
|
||||
'msg.userfront.dashboard.approved_device',
|
||||
fallback: '승인 기기: {{device}}',
|
||||
params: {'device': deviceLabel},
|
||||
),
|
||||
tr(
|
||||
'msg.userfront.dashboard.approved_ip',
|
||||
fallback: '승인 IP: {{ip}}',
|
||||
params: {'ip': approvedIp.isEmpty ? '-' : approvedIp},
|
||||
),
|
||||
].join('\n');
|
||||
@@ -607,7 +613,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
child: _selectableText(
|
||||
tr(
|
||||
'msg.userfront.dashboard.auth_method',
|
||||
fallback: '인증수단: {{method}}',
|
||||
params: {'method': authMethod},
|
||||
),
|
||||
style: const TextStyle(
|
||||
@@ -625,11 +630,9 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
final tooltipLabel = isOidc
|
||||
? tr(
|
||||
'ui.userfront.dashboard.approved_session.userfront',
|
||||
fallback: '승인한 Userfront 세션 ID',
|
||||
)
|
||||
: tr(
|
||||
'ui.userfront.dashboard.approved_session.default',
|
||||
fallback: '승인한 세션 ID',
|
||||
);
|
||||
return InkWell(
|
||||
onTap: approvedSessionId.isEmpty
|
||||
@@ -642,7 +645,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
content: Text(
|
||||
tr(
|
||||
'msg.userfront.dashboard.session_id_copied',
|
||||
fallback: '세션 ID가 복사되었습니다.',
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -653,18 +655,15 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
message: approvedSessionId.isEmpty
|
||||
? tr(
|
||||
'msg.userfront.dashboard.approved_session.none',
|
||||
fallback: '{{label}} 없음',
|
||||
params: {'label': tooltipLabel},
|
||||
)
|
||||
: tr(
|
||||
'msg.userfront.dashboard.approved_session.copy_tap',
|
||||
fallback: '{{label}}: {{id}}\\n탭하면 복사됩니다.',
|
||||
params: {'label': tooltipLabel, 'id': approvedSessionId},
|
||||
),
|
||||
child: Text(
|
||||
tr(
|
||||
'msg.userfront.dashboard.auth_method',
|
||||
fallback: '인증수단: {{method}}',
|
||||
params: {
|
||||
'method': isOidc
|
||||
? authMethod
|
||||
@@ -695,7 +694,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
final tooltip = clientId.isEmpty
|
||||
? tr(
|
||||
'msg.userfront.dashboard.client_id_missing',
|
||||
fallback: 'Client ID 없음',
|
||||
)
|
||||
: tr(
|
||||
'msg.userfront.dashboard.client_id',
|
||||
@@ -717,10 +715,10 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
|
||||
String _appLabelForPath(String path) {
|
||||
if (path.startsWith('/api/v1/auth')) {
|
||||
return tr('ui.userfront.app_label.baron', fallback: 'Baron 로그인');
|
||||
return tr('ui.userfront.app_label.baron');
|
||||
}
|
||||
if (path.startsWith('/api/v1/user')) {
|
||||
return tr('ui.userfront.app_label.baron', fallback: 'Baron 로그인');
|
||||
return tr('ui.userfront.app_label.baron');
|
||||
}
|
||||
if (path.startsWith('/api/v1/dev')) {
|
||||
return tr('ui.userfront.app_label.dev_console', fallback: 'Dev Console');
|
||||
@@ -731,11 +729,15 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
fallback: 'Admin Console',
|
||||
);
|
||||
}
|
||||
return tr('ui.userfront.app_label.baron', fallback: 'Baron 로그인');
|
||||
return tr('ui.userfront.app_label.baron');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_isLoggedIn()) {
|
||||
_redirectToSignin();
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final isWide = MediaQuery.of(context).size.width >= sideMenuBreakpoint;
|
||||
final profileState = ref.watch(profileProvider);
|
||||
final profile = profileState.value;
|
||||
@@ -747,14 +749,14 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
tr('ui.userfront.profile.user_fallback', fallback: 'User');
|
||||
final department = profile?.department.isNotEmpty == true
|
||||
? profile!.department
|
||||
: tr('ui.userfront.profile.department_empty', fallback: '소속 정보 없음');
|
||||
: tr('ui.userfront.profile.department_empty');
|
||||
final sessionIssuedAt = _getJwtIssuedAt();
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: _subtle,
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
tr('ui.userfront.app_title', fallback: 'Baron 로그인'),
|
||||
tr('ui.userfront.app_title'),
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
elevation: 0,
|
||||
@@ -763,17 +765,17 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.person_outline),
|
||||
tooltip: tr('ui.userfront.nav.profile', fallback: '내 정보'),
|
||||
tooltip: tr('ui.userfront.nav.profile'),
|
||||
onPressed: () => context.push('/profile'),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.qr_code_scanner),
|
||||
tooltip: tr('ui.userfront.nav.qr_scan', fallback: 'QR 스캔'),
|
||||
tooltip: tr('ui.userfront.nav.qr_scan'),
|
||||
onPressed: _onScanQR,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.logout),
|
||||
tooltip: tr('ui.userfront.nav.logout', fallback: '로그아웃'),
|
||||
tooltip: tr('ui.userfront.nav.logout'),
|
||||
onPressed: _logout,
|
||||
),
|
||||
],
|
||||
@@ -814,21 +816,18 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
_buildSectionTitle(
|
||||
tr(
|
||||
'ui.userfront.sections.apps',
|
||||
fallback: '나의 App 현황',
|
||||
),
|
||||
tr(
|
||||
'msg.userfront.sections.apps_subtitle',
|
||||
fallback: '현재 연결된 앱과 최근 인증 상태입니다.',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildActivitySection(isMobile),
|
||||
const SizedBox(height: 28),
|
||||
_buildSectionTitle(
|
||||
tr('ui.userfront.sections.audit', fallback: '접속이력'),
|
||||
tr('ui.userfront.sections.audit'),
|
||||
tr(
|
||||
'msg.userfront.sections.audit_subtitle',
|
||||
fallback: 'Baron 로그인 기준의 최근 접근 기록입니다.',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -853,14 +852,13 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
) {
|
||||
final sessionLabel = issuedAt != null
|
||||
? _formatDateTime(issuedAt)
|
||||
: tr('ui.userfront.session.unknown', fallback: '알 수 없음');
|
||||
: tr('ui.userfront.session.unknown');
|
||||
final infoColumn = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
tr(
|
||||
'msg.userfront.greeting',
|
||||
fallback: '안녕하세요, {{name}}님',
|
||||
params: {'name': userName},
|
||||
),
|
||||
style: const TextStyle(
|
||||
@@ -881,7 +879,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
children: [
|
||||
_buildInfoChip(
|
||||
Icons.verified_user,
|
||||
tr('ui.userfront.session.active', fallback: '세션 활성'),
|
||||
tr('ui.userfront.session.active'),
|
||||
),
|
||||
_buildInfoChip(Icons.lock_outline, _authMethodLabel()),
|
||||
_buildInfoChip(Icons.access_time, sessionLabel),
|
||||
@@ -967,7 +965,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
Text(
|
||||
tr(
|
||||
'msg.userfront.dashboard.activities.empty',
|
||||
fallback: '연동된 앱이 없습니다.',
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
@@ -979,7 +976,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
Text(
|
||||
tr(
|
||||
'msg.userfront.dashboard.activities.empty_detail',
|
||||
fallback: '앱을 연동하면 최근 활동과 상태가 표시됩니다.',
|
||||
),
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||
),
|
||||
@@ -998,14 +994,13 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
Text(
|
||||
tr(
|
||||
'msg.userfront.dashboard.activities.error',
|
||||
fallback: '연동 정보를 불러오지 못했습니다.',
|
||||
),
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed: () => ref.read(linkedRpsProvider.notifier).refresh(),
|
||||
child: Text(tr('ui.common.retry', fallback: '다시 시도')),
|
||||
child: Text(tr('ui.common.retry')),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1023,7 +1018,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
|
||||
final lastAuthLabel = rp.lastAuthenticatedAt != null
|
||||
? _formatDateTime(rp.lastAuthenticatedAt!)
|
||||
: tr('ui.userfront.dashboard.activity.linked', fallback: '연동됨');
|
||||
: tr('ui.userfront.dashboard.activity.linked');
|
||||
|
||||
final statusCode = isRevoked ? 'revoked' : 'active';
|
||||
final name = rp.name.isNotEmpty ? rp.name : rp.id;
|
||||
@@ -1128,8 +1123,8 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
),
|
||||
label: Text(
|
||||
_showAllActivities
|
||||
? tr('ui.common.collapse', fallback: '접기')
|
||||
: tr('ui.common.show_more', fallback: '+ 더보기'),
|
||||
? tr('ui.common.collapse')
|
||||
: tr('ui.common.show_more'),
|
||||
style: TextStyle(
|
||||
color: _showAllActivities
|
||||
? Colors.grey
|
||||
@@ -1193,19 +1188,18 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor,
|
||||
color: statusColor.withValues(alpha: 31),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Text(
|
||||
item.status == 'active'
|
||||
? tr('ui.common.status.active', fallback: '활성')
|
||||
? tr('ui.common.status.active')
|
||||
: tr(
|
||||
'ui.userfront.dashboard.status.revoked',
|
||||
fallback: '해지됨',
|
||||
),
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.white,
|
||||
color: statusColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
@@ -1214,7 +1208,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
tr('ui.userfront.dashboard.last_auth_label', fallback: '최근 인증'),
|
||||
tr('ui.userfront.dashboard.last_auth_label'),
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
@@ -1238,7 +1232,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
),
|
||||
child: Text(
|
||||
tr('ui.common.details', fallback: '상세정보'),
|
||||
tr('ui.common.details'),
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
@@ -1272,11 +1266,9 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
item.isRevoked
|
||||
? tr(
|
||||
'ui.userfront.dashboard.status.revoked',
|
||||
fallback: '해지됨',
|
||||
)
|
||||
: tr(
|
||||
'ui.userfront.dashboard.revoke.title',
|
||||
fallback: '연동 해지',
|
||||
),
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
@@ -1314,7 +1306,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
content: Text(
|
||||
tr(
|
||||
'msg.userfront.dashboard.link_open_error',
|
||||
fallback: '해당 링크를 열 수 없습니다.',
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1326,7 +1317,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
content: Text(
|
||||
tr(
|
||||
'msg.userfront.dashboard.link_missing',
|
||||
fallback: '이동할 페이지 주소(Client URI)가 설정되지 않았습니다.',
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1357,14 +1347,13 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
Text(
|
||||
tr(
|
||||
'msg.userfront.dashboard.audit_load_error',
|
||||
fallback: '접속이력을 불러오지 못했습니다.',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed: () =>
|
||||
ref.read(authTimelineProvider.notifier).refresh(),
|
||||
child: Text(tr('ui.common.retry', fallback: '다시 시도')),
|
||||
child: Text(tr('ui.common.retry')),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1378,7 +1367,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
child: Text(
|
||||
tr(
|
||||
'msg.userfront.dashboard.audit_empty',
|
||||
fallback: '최근 접속 이력이 없습니다.',
|
||||
),
|
||||
style: TextStyle(color: Colors.grey[600]),
|
||||
),
|
||||
@@ -1429,14 +1417,13 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
),
|
||||
DataColumn(
|
||||
label: Text(
|
||||
tr('ui.userfront.audit.table.date', fallback: '접속일자'),
|
||||
tr('ui.userfront.audit.table.date'),
|
||||
),
|
||||
),
|
||||
DataColumn(
|
||||
label: Text(
|
||||
tr(
|
||||
'ui.userfront.audit.table.app',
|
||||
fallback: '애플리케이션',
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1449,7 +1436,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
label: Text(
|
||||
tr(
|
||||
'ui.userfront.audit.table.device',
|
||||
fallback: '접속환경',
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1457,7 +1443,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
label: Text(
|
||||
tr(
|
||||
'ui.userfront.audit.table.auth_method',
|
||||
fallback: '인증수단',
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1465,20 +1450,19 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
label: Text(
|
||||
tr(
|
||||
'ui.userfront.audit.table.result',
|
||||
fallback: '인증결과',
|
||||
),
|
||||
),
|
||||
),
|
||||
DataColumn(
|
||||
label: Text(
|
||||
tr('ui.userfront.audit.table.status', fallback: '현황'),
|
||||
tr('ui.userfront.audit.table.status'),
|
||||
),
|
||||
),
|
||||
],
|
||||
rows: state.items.map((log) {
|
||||
final statusLabel = log.status == 'success'
|
||||
? tr('ui.common.status.success', fallback: '성공')
|
||||
: tr('ui.common.status.failure', fallback: '실패');
|
||||
? tr('ui.common.status.success')
|
||||
: tr('ui.common.status.failure');
|
||||
final statusColor = log.status == 'success'
|
||||
? Colors.green
|
||||
: Colors.redAccent;
|
||||
@@ -1523,7 +1507,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
_selectableText(
|
||||
tr(
|
||||
'ui.userfront.audit.table.pending',
|
||||
fallback: '(준비중)',
|
||||
),
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
@@ -1571,8 +1554,8 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
),
|
||||
_selectableText(
|
||||
log.status == 'success'
|
||||
? tr('ui.common.status.success', fallback: '성공')
|
||||
: tr('ui.common.status.failure', fallback: '실패'),
|
||||
? tr('ui.common.status.success')
|
||||
: tr('ui.common.status.failure'),
|
||||
style: TextStyle(
|
||||
color: log.status == 'success'
|
||||
? Colors.green
|
||||
@@ -1597,14 +1580,12 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
_selectableText(
|
||||
tr(
|
||||
'msg.userfront.audit.date',
|
||||
fallback: '접속일자: {{value}}',
|
||||
params: {'value': _formatDateTime(log.timestamp)},
|
||||
),
|
||||
),
|
||||
_selectableText(
|
||||
tr(
|
||||
'msg.userfront.audit.ip',
|
||||
fallback: '접속 IP: {{value}}',
|
||||
params: {
|
||||
'value': log.ipAddress.isEmpty
|
||||
? tr('ui.common.hyphen', fallback: '-')
|
||||
@@ -1615,7 +1596,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
_selectableText(
|
||||
tr(
|
||||
'msg.userfront.audit.device',
|
||||
fallback: '접속환경: {{value}}',
|
||||
params: {
|
||||
'value': _deviceLabelFromUserAgent(log.userAgent),
|
||||
},
|
||||
@@ -1630,16 +1610,15 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
_selectableText(
|
||||
tr(
|
||||
'msg.userfront.audit.result',
|
||||
fallback: '인증결과: {{value}}',
|
||||
params: {
|
||||
'value': log.status == 'success'
|
||||
? tr('ui.common.status.success', fallback: '성공')
|
||||
: tr('ui.common.status.failure', fallback: '실패'),
|
||||
? tr('ui.common.status.success')
|
||||
: tr('ui.common.status.failure'),
|
||||
},
|
||||
),
|
||||
),
|
||||
_selectableText(
|
||||
tr('msg.userfront.audit.status', fallback: '현황: (준비중)'),
|
||||
tr('msg.userfront.audit.status'),
|
||||
style: TextStyle(color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
@@ -1667,13 +1646,12 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
Text(
|
||||
tr(
|
||||
'msg.userfront.audit.load_more_error',
|
||||
fallback: '더 불러오지 못했습니다.',
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () =>
|
||||
ref.read(authTimelineProvider.notifier).loadMore(),
|
||||
child: Text(tr('ui.common.retry', fallback: '재시도')),
|
||||
child: Text(tr('ui.common.retry')),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1683,13 +1661,34 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
tr('msg.userfront.audit.end', fallback: '더 이상 항목이 없습니다.'),
|
||||
tr('msg.userfront.audit.end'),
|
||||
style: TextStyle(color: Colors.grey[600], fontSize: 12),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
bool _isLoggedIn() {
|
||||
return AuthTokenStore.getToken() != null || AuthTokenStore.usesCookie();
|
||||
}
|
||||
|
||||
void _redirectToSignin() {
|
||||
if (!mounted || _redirectingToSignin) {
|
||||
return;
|
||||
}
|
||||
_redirectingToSignin = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
final uri = GoRouterState.of(context).uri;
|
||||
final localeCode =
|
||||
extractLocaleFromPath(uri) ?? resolvePreferredLocaleCode();
|
||||
context.go('/$localeCode/signin');
|
||||
_redirectingToSignin = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _ActivityItem {
|
||||
|
||||
Reference in New Issue
Block a user