diff --git a/frontend/src/app/(dashboard)/billing/page.tsx b/frontend/src/app/(dashboard)/billing/page.tsx
index e92e5fe..0897555 100644
--- a/frontend/src/app/(dashboard)/billing/page.tsx
+++ b/frontend/src/app/(dashboard)/billing/page.tsx
@@ -1,7 +1,7 @@
// src/app/(dashboard)/billing/page.tsx
'use client';
import { useState } from 'react';
-import { Search, Filter, Plus } from 'lucide-react';
+import { Search } from 'lucide-react';
import { Button } from '@/components/ui/common/Button';
import { Input } from '@/components/ui/common/Input';
import { Badge } from '@/components/ui/common/Badge';
@@ -44,11 +44,9 @@ export default function BillingPage() {
Billing
@@ -129,7 +127,7 @@ export default function BillingPage() {
Paid
|
-
+ |
Status
|
@@ -158,7 +156,7 @@ export default function BillingPage() {
|
${invoice.paid}
|
-
+ |
@@ -238,7 +244,6 @@ export default function OrganizationsPage() {
disabled={!selectedResult}
onClick={() => void createLink()}
>
-
Request link
@@ -303,9 +308,12 @@ export default function OrganizationsPage() {
| {item.organizationName} |
{item.ownerEmail} |
-
- {item.status}
-
+
+ {formatOrganizationStatusLabel(item.status)}
+
|
{item.kind === 'INVITATION' &&
@@ -327,14 +335,7 @@ export default function OrganizationsPage() {
}
}}
>
- {copiedId === item.id ? (
-
- ) : (
-
- )}
-
- {copiedId === item.id ? 'Copied' : 'Copy link'}
-
+ {copiedId === item.id ? 'Copied' : 'Copy link'}
)}
|
diff --git a/frontend/src/app/(dashboard)/patients/page.tsx b/frontend/src/app/(dashboard)/patients/page.tsx
index 6a3aba9..4095d41 100644
--- a/frontend/src/app/(dashboard)/patients/page.tsx
+++ b/frontend/src/app/(dashboard)/patients/page.tsx
@@ -1,7 +1,6 @@
'use client';
import { useEffect, useMemo, useState } from 'react';
-import { Plus } from 'lucide-react';
import { Button } from '@/components/ui/common/Button';
import { patientsApi } from '@/lib/api/patients';
import { useAuth } from '@/lib/hooks/useAuth';
@@ -160,7 +159,6 @@ export default function PatientsPage() {
Patients
diff --git a/frontend/src/app/(dashboard)/settings/subscriptions/page.tsx b/frontend/src/app/(dashboard)/settings/subscriptions/page.tsx
index edc45aa..e5ab6b1 100644
--- a/frontend/src/app/(dashboard)/settings/subscriptions/page.tsx
+++ b/frontend/src/app/(dashboard)/settings/subscriptions/page.tsx
@@ -5,6 +5,7 @@ import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/lib/hooks/useAuth';
import { authApi } from '@/lib/api/auth';
+import { Button } from '@/components/ui/common/Button';
import type { SubscriptionAlertData } from '@/types/subscription';
const PLAN_OPTIONS = [
@@ -176,9 +177,9 @@ export default function SubscriptionsSettingsPage() {
);
})}
-
+
{purchaseNotice && (
{purchaseNotice}
diff --git a/frontend/src/app/(dashboard)/staff/page.tsx b/frontend/src/app/(dashboard)/staff/page.tsx
index e7c7a6d..4359e9b 100644
--- a/frontend/src/app/(dashboard)/staff/page.tsx
+++ b/frontend/src/app/(dashboard)/staff/page.tsx
@@ -16,10 +16,11 @@ import {
formatAccessSummary,
type FeaturePermState,
} from './staff-permission-form';
-import { UserPlus, Pencil, Trash2, Copy, Check, X, Clock3 } from 'lucide-react';
+import { Pencil, Trash2, Copy, Check, X } from 'lucide-react';
import { useAuth } from '@/lib/hooks/useAuth';
import { staffApi, type StaffMemberDto } from '@/lib/api/staff';
import { Button } from '@/components/ui/common/Button';
+import { Badge } from '@/components/ui/common/Badge';
import { Input } from '@/components/ui/common/Input';
import { Checkbox } from '@/components/ui/common/Checkbox';
import type { ApiError } from '@/types/api';
@@ -335,7 +336,6 @@ export default function StaffPage() {
className="shrink-0"
title={!canEdit ? 'Read-only access for this organization.' : undefined}
>
-
Invite member
@@ -410,14 +410,7 @@ export default function StaffPage() {
}
}}
>
- {copiedInviteMembershipId === lastInviteInfo.membershipId ? (
-
- ) : (
-
- )}
-
- {copiedInviteMembershipId === lastInviteInfo.membershipId ? 'Copied' : 'Copy link'}
-
+ {copiedInviteMembershipId === lastInviteInfo.membershipId ? 'Copied' : 'Copy link'}
@@ -438,7 +431,7 @@ export default function StaffPage() {
Name |
Email |
Role |
- Status |
+ Status |
Access |
Actions |
@@ -455,20 +448,13 @@ export default function StaffPage() {
Staff
)}
-
+ |
{m.isOwner || m.invitationStatus === 'ACTIVE' ? (
-
- Active
-
+ Active
) : m.invitationStatus === 'PENDING' ? (
-
-
- Pending
-
+ Pending
) : (
-
- Expired
-
+ Expired
)}
|
diff --git a/frontend/src/components/ui/common/Badge.tsx b/frontend/src/components/ui/common/Badge.tsx
index 9c5b844..afc8f5f 100644
--- a/frontend/src/components/ui/common/Badge.tsx
+++ b/frontend/src/components/ui/common/Badge.tsx
@@ -1,31 +1,59 @@
-//src/components/ui/Badge.tsx
import React from 'react';
-type BadgeVariant = 'success' | 'warning' | 'danger' | 'default';
+export type BadgeVariant = 'success' | 'warning' | 'danger' | 'default';
interface BadgeProps {
- children: React.ReactNode;
- variant?: BadgeVariant;
- className?: string;
+ children: React.ReactNode;
+ variant?: BadgeVariant;
+ className?: string;
+ /**
+ * Same pixel width for every badge (table columns).
+ * Set false only when the pill should shrink to the label.
+ */
+ fixedWidth?: boolean;
}
const variantStyles: Record = {
- success: 'bg-emerald-900/30 text-emerald-300 border-emerald-700/60',
- warning: 'bg-amber-900/30 text-amber-300 border-amber-700/60',
- danger: 'bg-red-950/30 text-red-300 border-red-700/60',
- default: 'bg-background-secondary text-text-secondary border-border',
+ success: 'bg-emerald-900/30 text-emerald-300 border-emerald-700/60',
+ warning: 'bg-amber-900/30 text-amber-300 border-amber-700/60',
+ danger: 'bg-red-950/30 text-red-300 border-red-700/60',
+ default: 'bg-background-secondary text-text-secondary border-border',
};
+/** Explicit width + height so every row matches; flex centers label optically. */
+const FIXED_LAYOUT_CLASS =
+ 'w-[8rem] min-w-[8rem] max-w-[8rem] shrink-0 h-7 px-2 py-0';
+
export function Badge({
- children,
- variant = 'default',
- className,
+ children,
+ variant = 'default',
+ className,
+ fixedWidth = true,
}: BadgeProps) {
- return (
-
- {children}
-
- );
-}
\ No newline at end of file
+ const layoutClass = fixedWidth
+ ? `${FIXED_LAYOUT_CLASS} justify-center text-center`
+ : 'min-h-[1.75rem] px-2.5 py-1 justify-center';
+
+ return (
+
+ {children}
+
+ );
+}
+
+/** Map organization link / invitation row status to badge variant. */
+export function organizationLinkStatusVariant(status: string): BadgeVariant {
+ switch (status) {
+ case 'ACTIVE':
+ return 'success';
+ case 'PENDING':
+ return 'warning';
+ case 'REJECTED':
+ case 'EXPIRED':
+ return 'danger';
+ default:
+ return 'default';
+ }
+}
diff --git a/frontend/src/components/ui/common/Button.tsx b/frontend/src/components/ui/common/Button.tsx
index 1794b75..71e1384 100644
--- a/frontend/src/components/ui/common/Button.tsx
+++ b/frontend/src/components/ui/common/Button.tsx
@@ -1,6 +1,4 @@
-// src/components/ui/Button.tsx
import React from 'react';
-import { Loader2 } from 'lucide-react';
type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'danger' | 'ghost';
type ButtonSize = 'sm' | 'md' | 'lg';
@@ -25,23 +23,22 @@ export const Button: React.FC = ({
}) => {
const baseClasses =
'inline-flex items-center justify-center rounded-[var(--radius-md)] font-medium transition-all duration-200 ' +
- 'focus:outline-none focus:ring-2 focus:ring-primary/40 disabled:opacity-50 disabled:cursor-not-allowed';
+ 'focus:outline-none focus:ring-2 focus:ring-primary/40 disabled:cursor-not-allowed';
const variantClasses: Record = {
primary:
- 'bg-primary text-primary-contrast hover:brightness-105 shadow-[0_0_0_1px_var(--color-primary-soft)]',
+ 'bg-primary text-white hover:opacity-90 disabled:opacity-60',
secondary:
- 'bg-surface-elevated text-text-primary border border-border hover:border-border-strong',
+ 'bg-surface-elevated text-text-primary border border-border hover:border-border-strong disabled:opacity-50',
outline:
- 'border border-border text-text-primary hover:bg-background-card/70',
+ 'border border-border text-text-primary hover:bg-background-card/70 disabled:opacity-50',
- danger:
- 'bg-red-600 text-white hover:bg-red-700',
+ danger: 'bg-red-600 text-white hover:bg-red-700 disabled:opacity-50',
ghost:
- 'text-text-secondary hover:text-text-primary hover:bg-background-card/70',
+ 'text-text-secondary hover:text-text-primary hover:bg-background-card/70 disabled:opacity-50',
};
const sizeClasses: Record = {
@@ -51,17 +48,16 @@ export const Button: React.FC = ({
};
const widthClass = fullWidth ? 'w-full' : '';
+ const loadingClass = isLoading ? 'opacity-70 animate-pulse pointer-events-none' : '';
return (
);
-};
\ No newline at end of file
+};
diff --git a/frontend/src/components/ui/organization/OrganizationSelectorContent.tsx b/frontend/src/components/ui/organization/OrganizationSelectorContent.tsx
index 29beead..d997d71 100644
--- a/frontend/src/components/ui/organization/OrganizationSelectorContent.tsx
+++ b/frontend/src/components/ui/organization/OrganizationSelectorContent.tsx
@@ -2,7 +2,7 @@
import { useState } from 'react';
import { useAuth } from '@/lib/hooks/useAuth';
-import { Building2, Beaker, Mail, Plus } from 'lucide-react';
+import { Building2, Beaker, Mail } from 'lucide-react';
import { Input } from '@/components/ui/common/Input';
import { Button } from '@/components/ui/common/Button';
@@ -55,7 +55,6 @@ export function OrganizationSelectorContent() {
setIsCreateOpen((prev) => !prev);
}}
>
-
{isCreateOpen ? 'Cancel' : 'Create Organization'}
|