feat(healthchat): show default contacts while approval is pending

An unapproved account only gets the server's default contacts, which are
its way to reach support — list them instead of discarding them, and hide
the search field that cannot return anything.
This commit is contained in:
Sedat Qaja
2026-10-09 14:23:39 +02:00
parent 8124a72516
commit b70e77f7c5
8 changed files with 121 additions and 26 deletions
@@ -12,12 +12,21 @@ struct HealthChatMemberDirectoryScreen: View {
@Bindable var context: HealthChatMemberDirectoryScreenViewModel.Context
var body: some View {
// Searching is approved-only, and the search controller can't be applied conditionally.
if context.viewState.isApproved {
screen
.searchController(query: $context.searchQuery,
placeholder: HealthChatL10n.screenMemberDirectorySearchPlaceholder)
} else {
screen
}
}
private var screen: some View {
content
.navigationTitle(HealthChatL10n.screenMemberDirectoryTitle)
.navigationBarTitleDisplayMode(.inline)
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.searchController(query: $context.searchQuery,
placeholder: HealthChatL10n.screenMemberDirectorySearchPlaceholder)
.toolbar { toolbar }
.alert(item: $context.alertInfo)
}
@@ -53,8 +62,8 @@ struct HealthChatMemberDirectoryScreen: View {
case .loading:
ProgressView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
case .pendingApproval(let message):
statusView(icon: \.time, message: message)
case .pendingApproval(let message, let members):
pendingApprovalList(message: message, members: members)
case .failure:
failureView
case .members(let members) where members.isEmpty:
@@ -75,17 +84,44 @@ struct HealthChatMemberDirectoryScreen: View {
}
}
memberSection(members)
}
.compoundList()
}
/// The server allows an unapproved account the default contacts only, so they are listed
/// below the hint rather than withheld — they are the way to reach support.
private func pendingApprovalList(message: String, members: [HealthChatMember]) -> some View {
Form {
Section {
ForEach(members) { member in
HealthChatMemberListRow(member: member, mediaProvider: context.mediaProvider) {
context.send(viewAction: .selectMember(member))
} startChatAction: {
context.send(viewAction: .startChat(member))
}
ListRow(label: .default(title: HealthChatL10n.screenMemberDirectoryPendingTitle,
description: message,
icon: \.time),
kind: .label)
// Without the search field there is no other way to pick up an approval
// that arrives while the app is running.
ListRow(label: .default(title: HealthChatL10n.actionRefresh, icon: \.restart),
kind: .button {
context.send(viewAction: .refresh)
})
}
memberSection(members)
}
.compoundList()
}
private func memberSection(_ members: [HealthChatMember]) -> some View {
Section {
ForEach(members) { member in
HealthChatMemberListRow(member: member, mediaProvider: context.mediaProvider) {
context.send(viewAction: .selectMember(member))
} startChatAction: {
context.send(viewAction: .startChat(member))
}
}
}
.compoundList()
}
private var failureView: some View {
@@ -202,7 +238,22 @@ struct HealthChatMemberDirectoryScreen_Previews: PreviewProvider, TestablePrevie
apiService.profileCompletenessReturnValue = try? .success(JSONDecoder().decode(HealthChatProfileCompleteness.self,
from: Data(#"{ "isComplete": false, "message": "Profil unvollständig – Fehlende Angaben: Fachgebiet, Ort" }"#.utf8)))
case .pendingApproval:
apiService.membersSearchQueryReturnValue = .success(.init(users: [],
// The server hands an unapproved account its default contacts, nothing else.
let defaultContacts = try? JSONDecoder().decode([HealthChatMember].self, from: Data("""
[
{
"userId": "@natascha.katumba:healthchat.ch",
"displayName": "Natascha Katumba",
"practiceName": "HealthChat Support"
},
{
"userId": "@sophia.cagna:healthchat.ch",
"displayName": "Sophia Cagna",
"practiceName": "HealthChat Support"
}
]
""".utf8))
apiService.membersSearchQueryReturnValue = .success(.init(users: defaultContacts ?? [],
isApproved: false,
pendingMessage: "Ihr Konto wird derzeit geprüft. Sie erhalten Zugriff auf das Mitgliederverzeichnis, sobald Ihr Konto freigegeben wurde."))
case .failure: