1
0
forked from baron/baron-sso

RP 활성화/비활성화 UX 개선

This commit is contained in:
2026-02-04 10:38:41 +09:00
parent 1f1e7b6ce7
commit 1a02c15e78

View File

@@ -164,6 +164,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
Future<List<LinkedRp>>? _linkedRpsFuture; Future<List<LinkedRp>>? _linkedRpsFuture;
bool _showAllActivities = false; bool _showAllActivities = false;
final Set<String> _revokedClientIds = {};
@override @override
void initState() { void initState() {
@@ -213,7 +214,9 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('$appName 연동이 해지되었습니다.')), SnackBar(content: Text('$appName 연동이 해지되었습니다.')),
); );
_refreshAll(); setState(() {
_revokedClientIds.add(clientId);
});
} }
} catch (e) { } catch (e) {
if (mounted) { if (mounted) {
@@ -297,6 +300,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
await ref.read(profileProvider.notifier).loadProfile(); await ref.read(profileProvider.notifier).loadProfile();
await _loadAuditLogs(reset: true); await _loadAuditLogs(reset: true);
setState(() { setState(() {
_revokedClientIds.clear();
_linkedRpsFuture = _fetchLinkedRps(); _linkedRpsFuture = _fetchLinkedRps();
}); });
if (_linkedRpsFuture != null) { if (_linkedRpsFuture != null) {
@@ -866,11 +870,13 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
List<_ActivityItem> _buildActivityItems(List<LinkedRp> linkedRps) { List<_ActivityItem> _buildActivityItems(List<LinkedRp> linkedRps) {
final items = <_ActivityItem>[]; final items = <_ActivityItem>[];
for (final rp in linkedRps) { for (final rp in linkedRps) {
final isRevoked = _revokedClientIds.contains(rp.id);
final lastAuthLabel = rp.lastAuthenticatedAt != null final lastAuthLabel = rp.lastAuthenticatedAt != null
? _formatDateTime(rp.lastAuthenticatedAt!) ? _formatDateTime(rp.lastAuthenticatedAt!)
: '연동됨'; : '연동됨';
final normalizedStatus = rp.status.toLowerCase(); final normalizedStatus = rp.status.toLowerCase();
final statusLabel = normalizedStatus.isEmpty || normalizedStatus == 'active' ? '활성' : '비활성'; final statusLabel = isRevoked ? '비활성' : (normalizedStatus.isEmpty || normalizedStatus == 'active' ? '활성' : '비활성');
final name = rp.name.isNotEmpty ? rp.name : rp.id; final name = rp.name.isNotEmpty ? rp.name : rp.id;
items.add( items.add(
_ActivityItem( _ActivityItem(
@@ -879,7 +885,8 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
lastAuthAt: lastAuthLabel, lastAuthAt: lastAuthLabel,
status: statusLabel, status: statusLabel,
canLogout: false, canLogout: false,
onRevoke: () => _onRevokeLink(rp.id, name), isRevoked: isRevoked,
onRevoke: isRevoked ? null : () => _onRevokeLink(rp.id, name),
), ),
); );
} }
@@ -933,85 +940,99 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
} }
Widget _buildActivityCard(_ActivityItem item) { Widget _buildActivityCard(_ActivityItem item) {
final statusColor = item.status == '활성' ? Colors.green : Colors.grey; final isActive = item.status == '활성';
return Container( final statusColor = isActive ? Colors.green : Colors.grey;
width: 260, final borderColor = isActive ? Colors.green.withOpacity(0.5) : _border;
padding: const EdgeInsets.all(16), final borderWidth = isActive ? 1.5 : 1.0;
decoration: BoxDecoration(
color: _surface, return Opacity(
borderRadius: BorderRadius.circular(14), opacity: item.isRevoked ? 0.6 : 1.0,
border: Border.all(color: _border), child: Container(
), width: 260,
child: Column( padding: const EdgeInsets.all(16),
crossAxisAlignment: CrossAxisAlignment.start, decoration: BoxDecoration(
children: [ color: _surface,
Row( borderRadius: BorderRadius.circular(14),
children: [ border: Border.all(color: borderColor, width: borderWidth),
Expanded( boxShadow: isActive ? [
child: Text( BoxShadow(
item.appName, color: Colors.green.withOpacity(0.05),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: _ink), blurRadius: 10,
offset: const Offset(0, 4),
)
] : null,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
item.appName,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: _ink),
),
), ),
), Container(
Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration(
decoration: BoxDecoration( color: statusColor.withOpacity(0.12),
color: statusColor.withOpacity(0.12), borderRadius: BorderRadius.circular(999),
borderRadius: BorderRadius.circular(999), ),
child: Text(
item.status,
style: TextStyle(fontSize: 11, color: statusColor, fontWeight: FontWeight.w600),
),
), ),
child: Text( ],
item.status, ),
style: TextStyle(fontSize: 11, color: statusColor, fontWeight: FontWeight.w600), const SizedBox(height: 12),
), Text(
), '최근 인증',
], style: TextStyle(fontSize: 12, color: Colors.grey[600]),
), ),
const SizedBox(height: 12), const SizedBox(height: 4),
Text( Text(
'최근 인증', item.lastAuthAt,
style: TextStyle(fontSize: 12, color: Colors.grey[600]), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: _ink),
), ),
const SizedBox(height: 4), const SizedBox(height: 16),
Text( Row(
item.lastAuthAt, children: [
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: _ink), if (item.canLogout)
), Expanded(
const SizedBox(height: 16), child: OutlinedButton(
Row( onPressed: item.onLogout,
children: [ style: OutlinedButton.styleFrom(
if (item.canLogout) foregroundColor: _ink,
side: const BorderSide(color: _border),
padding: const EdgeInsets.symmetric(vertical: 8),
),
child: const Text('로그아웃', style: TextStyle(fontSize: 13)),
),
),
if (item.canLogout) const SizedBox(width: 8),
Expanded( Expanded(
child: OutlinedButton( child: OutlinedButton(
onPressed: item.onLogout, onPressed: (_isRevoking || item.isRevoked) ? null : item.onRevoke,
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: _ink, foregroundColor: item.isRevoked ? Colors.grey : Colors.redAccent,
side: const BorderSide(color: _border), side: BorderSide(color: item.isRevoked ? Colors.grey : Colors.redAccent, width: 0.5),
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
), ),
child: const Text('로그아웃', style: TextStyle(fontSize: 13)), child: _isRevoking && !item.isRevoked
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.redAccent),
)
: Text(item.isRevoked ? '해지됨' : '연동 해지', style: const TextStyle(fontSize: 13)),
), ),
), ),
if (item.canLogout) const SizedBox(width: 8), ],
Expanded( ),
child: OutlinedButton( ],
onPressed: _isRevoking ? null : item.onRevoke, ),
style: OutlinedButton.styleFrom(
foregroundColor: Colors.redAccent,
side: const BorderSide(color: Colors.redAccent, width: 0.5),
padding: const EdgeInsets.symmetric(vertical: 8),
),
child: _isRevoking
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.redAccent),
)
: const Text('연동 해지', style: TextStyle(fontSize: 13)),
),
),
],
),
],
), ),
); );
} }
@@ -1212,6 +1233,7 @@ class _ActivityItem {
final String lastAuthAt; final String lastAuthAt;
final String status; final String status;
final bool canLogout; final bool canLogout;
final bool isRevoked;
final VoidCallback? onLogout; final VoidCallback? onLogout;
final VoidCallback? onRevoke; final VoidCallback? onRevoke;
@@ -1221,7 +1243,8 @@ class _ActivityItem {
required this.lastAuthAt, required this.lastAuthAt,
required this.status, required this.status,
required this.canLogout, required this.canLogout,
this.isRevoked = false,
this.onLogout, this.onLogout,
this.onRevoke, this.onRevoke,
}); });
} }