gitbay/Views/Account/WebSessionsView.swift

36835f99e6d463f3e18c5c3fc3562bb92513f52b
gitbay-ios/gitbay/Views/Account/WebSessionsView.swift history · blame · raw

78 lines · 3308 bytes

1 symbol in this file
 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}