gitbay/Views/Account/WebSessionsView.swift
78 lines · 3308 bytes
1import SwiftUI
2
3/// Browsers signed in to the web as you, with a way to end one or all.
4struct WebSessionsView: View {
5
6 @State private var model: WebSessionsViewModel
7 @State private var revoking: WebSession?
8 @State private var revokingAll = false
9
10 init(client: GitbayClient) {
11 _model = State(initialValue: WebSessionsViewModel(client: client))
12 }
13
14 var body: some View {
15 List {
16 if let error = model.actionError {
17 Section { GBNotice(error, .gbWarn) }
18 }
19 Section {
20 ForEach(model.state.value ?? []) { session in
21 VStack(alignment: .leading, spacing: 3) {
22 Text("Used \(session.lastUsedAt.formatted(.relative(presentation: .named)))")
23 .font(.gbSans(.subheadline))
24 Text("Signed in \(session.createdAt.formatted(date: .abbreviated, time: .shortened)) · signs out \(session.expiresAt.formatted(.relative(presentation: .named))) if unused")
25 .font(.gbSans(.caption2))
26 .foregroundStyle(.secondary)
27 Text(session.id)
28 .font(.gbMono(.caption2))
29 .foregroundStyle(.secondary)
30 }
31 .padding(.vertical, 2)
32 .accessibilityElement(children: .combine)
33 .swipeActions {
34 Button("Sign Out", role: .destructive) { revoking = session }
35 .disabled(model.working)
36 }
37 }
38 } footer: {
39 if model.state.value != nil {
40 Text("Each is a browser signed in to the web. This app uses its token and stays signed in.")
41 }
42 }
43 if let sessions = model.state.value, sessions.count > 1 {
44 Section {
45 Button("Sign Out Everywhere", role: .destructive) { revokingAll = true }
46 .disabled(model.working)
47 .accessibilityIdentifier("sessions-revoke-all")
48 }
49 }
50 }
51 .overlay { LoadStateOverlay(state: model.state) }
52 .navigationTitle("Browser Sessions")
53 .navigationBarTitleDisplayMode(.inline)
54 .confirmationDialog(
55 "Sign this browser out? It will need to sign in again.",
56 isPresented: Binding(get: { revoking != nil }, set: { if !$0 { revoking = nil } }),
57 titleVisibility: .visible
58 ) {
59 Button("Sign Out", role: .destructive) {
60 if let session = revoking { Task { await model.revoke(id: session.id) } }
61 revoking = nil
62 }
63 Button("Cancel", role: .cancel) {}
64 }
65 .confirmationDialog(
66 "Sign out every browser? Each will need to sign in again.",
67 isPresented: $revokingAll,
68 titleVisibility: .visible
69 ) {
70 Button("Sign Out Everywhere", role: .destructive) {
71 Task { await model.revokeAll() }
72 }
73 Button("Cancel", role: .cancel) {}
74 }
75 .task { await model.load() }
76 .refreshable { await model.load() }
77 }
78}