1
0
forked from baron/baron-sso

Merge feature/i18n into dev (userfront only)

This commit is contained in:
Lectom C Han
2026-02-12 21:53:42 +09:00
55 changed files with 3982 additions and 1104 deletions

View File

@@ -174,7 +174,6 @@ class AuthTimelineNotifier extends Notifier<AuthTimelineState> {
isLoadingMore: false,
error: tr(
'msg.userfront.dashboard.timeline.load_error',
fallback: '접속이력을 불러오지 못했습니다.',
),
);
}

View File

@@ -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 {