mirror of
https://github.com/temetro/temetro.git
synced 2026-08-22 07:56:38 +00:00
Patients page, clinical Settings re-theme, Polar-style user footer
- Patients: new /patients directory table (name, MRN, age·sex, status,
last seen, allergies) with search + Add patient; clicking a row opens
the record in the chat via /?patient=<#>. chat-panel reads that search
param (Suspense-wrapped) and runs the lookup once on arrival. Sidebar
"Patients" now links to /patients; lib/patients gains listPatients().
- Settings: re-themed to clinical tabs (Profile · Records · Signing ·
Care team · Developers) — clinician profile, patient notifications,
patient-owned-storage/signed-records features, and a Signing key panel.
Same layout/components, only content changed.
- Sidebar footer: Polar-style NavUser (avatar + name + chevron) opening a
menu upward — Settings · Docs & GitHub · Theme · Log out. Collapsed
("locked"): avatar only, name on hover (tooltip), menu opens to the
right. Placeholder identity (no auth yet).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,58 +4,52 @@ import { cn } from "@/lib/utils";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
CopyField,
|
||||
SettingsCard,
|
||||
SettingsSection,
|
||||
whiteButton,
|
||||
} from "@/components/settings/settings-parts";
|
||||
|
||||
export function BillingPanel() {
|
||||
export function SigningPanel() {
|
||||
return (
|
||||
<>
|
||||
<SettingsCard className="flex flex-col gap-6 p-6 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<h3 className="text-xl font-semibold tracking-tight">Early Member</h3>
|
||||
<h3 className="text-xl font-semibold tracking-tight">Signing key</h3>
|
||||
<Badge className="bg-emerald-500/15 text-emerald-400">Active</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
For our founding community of early members.
|
||||
Every change you make to a patient record is signed with this key, so patients
|
||||
can verify it came from you before approving it.
|
||||
</p>
|
||||
<Button className={cn("rounded-lg", whiteButton)}>Change plan</Button>
|
||||
<Button className={cn("rounded-lg", whiteButton)}>Rotate key</Button>
|
||||
</div>
|
||||
<div className="sm:text-right">
|
||||
<p className="text-3xl font-semibold tracking-tight">Free</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
4.00% + $0.40 per transaction
|
||||
</p>
|
||||
<p className="text-3xl font-semibold tracking-tight">Ed25519</p>
|
||||
<p className="text-sm text-muted-foreground">Created May 28, 2026</p>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
|
||||
<SettingsSection
|
||||
action={
|
||||
<Button className="rounded-lg" variant="secondary">
|
||||
Add payment method
|
||||
</Button>
|
||||
}
|
||||
description="Cards used to pay for your temetro subscription"
|
||||
title="Payment methods"
|
||||
description="The public key patients use to verify your signatures"
|
||||
title="Signing identity"
|
||||
>
|
||||
<SettingsCard className="flex items-center justify-center p-12">
|
||||
<p className="text-sm text-muted-foreground">No payment methods on file</p>
|
||||
<SettingsCard className="p-5">
|
||||
<CopyField
|
||||
description="Share or publish this fingerprint so patients can trust your changes"
|
||||
label="Public key fingerprint"
|
||||
value="ed25519:9f86 d081 884c 7d65 9a2f eaa0 c55a d015"
|
||||
/>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection
|
||||
action={
|
||||
<Button className="rounded-lg" variant="secondary">
|
||||
Edit
|
||||
</Button>
|
||||
}
|
||||
description="Used on invoices for your temetro subscription"
|
||||
title="Billing address"
|
||||
description="Changes you've signed that are waiting on the patient's approval"
|
||||
title="Signed records"
|
||||
>
|
||||
<SettingsCard className="p-5">
|
||||
<p className="text-sm">khalid</p>
|
||||
<SettingsCard className="flex items-center justify-center p-12">
|
||||
<p className="text-sm text-muted-foreground">No pending signatures</p>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
</>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { ChevronDown, Plus } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -12,59 +11,54 @@ import {
|
||||
SettingsCard,
|
||||
SettingsSection,
|
||||
ToggleRow,
|
||||
whiteButton,
|
||||
} from "@/components/settings/settings-parts";
|
||||
|
||||
const customerNotifications = [
|
||||
const patientNotifications = [
|
||||
{
|
||||
title: "Order confirmation",
|
||||
description: "Sent when a customer completes a one-time purchase",
|
||||
title: "New lab result",
|
||||
description: "Sent when a new lab result is available on a patient's chart",
|
||||
},
|
||||
{
|
||||
title: "Subscription confirmation",
|
||||
description: "Sent when a customer starts a new subscription",
|
||||
title: "Record updated",
|
||||
description: "Sent when a patient's record is updated by a member of the care team",
|
||||
},
|
||||
{
|
||||
title: "Subscription cycled",
|
||||
description: "Sent when a subscription automatically renews",
|
||||
title: "Approval requested",
|
||||
description: "Sent when a signed change is awaiting the patient's approval",
|
||||
},
|
||||
{
|
||||
title: "Trial converted",
|
||||
description: "Sent when a trial ends and the subscription becomes paid",
|
||||
title: "Change approved",
|
||||
description: "Sent when a patient approves a pending change to their record",
|
||||
},
|
||||
{
|
||||
title: "Renewal reminder",
|
||||
description: "Sent 7 days before a subscription with a long billing cycle renews",
|
||||
title: "New message",
|
||||
description: "Sent when a patient or another clinician sends a message",
|
||||
},
|
||||
{
|
||||
title: "Trial conversion reminder",
|
||||
description: "Sent before a trial ends and converts to a paid subscription",
|
||||
},
|
||||
{
|
||||
title: "Subscription updated",
|
||||
description: "Sent when a customer changes their subscription to a different product",
|
||||
title: "Visit scheduled",
|
||||
description: "Sent when an upcoming visit is added to a patient's record",
|
||||
},
|
||||
];
|
||||
|
||||
export function PreferencesPanel() {
|
||||
export function ProfilePanel() {
|
||||
return (
|
||||
<>
|
||||
<SettingsSection title="Organization">
|
||||
<SettingsSection title="Clinician profile">
|
||||
<SettingsCard className="space-y-6 p-5">
|
||||
<CopyField
|
||||
description="Unique identifier for your organization"
|
||||
label="Identifier"
|
||||
description="Your unique clinician identifier, used when signing records"
|
||||
label="Clinician ID"
|
||||
value="62a5278f-91c6-4912-b711-ee1c9c2f0a73"
|
||||
/>
|
||||
<CopyField
|
||||
description="Used for Customer Portal, Transaction Statements, etc."
|
||||
label="Organization Slug"
|
||||
value="khalid"
|
||||
description="Used in your public profile and the patient portal"
|
||||
label="Handle"
|
||||
value="dr-khalid"
|
||||
/>
|
||||
|
||||
<div className="flex items-end gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<FieldLabel>Logo</FieldLabel>
|
||||
<FieldLabel>Avatar</FieldLabel>
|
||||
<Avatar className="size-10 rounded-xl" size="lg">
|
||||
<AvatarFallback className="rounded-xl bg-muted text-sm font-medium">
|
||||
K
|
||||
@@ -72,55 +66,55 @@ export function PreferencesPanel() {
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className="flex-1 space-y-1.5">
|
||||
<FieldLabel required>Organization Name</FieldLabel>
|
||||
<Input defaultValue="khalid" />
|
||||
<FieldLabel required>Display name</FieldLabel>
|
||||
<Input defaultValue="Dr. Khalid" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<FieldLabel>Country</FieldLabel>
|
||||
<FieldLabel>Specialty</FieldLabel>
|
||||
<button
|
||||
className="flex h-9 w-full items-center justify-between rounded-3xl bg-input/50 px-3 text-sm text-muted-foreground transition-colors hover:bg-input/70"
|
||||
type="button"
|
||||
>
|
||||
Select country
|
||||
Select specialty
|
||||
<ChevronDown className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-1.5">
|
||||
<FieldLabel required>Website</FieldLabel>
|
||||
<Input placeholder="https://acme.com" />
|
||||
<FieldLabel>Clinic / practice</FieldLabel>
|
||||
<Input placeholder="e.g. Main Hospital" />
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<FieldLabel required>Support Email</FieldLabel>
|
||||
<Input placeholder="support@acme.com" />
|
||||
<FieldLabel required>Contact email</FieldLabel>
|
||||
<Input placeholder="clinician@example.org" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2.5">
|
||||
<div className="space-y-0.5">
|
||||
<FieldLabel>Social Media</FieldLabel>
|
||||
<FieldLabel>Professional links</FieldLabel>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Your personal social media links are used for identity verification. They
|
||||
will never be shown publicly.
|
||||
Registry or institutional profiles used to verify your identity. They are
|
||||
never shown to patients.
|
||||
</p>
|
||||
</div>
|
||||
<Button className="rounded-lg" size="sm" variant="outline">
|
||||
<Plus className="size-4" />
|
||||
Add Social
|
||||
Add link
|
||||
</Button>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection
|
||||
description="Emails automatically sent to customers for purchases, renewals, and other subscription lifecycle events"
|
||||
title="Customer notifications"
|
||||
description="Emails sent to patients about their records, results, and pending approvals"
|
||||
title="Patient notifications"
|
||||
>
|
||||
<div className="space-y-3">
|
||||
{customerNotifications.map((item) => (
|
||||
{patientNotifications.map((item) => (
|
||||
<ToggleRow
|
||||
defaultChecked
|
||||
description={item.description}
|
||||
@@ -132,63 +126,49 @@ export function PreferencesPanel() {
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection
|
||||
description="Emails sent to members of your organization for account and product activity"
|
||||
description="Notifications sent to you about your patients and the care team"
|
||||
title="Account notifications"
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<ToggleRow
|
||||
defaultChecked
|
||||
description="Send a notification when new orders are created"
|
||||
title="New Orders"
|
||||
description="Notify me when a patient approves or rejects a pending change"
|
||||
title="Pending approvals"
|
||||
/>
|
||||
<ToggleRow
|
||||
defaultChecked
|
||||
description="Send a notification when new subscriptions are created"
|
||||
title="New Subscriptions"
|
||||
description="Notify me when a patient shares a record with me"
|
||||
title="Records shared with me"
|
||||
/>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection
|
||||
description="Manage alpha & beta features for your organization"
|
||||
description="Manage alpha & beta features for your account"
|
||||
title="Features"
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<ToggleRow
|
||||
description="Show translated checkouts to your customers"
|
||||
title="Localized Checkout"
|
||||
description="Write records to the patient's own device instead of your database"
|
||||
title="Patient-owned storage (beta)"
|
||||
/>
|
||||
<ToggleRow
|
||||
description="Enable seat-based pricing for subscription products. Requires the member model to be enabled."
|
||||
title="Seat-Based Billing"
|
||||
description="Require a signature on every change you make to a patient record"
|
||||
title="Require signed records"
|
||||
/>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection
|
||||
description="Manage access tokens to authenticate with the temetro API"
|
||||
title="Developers"
|
||||
>
|
||||
<SettingsCard className="flex flex-col items-start gap-4 p-6">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
You don't have any active organization access tokens.
|
||||
</p>
|
||||
<Button className={cn("rounded-lg", whiteButton)} size="sm">
|
||||
Create token
|
||||
</Button>
|
||||
</SettingsCard>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection
|
||||
description="Irreversible actions for this organization"
|
||||
description="Irreversible actions for your account"
|
||||
title="Danger Zone"
|
||||
>
|
||||
<SettingsCard className="flex items-center justify-between gap-4 p-4">
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-sm font-medium">Delete Organization</p>
|
||||
<p className="text-sm font-medium">Delete account</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Permanently delete this organization and all associated data. This action
|
||||
cannot be undone.
|
||||
Permanently delete your temetro account and any locally stored signing
|
||||
keys. This action cannot be undone.
|
||||
</p>
|
||||
</div>
|
||||
<Button className="rounded-lg bg-destructive text-white hover:bg-destructive/90">
|
||||
|
||||
@@ -7,15 +7,15 @@ import {
|
||||
SettingsCard,
|
||||
SettingsSection,
|
||||
} from "@/components/settings/settings-parts";
|
||||
import { BillingPanel } from "@/components/settings/settings-billing";
|
||||
import { PreferencesPanel } from "@/components/settings/settings-preferences";
|
||||
import { SigningPanel } from "@/components/settings/settings-billing";
|
||||
import { ProfilePanel } from "@/components/settings/settings-preferences";
|
||||
|
||||
const TABS = [
|
||||
"Preferences",
|
||||
"Billing",
|
||||
"Members",
|
||||
"Webhooks",
|
||||
"Custom Fields",
|
||||
"Profile",
|
||||
"Records",
|
||||
"Signing",
|
||||
"Care team",
|
||||
"Developers",
|
||||
] as const;
|
||||
|
||||
type Tab = (typeof TABS)[number];
|
||||
@@ -37,7 +37,7 @@ function PlaceholderPanel({
|
||||
}
|
||||
|
||||
export function SettingsView() {
|
||||
const [tab, setTab] = useState<Tab>("Preferences");
|
||||
const [tab, setTab] = useState<Tab>("Profile");
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-3xl px-6 py-10">
|
||||
@@ -63,24 +63,24 @@ export function SettingsView() {
|
||||
</div>
|
||||
|
||||
<div className="mt-10 space-y-12">
|
||||
{tab === "Preferences" && <PreferencesPanel />}
|
||||
{tab === "Billing" && <BillingPanel />}
|
||||
{tab === "Members" && (
|
||||
{tab === "Profile" && <ProfilePanel />}
|
||||
{tab === "Records" && (
|
||||
<PlaceholderPanel
|
||||
description="Invite and manage members of your organization"
|
||||
title="Members"
|
||||
description="How patient records are sourced, stored, and displayed"
|
||||
title="Records"
|
||||
/>
|
||||
)}
|
||||
{tab === "Webhooks" && (
|
||||
{tab === "Signing" && <SigningPanel />}
|
||||
{tab === "Care team" && (
|
||||
<PlaceholderPanel
|
||||
description="Send event notifications to your own endpoints"
|
||||
title="Webhooks"
|
||||
description="Clinicians with access to this workspace"
|
||||
title="Care team"
|
||||
/>
|
||||
)}
|
||||
{tab === "Custom Fields" && (
|
||||
{tab === "Developers" && (
|
||||
<PlaceholderPanel
|
||||
description="Collect additional information from your customers"
|
||||
title="Custom Fields"
|
||||
description="Access tokens for the temetro API"
|
||||
title="Developers"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user