From 625847786079284f79680b3140499ae50ccd4ec0 Mon Sep 17 00:00:00 2001 From: Admin Date: Wed, 6 May 2026 02:20:19 +0330 Subject: [PATCH] feature: button and badge components updated. --- frontend/src/app/(dashboard)/billing/page.tsx | 8 +-- .../app/(dashboard)/organizations/page.tsx | 29 ++++---- .../src/app/(dashboard)/patients/page.tsx | 3 - .../settings/subscriptions/page.tsx | 7 +- frontend/src/app/(dashboard)/staff/page.tsx | 30 +++----- frontend/src/components/ui/common/Badge.tsx | 68 +++++++++++++------ frontend/src/components/ui/common/Button.tsx | 24 +++---- .../OrganizationSelectorContent.tsx | 3 +- 8 files changed, 89 insertions(+), 83 deletions(-) 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'}