scaffolding hermes flow and audit logging
This commit is contained in:
@@ -1,7 +1,147 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SessionView: View {
|
||||
@EnvironmentObject private var analytics: HermesAnalyticsClient
|
||||
@EnvironmentObject private var localization: LocalizationStore
|
||||
@EnvironmentObject private var repository: HermesRepository
|
||||
|
||||
let onRetry: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Text("Session scaffold")
|
||||
let session = repository.currentSession
|
||||
let bannerMessage = hermesUserFacingErrorMessage(
|
||||
localization: localization,
|
||||
localeCode: localization.localeCode,
|
||||
error: repository.errorCause
|
||||
)
|
||||
let statusText: String
|
||||
if session != nil {
|
||||
statusText = localization.string(for: "session.status_ready")
|
||||
} else if bannerMessage != nil {
|
||||
statusText = localization.string(for: "session.status_error")
|
||||
} else {
|
||||
statusText = localization.string(for: "session.status_loading")
|
||||
}
|
||||
|
||||
return HermesCard {
|
||||
VStack(alignment: .leading, spacing: HermesTheme.sectionSpacing) {
|
||||
HermesSectionHeader(
|
||||
title: localization.string(for: "session.title"),
|
||||
subtitle: localization.string(for: "session.subtitle")
|
||||
)
|
||||
|
||||
if session == nil {
|
||||
if let bannerMessage {
|
||||
sessionErrorState(
|
||||
message: bannerMessage,
|
||||
retryText: localization.string(for: "common.retry"),
|
||||
onRetry: onRetry
|
||||
)
|
||||
} else {
|
||||
sessionLoadingState(
|
||||
title: statusText,
|
||||
subtitle: localization.string(for: "session.note")
|
||||
)
|
||||
}
|
||||
} else {
|
||||
sessionStatusBadge(text: statusText, warning: bannerMessage != nil)
|
||||
|
||||
if let bannerMessage {
|
||||
sessionBanner(message: bannerMessage)
|
||||
}
|
||||
|
||||
sessionRow(label: localization.string(for: "session.id_label"), value: session?.sessionId.uuidString ?? "--")
|
||||
sessionRow(label: localization.string(for: "session.user_id_label"), value: session?.userId.uuidString ?? "--")
|
||||
sessionRow(
|
||||
label: localization.string(for: "session.locale_label"),
|
||||
value: localization.localeName(for: session?.localeCode ?? localization.localeCode)
|
||||
)
|
||||
sessionRow(label: localization.string(for: "session.started_label"), value: session.map { Self.compactDateFormatter.string(from: $0.startedAt) } ?? "--")
|
||||
sessionRow(label: localization.string(for: "session.variant_label"), value: session?.experimentVariant ?? "--")
|
||||
sessionRow(label: localization.string(for: "session.app_version_label"), value: session?.appVersion ?? "--")
|
||||
sessionRow(label: localization.string(for: "session.device_model_label"), value: session?.deviceModel ?? "--")
|
||||
sessionRow(label: localization.string(for: "session.os_version_label"), value: session?.osVersion ?? "--")
|
||||
|
||||
Text(localization.string(for: "session.note"))
|
||||
.font(.callout)
|
||||
.foregroundStyle(HermesTheme.textSecondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
analytics.track("screen_viewed", attributes: ["screen_name": "session"])
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func sessionLoadingState(title: String, subtitle: String) -> some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(title)
|
||||
.font(.headline.weight(.semibold))
|
||||
.foregroundStyle(HermesTheme.textPrimary)
|
||||
Text(subtitle)
|
||||
.font(.callout)
|
||||
.foregroundStyle(HermesTheme.textSecondary)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func sessionErrorState(message: String, retryText: String, onRetry: @escaping () -> Void) -> some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text(message)
|
||||
.font(.callout)
|
||||
.foregroundStyle(HermesTheme.warning)
|
||||
|
||||
Button {
|
||||
onRetry()
|
||||
} label: {
|
||||
Text(retryText)
|
||||
}
|
||||
.buttonStyle(HermesSecondaryButtonStyle())
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func sessionStatusBadge(text: String, warning: Bool) -> some View {
|
||||
Text(text)
|
||||
.font(.caption.weight(.semibold))
|
||||
.foregroundStyle(warning ? HermesTheme.warning : HermesTheme.accent)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 8)
|
||||
.background((warning ? HermesTheme.warning : HermesTheme.accent).opacity(0.16))
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func sessionBanner(message: String) -> some View {
|
||||
Text(message)
|
||||
.font(.callout)
|
||||
.foregroundStyle(HermesTheme.warning)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 10)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(HermesTheme.warning.opacity(0.12))
|
||||
.clipShape(RoundedRectangle(cornerRadius: HermesTheme.insetRadius, style: .continuous))
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func sessionRow(label: String, value: String) -> some View {
|
||||
HStack {
|
||||
Text(label)
|
||||
.font(.callout)
|
||||
.foregroundStyle(HermesTheme.textSecondary)
|
||||
Spacer(minLength: 12)
|
||||
Text(value)
|
||||
.font(.callout.weight(.semibold))
|
||||
.foregroundStyle(HermesTheme.textPrimary)
|
||||
}
|
||||
}
|
||||
|
||||
private static let compactDateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter.timeZone = .current
|
||||
formatter.dateFormat = "yyyy-MM-dd HH:mm"
|
||||
return formatter
|
||||
}()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user