-
+
Password
setPassword(e.target.value)}
required
- className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-primary focus-visible:ring-primary/20"
+ className="border-border bg-muted text-foreground placeholder:text-muted-foreground focus-visible:border-primary focus-visible:ring-primary/20"
/>
@@ -141,7 +141,7 @@ function LoginPageInner() {
-
+
Don't have an account?{" "}
-
+
+
-
+
Check your email
-
+
We've sent a confirmation link to{" "}
- {email} . Please check your
+ {email} . Please check your
inbox and click the link to verify your account.
@@ -116,7 +116,7 @@ function SignupPageInner() {
>
Back to sign in
@@ -128,8 +128,8 @@ function SignupPageInner() {
}
return (
-
-
+
+
{inviteToken ? (
@@ -138,10 +138,10 @@ function SignupPageInner() {
)}
-
+
{inviteToken ? "Create account & join" : "Create account"}
-
+
{inviteToken
? "Verify your email, then accept the invitation to join your team."
: "Get started with CRM Template for WhatsApp"}
@@ -156,7 +156,7 @@ function SignupPageInner() {
)}
-
+
Full name
setFullName(e.target.value)}
required
- className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-primary focus-visible:ring-primary/20"
+ className="border-border bg-muted text-foreground placeholder:text-muted-foreground focus-visible:border-primary focus-visible:ring-primary/20"
/>
-
+
Email
setEmail(e.target.value)}
required
- className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-primary focus-visible:ring-primary/20"
+ className="border-border bg-muted text-foreground placeholder:text-muted-foreground focus-visible:border-primary focus-visible:ring-primary/20"
/>
-
+
Password
setPassword(e.target.value)}
required
- className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-primary focus-visible:ring-primary/20"
+ className="border-border bg-muted text-foreground placeholder:text-muted-foreground focus-visible:border-primary focus-visible:ring-primary/20"
/>
-
+
Confirm password
setConfirmPassword(e.target.value)}
required
- className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-primary focus-visible:ring-primary/20"
+ className="border-border bg-muted text-foreground placeholder:text-muted-foreground focus-visible:border-primary focus-visible:ring-primary/20"
/>
@@ -224,7 +224,7 @@ function SignupPageInner() {
-
+
Already have an account?{" "}
router.push("/automations")}
- className="flex h-8 w-8 items-center justify-center rounded-md text-slate-400 transition-colors hover:bg-slate-800 hover:text-white"
+ className="flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
aria-label="Back"
>
-
{automation.name}
-
Execution logs
+
{automation.name}
+
Execution logs
{logs.length === 0 ? (
-
-
No executions yet
-
+
+
No executions yet
+
Trigger this automation to see runs here.
@@ -112,7 +112,7 @@ export default function AutomationLogsPage({
return (
{isOpen ? (
-
+
) : (
-
+
)}
-
+
{log.contact?.name ?? log.contact?.phone ?? "Unknown contact"}
-
+
{log.trigger_event} · {log.steps_executed?.length ?? 0} step
{log.steps_executed?.length === 1 ? "" : "s"}
-
+
{formatRelative(log.created_at)}
{isOpen && (
-
+
{log.error_message && (
{log.error_message}
@@ -150,7 +150,7 @@ export default function AutomationLogsPage({
))}
{(log.steps_executed ?? []).length === 0 && (
-
No steps recorded.
+
No steps recorded.
)}
@@ -196,9 +196,9 @@ function StepRow({ result }: { result: AutomationLogStepResult }) {
>
{ok ?
:
}
-
{result.step_type}
+
{result.step_type}
{result.detail && (
-
— {result.detail}
+
— {result.detail}
)}
)
diff --git a/src/app/(dashboard)/automations/page.tsx b/src/app/(dashboard)/automations/page.tsx
index ac5eaad..28760ae 100644
--- a/src/app/(dashboard)/automations/page.tsx
+++ b/src/app/(dashboard)/automations/page.tsx
@@ -160,8 +160,8 @@ export default function AutomationsPage() {
-
Automations
-
+
Automations
+
Build workflows that react to WhatsApp® events automatically.
@@ -178,7 +178,7 @@ export default function AutomationsPage() {
{showTemplates && (
- Quick-start templates
+ Quick-start templates
{TEMPLATE_ORDER.map((slug) => {
const t = AUTOMATION_TEMPLATES[slug]
@@ -187,13 +187,13 @@ export default function AutomationsPage() {
startFromTemplate(slug)}
- className="group flex flex-col items-start rounded-xl border border-slate-800 bg-slate-900 p-4 text-left transition-colors hover:border-primary/50 hover:bg-slate-900/80"
+ className="group flex flex-col items-start rounded-xl border border-border bg-card p-4 text-left transition-colors hover:border-primary/50 hover:bg-card/80"
>
- {t.name}
- {t.description}
+ {t.name}
+ {t.description}
)
})}
@@ -202,12 +202,12 @@ export default function AutomationsPage() {
)}
{automations.length === 0 ? (
-
+
-
No automations yet
-
+
No automations yet
+
Pick a template above or create one from scratch.
@@ -233,7 +233,7 @@ export default function AutomationsPage() {
Delete automation
This permanently removes{" "}
- {pendingDelete?.name} and its execution
+ {pendingDelete?.name} and its execution
history. This cannot be undone.
@@ -277,7 +277,7 @@ function AutomationCard({
}) {
const meta = triggerMeta(automation.trigger_type)
return (
-
+
-
+
{automation.name}
{automation.is_active && (
@@ -303,9 +303,9 @@ function AutomationCard({
)}
{automation.description && (
-
{automation.description}
+
{automation.description}
)}
-
+
diff --git a/src/app/(dashboard)/broadcasts/[id]/page.tsx b/src/app/(dashboard)/broadcasts/[id]/page.tsx
index 7511806..7449d51 100644
--- a/src/app/(dashboard)/broadcasts/[id]/page.tsx
+++ b/src/app/(dashboard)/broadcasts/[id]/page.tsx
@@ -50,15 +50,15 @@ interface StatCardProps {
function StatCard({ label, value, total, icon, color }: StatCardProps) {
const pct = total > 0 ? Math.round((value / total) * 100) : 0;
return (
-
+
-
{value.toLocaleString()}
-
{label}
+
{value.toLocaleString()}
+
{label}
);
}
@@ -77,8 +77,8 @@ interface FunnelStep {
function FunnelChart({ steps }: { steps: FunnelStep[] }) {
const max = Math.max(...steps.map((s) => s.value), 1);
return (
-
-
Funnel
+
+
Funnel
{steps.map((step) => {
const pctOfMax = Math.max(5, Math.round((step.value / max) * 100));
@@ -88,17 +88,17 @@ function FunnelChart({ steps }: { steps: FunnelStep[] }) {
: 0;
return (
-
+
{step.label}
-
+
-
+
{step.value.toLocaleString()}
-
+
({pctOfSent}%)
@@ -280,20 +280,20 @@ export default function BroadcastDetailPage() {
variant="outline"
size="icon"
onClick={() => router.push('/broadcasts')}
- className="border-slate-700"
+ className="border-border"
>
-
{broadcast.name}
+ {broadcast.name}
{status.label}
-
+
Template: {broadcast.template_name}
-
@@ -315,7 +315,7 @@ export default function BroadcastDetailPage() {
size="sm"
onClick={() => setConfirmDelete(false)}
disabled={deleting}
- className="h-7 border-slate-700 bg-transparent text-slate-300 hover:bg-slate-800"
+ className="h-7 border-border bg-transparent text-muted-foreground hover:bg-muted"
>
Cancel
@@ -354,7 +354,7 @@ export default function BroadcastDetailPage() {
value={broadcast.total_recipients}
total={broadcast.total_recipients}
icon={ }
- color="bg-slate-800 text-slate-300"
+ color="bg-muted text-muted-foreground"
/>
{/* Recipients Table */}
-
-
-
+
+
+
Recipients ({filteredRecipients.length}
{statusFilter !== 'all' ? ` of ${recipients.length}` : ''})
@@ -409,7 +409,7 @@ export default function BroadcastDetailPage() {
}
>
@@ -419,11 +419,11 @@ export default function BroadcastDetailPage() {
: getRecipientStatus(statusFilter).label}
-
+
setStatusFilter('all')}
className={
- statusFilter === 'all' ? 'text-primary' : 'text-slate-300'
+ statusFilter === 'all' ? 'text-primary' : 'text-popover-foreground'
}
>
All statuses
@@ -435,7 +435,7 @@ export default function BroadcastDetailPage() {
className={
statusFilter === s
? 'text-primary'
- : 'text-slate-300'
+ : 'text-popover-foreground'
}
>
{getRecipientStatus(s).label}
@@ -449,7 +449,7 @@ export default function BroadcastDetailPage() {
size="sm"
onClick={handleExport}
disabled={recipients.length === 0}
- className="border-slate-700 text-slate-300 hover:bg-slate-800"
+ className="border-border text-muted-foreground hover:bg-muted"
>
Export CSV
@@ -459,7 +459,7 @@ export default function BroadcastDetailPage() {
{filteredRecipients.length === 0 ? (
-
+
{recipients.length === 0
? 'No recipients found.'
: 'No recipients match this filter.'}
@@ -469,25 +469,25 @@ export default function BroadcastDetailPage() {
-
- Contact
- Phone
- Status
- Sent
- Delivered
- Read
- Error
+
+ Contact
+ Phone
+ Status
+ Sent
+ Delivered
+ Read
+ Error
{filteredRecipients.map((recipient) => {
const rStatus = getRecipientStatus(recipient.status);
return (
-
-
+
+
{recipient.contact?.name ?? 'Unknown'}
-
+
{recipient.contact?.phone ?? '-'}
@@ -497,17 +497,17 @@ export default function BroadcastDetailPage() {
{rStatus.label}
-
+
{recipient.sent_at
? new Date(recipient.sent_at).toLocaleString()
: '-'}
-
+
{recipient.delivered_at
? new Date(recipient.delivered_at).toLocaleString()
: '-'}
-
+
{recipient.read_at
? new Date(recipient.read_at).toLocaleString()
: '-'}
diff --git a/src/app/(dashboard)/broadcasts/new/page.tsx b/src/app/(dashboard)/broadcasts/new/page.tsx
index 9763620..bcbbb57 100644
--- a/src/app/(dashboard)/broadcasts/new/page.tsx
+++ b/src/app/(dashboard)/broadcasts/new/page.tsx
@@ -129,8 +129,8 @@ export default function NewBroadcastPage() {
{/* Header */}
-
New Broadcast
-
+
New Broadcast
+
Create and send a broadcast message to your contacts.
@@ -150,14 +150,14 @@ export default function NewBroadcastPage() {
? 'bg-primary text-primary-foreground'
: isActive
? 'border-2 border-primary bg-primary/10 text-primary'
- : 'border border-slate-700 bg-slate-800 text-slate-500'
+ : 'border border-border bg-muted text-muted-foreground'
}`}
>
{isCompleted ?
: index + 1}
{step.label}
@@ -166,7 +166,7 @@ export default function NewBroadcastPage() {
{index < steps.length - 1 && (
)}
diff --git a/src/app/(dashboard)/broadcasts/page.tsx b/src/app/(dashboard)/broadcasts/page.tsx
index 2583dd2..986000e 100644
--- a/src/app/(dashboard)/broadcasts/page.tsx
+++ b/src/app/(dashboard)/broadcasts/page.tsx
@@ -43,10 +43,10 @@ function RateCell({
const pct = percent(value, total);
return (
-
+
{pct}%
-
+
@@ -209,21 +209,21 @@ function StageColumn({
// restore the flex-1 share-the-row behavior. The droppable ref is
// on the inner messages region below — intentionally NOT here, so
// a drag over the column header doesn't highlight the whole column.
-
+
{/* 3px colored top border — sits above the column's padding */}
-
+
{stage.name}
-
+
{deals.length}
-
+
{formatCurrency(totalValue, currency)}
@@ -236,7 +236,7 @@ function StageColumn({
}`}
>
{deals.length === 0 ? (
-
+
Drop a deal here
) : (
@@ -255,7 +255,7 @@ function StageColumn({
variant="ghost"
size="sm"
onClick={() => onAddDeal(stage.id)}
- className="mt-3 w-full justify-start border border-dashed border-slate-700 bg-transparent text-slate-400 hover:border-slate-600 hover:bg-slate-800 hover:text-white"
+ className="mt-3 w-full justify-start border border-dashed border-border bg-transparent text-muted-foreground hover:border-border hover:bg-muted hover:text-foreground"
>
Add Deal
diff --git a/src/components/pipelines/pipeline-settings.tsx b/src/components/pipelines/pipeline-settings.tsx
index dffcbbd..47036b8 100644
--- a/src/components/pipelines/pipeline-settings.tsx
+++ b/src/components/pipelines/pipeline-settings.tsx
@@ -199,9 +199,9 @@ export function PipelineSettings({
return (
-
+
- Manage Pipeline
+ Manage Pipeline
{showDeleteConfirm ? (
@@ -212,7 +212,7 @@ export function PipelineSettings({
Delete Pipeline
-
+
This will archive all deals in this pipeline. This cannot be
undone.
@@ -222,7 +222,7 @@ export function PipelineSettings({
setShowDeleteConfirm(false)}
- className="border-slate-700 bg-transparent text-slate-300 hover:bg-slate-800"
+ className="border-border bg-transparent text-muted-foreground hover:bg-muted"
>
Cancel
@@ -239,16 +239,16 @@ export function PipelineSettings({
<>
- Pipeline Name
+ Pipeline Name
setName(e.target.value)}
- className="border-slate-700 bg-slate-800 text-white"
+ className="border-border bg-muted text-foreground"
/>
-
Stages
+
Stages
@@ -303,7 +305,7 @@ export function PipelineSettings({
value={newStageName}
onChange={(e) => setNewStageName(e.target.value)}
placeholder="New stage name"
- className="border-slate-700 bg-slate-800 text-sm text-white"
+ className="border-border bg-muted text-sm text-foreground"
onKeyDown={(e) => {
if (e.key === "Enter") handleAddStage();
}}
@@ -313,7 +315,7 @@ export function PipelineSettings({
size="sm"
onClick={handleAddStage}
disabled={!newStageName.trim()}
- className="shrink-0 border-slate-700 bg-transparent text-slate-300 hover:bg-slate-800"
+ className="shrink-0 border-border bg-transparent text-muted-foreground hover:bg-muted"
>
Add
@@ -324,14 +326,14 @@ export function PipelineSettings({
Create a new pipeline
-
+
setShowDeleteConfirm(true)}
@@ -342,7 +344,7 @@ export function PipelineSettings({
onOpenChange(false)}
- className="border-slate-700 bg-transparent text-slate-300 hover:bg-slate-800"
+ className="border-border bg-transparent text-muted-foreground hover:bg-muted"
>
Cancel
@@ -387,13 +389,13 @@ function SortableStageRow({
@@ -402,13 +404,13 @@ function SortableStageRow({
onNameChange(e.target.value)}
- className="h-7 flex-1 border-transparent bg-transparent text-sm text-white focus:border-slate-600"
+ className="h-7 flex-1 border-transparent bg-transparent text-sm text-foreground focus:border-border"
/>
@@ -431,14 +433,14 @@ function ColorSwatch({
setOpen((v) => !v)}
- className="h-4 w-4 rounded-full border border-slate-600"
+ className="h-4 w-4 rounded-full border border-border"
style={{ backgroundColor: value }}
aria-label="Change color"
/>
{open && (
<>
setOpen(false)} />
-
+
{colors.map((c) => (
))}
diff --git a/src/components/settings/appearance-panel.tsx b/src/components/settings/appearance-panel.tsx
index 3dfc0a5..0100686 100644
--- a/src/components/settings/appearance-panel.tsx
+++ b/src/components/settings/appearance-panel.tsx
@@ -1,53 +1,126 @@
"use client";
-import { Check } from "lucide-react";
+import { Check, Moon, Sun } from "lucide-react";
import { useTheme } from "@/hooks/use-theme";
-import { THEMES, type ThemeId } from "@/lib/themes";
+import { MODES, THEMES, type Mode, type ThemeId } from "@/lib/themes";
import { cn } from "@/lib/utils";
/**
- * Appearance panel — color-theme picker.
+ * Appearance panel — light/dark mode + accent-color picker.
*
- * Click a card → applies + persists immediately. No save button:
- * the whole change is a single CSS-variable swap on , there's
- * nothing to roll back. The active card carries a check chip + a
- * primary-tinted border so the current pick is obvious.
+ * Two independent controls: a mode toggle (light / dark) and the
+ * accent grid. Either applies + persists immediately. No save button:
+ * each change is a single attribute swap on , there's nothing
+ * to roll back.
*
* Persistence: localStorage only (device-scoped). The boot script in
- * layout.tsx replays the choice before first paint on subsequent
+ * layout.tsx replays both choices before first paint on subsequent
* loads.
*/
export function AppearancePanel() {
- const { theme, setTheme } = useTheme();
+ const { theme, setTheme, mode, setMode } = useTheme();
return (
-
-
-
Color theme
-
- Pick the accent color used across the app. All themes stay
- dark — only the primary color (buttons, active nav, badges)
- changes. Saved to this device.
-
+
+
+
+
Mode
+
+ Choose light or dark. Light is easier on the eyes in bright
+ rooms; dark is the original look. Works with any accent
+ color below. Saved to this device.
+
+
+
+
+ {MODES.map((m) => (
+ setMode(m)}
+ />
+ ))}
+
-
- {THEMES.map((t) => (
-
setTheme(t.id)}
- />
- ))}
+
+
+
+ Accent color
+
+
+ Pick the accent color used across the app — buttons, active
+ nav, and badges. Saved to this device.
+
+
+
+
+ {THEMES.map((t) => (
+ setTheme(t.id)}
+ />
+ ))}
+
);
}
+function ModeCard({
+ mode,
+ isActive,
+ onPick,
+}: {
+ mode: Mode;
+ isActive: boolean;
+ onPick: () => void;
+}) {
+ const isLight = mode === "light";
+ const Icon = isLight ? Sun : Moon;
+ return (
+
+
+
+
+
+ {mode}
+
+ {isActive && (
+
+
+ Active
+
+ )}
+
+ );
+}
+
function ThemeCard({
id,
name,
@@ -73,7 +146,7 @@ function ThemeCard({
"flex flex-col gap-3 rounded-lg border bg-card p-4 text-left transition-colors",
isActive
? "border-primary/60 ring-2 ring-primary/40"
- : "border-slate-800 hover:border-slate-700 hover:bg-slate-800/40",
+ : "border-border hover:border-border hover:bg-muted/40",
)}
>
@@ -93,8 +166,8 @@ function ThemeCard({
)}
-
{name}
-
@@ -103,9 +176,9 @@ function ThemeCard({
aria-hidden
>
-
-
-
+
+
+
Theme id: {id}
diff --git a/src/components/settings/custom-fields-settings.tsx b/src/components/settings/custom-fields-settings.tsx
index d49150a..dfee78f 100644
--- a/src/components/settings/custom-fields-settings.tsx
+++ b/src/components/settings/custom-fields-settings.tsx
@@ -12,14 +12,14 @@ export function CustomFieldsSettings() {
return (
-
Custom fields
-
+
Custom fields
+
Define extra contact fields (e.g. ZIP code, lead source). They appear
on every contact and in the “Update Contact Field” automation action.
-
+
diff --git a/src/components/settings/deals-settings.tsx b/src/components/settings/deals-settings.tsx
index 61deebf..ad3460c 100644
--- a/src/components/settings/deals-settings.tsx
+++ b/src/components/settings/deals-settings.tsx
@@ -68,13 +68,13 @@ export function DealsSettings() {
return (
-
+
-
+
Default currency
-
+
New deals default to this currency, and pipeline and
dashboard totals are shown in it. Existing deals keep the
currency they were saved with.
@@ -82,12 +82,12 @@ export function DealsSettings() {
-
Currency
+
Currency
setSelected(e.target.value)}
disabled={!canEditSettings || profileLoading}
- className="h-9 w-full rounded-lg border border-slate-700 bg-slate-800 px-2.5 text-sm text-white outline-none focus:border-primary focus:ring-1 focus:ring-primary disabled:cursor-not-allowed disabled:opacity-60"
+ className="h-9 w-full rounded-lg border border-border bg-muted px-2.5 text-sm text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary disabled:cursor-not-allowed disabled:opacity-60"
>
{CURRENCIES.map((c) => (
@@ -96,7 +96,7 @@ export function DealsSettings() {
))}
{!canEditSettings && (
-
+
Only account admins can change the default currency.
)}
diff --git a/src/components/settings/invite-member-dialog.tsx b/src/components/settings/invite-member-dialog.tsx
index 455bfb5..a767a6d 100644
--- a/src/components/settings/invite-member-dialog.tsx
+++ b/src/components/settings/invite-member-dialog.tsx
@@ -185,20 +185,20 @@ export function InviteMemberDialog({
onOpenChange(next);
}}
>
-
+
{result ? (
<>
-
+
Invite created
-
+
Share this link with your new teammate. They'll be able
to sign up (or sign in) and join the account as{' '}
- {result.role}
+ {result.role}
. The link is valid for{' '}
-
+
{result.expiresInDays} day{result.expiresInDays === 1 ? '' : 's'}
.
@@ -206,12 +206,12 @@ export function InviteMemberDialog({
-
Invite link
+
Invite link
e.currentTarget.select()}
/>
@@ -258,7 +258,7 @@ export function InviteMemberDialog({
-
+
onOpenChange(false)}
className="bg-primary hover:bg-primary/90 text-primary-foreground"
@@ -270,8 +270,8 @@ export function InviteMemberDialog({
) : (
<>
- Invite a teammate
-
+ Invite a teammate
+
Generate a one-time invite link. Share it via WhatsApp,
Slack, or any channel you like — no email service required.
@@ -279,12 +279,12 @@ export function InviteMemberDialog({
-
Role
+
Role
v && setRole(v as InviteRole)}
>
-
+
@@ -293,18 +293,18 @@ export function InviteMemberDialog({
Viewer
-
+
{ROLE_DESCRIPTIONS[role]}
- Link valid for
+ Link valid for
v && setExpiry(v)}
>
-
+
@@ -318,29 +318,29 @@ export function InviteMemberDialog({
-
+
Label{' '}
- (optional)
+ (optional)
setLabel(e.target.value)}
maxLength={MAX_LABEL_LEN}
- className="bg-slate-800 border-slate-700 text-white placeholder:text-slate-500"
+ className="bg-muted border-border text-foreground placeholder:text-muted-foreground"
/>
-
+
Helps you remember who you sent the link to in the pending
list below.
-
+
onOpenChange(false)}
- className="border-slate-700 text-slate-300 hover:bg-slate-800"
+ className="border-border text-muted-foreground hover:bg-muted"
>
Cancel
diff --git a/src/components/settings/members-tab.tsx b/src/components/settings/members-tab.tsx
index 14a83e9..aa30393 100644
--- a/src/components/settings/members-tab.tsx
+++ b/src/components/settings/members-tab.tsx
@@ -110,12 +110,14 @@ const ROLE_CHIP: Record<
agent: {
icon: UserCog,
label: 'Agent',
- className: 'border-slate-700 bg-slate-800 text-slate-300',
+ className: 'border-border bg-muted text-muted-foreground',
},
viewer: {
icon: UserIcon,
label: 'Viewer',
- className: 'border-slate-800 bg-slate-900 text-slate-500',
+ // Outline-only so it stays quieter than the filled Agent chip in
+ // both modes — bg-card would blend into a card surface in light mode.
+ className: 'border-border bg-transparent text-muted-foreground',
},
};
@@ -296,8 +298,8 @@ export function MembersTab() {
{/* Header + invite button */}
-
Account members
-
+
Account members
+
People with access to this account. Roles control what each
teammate can do.
@@ -314,9 +316,9 @@ export function MembersTab() {
{/* Roster */}
-
+
-
+
{members.map((member) => {
const roleMeta = ROLE_CHIP[member.role];
const RoleIcon = roleMeta.icon;
@@ -351,17 +353,17 @@ export function MembersTab() {
-
+
{member.full_name || 'Unnamed'}
{isSelf && (
-
+
You
)}
{member.email && (
-
+
{member.email}
)}
@@ -370,7 +372,7 @@ export function MembersTab() {
{/* Joined date stays desktop-only. The mobile row's
vertical density makes the joined date noise. */}
-
+
Joined {fmtDate(member.joined_at)}
@@ -394,7 +396,7 @@ export function MembersTab() {
}
>
@@ -446,11 +448,11 @@ export function MembersTab() {
-
-
+
+
Pending invitations
-
+
{invitations.length}
@@ -460,7 +462,7 @@ export function MembersTab() {
front (rather than letting the user discover it by
looking for a button) keeps it from feeling like a bug. */}
{invitations.length > 0 ? (
-
+
The plaintext invite URL is only shown once at creation
for security — to re-share, revoke the invite below and
create a new one.
@@ -468,22 +470,22 @@ export function MembersTab() {
) : null}
{invitations.length === 0 ? (
-
+
-
-
+
+
No pending invitations.
-
- Click Invite member {' '}
+
+ Click Invite member {' '}
above to generate a shareable link.
) : (
-
+
-