refactor(frontend): consolidate shared styles into centralized style system

- Create comprehensive shared.ts with design tokens and categorized styles:
  - layoutStyles: main, loader, content variants
  - cardStyles: card, tableCard, cardHeader
  - tableStyles: complete table styling
  - paginationStyles: pagination controls
  - formStyles: inputs, labels, errors
  - buttonStyles: primary, secondary, accent, danger variants
  - badgeStyles: status badges with color variants
  - bannerStyles: error/success banners
  - modalStyles: modal overlay and content
  - toastStyles: toast notifications
  - utilityStyles: divider, emptyState, etc.

- Refactor all page components to use shared styles:
  - page.tsx (counter)
  - audit/page.tsx
  - booking/page.tsx
  - appointments/page.tsx
  - profile/page.tsx
  - invites/page.tsx
  - admin/invites/page.tsx
  - admin/availability/page.tsx

- Reduce code duplication significantly (each page now has only
  truly page-specific styles)
- Maintain backwards compatibility with sharedStyles export
This commit is contained in:
counterweight 2025-12-21 23:45:47 +01:00
parent 4d9edd7fd4
commit 81cd34b0e7
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
9 changed files with 1148 additions and 1173 deletions

View file

@ -9,145 +9,16 @@ import { useRequireAuth } from "../hooks/useRequireAuth";
import { components } from "../generated/api";
import { formatDateTime } from "../utils/date";
import { getStatusDisplay } from "../utils/appointment";
import { sharedStyles } from "../styles/shared";
import {
layoutStyles,
typographyStyles,
bannerStyles,
badgeStyles,
buttonStyles,
} from "../styles/shared";
type AppointmentResponse = components["schemas"]["AppointmentResponse"];
const pageStyles: Record<string, React.CSSProperties> = {
main: {
minHeight: "100vh",
background: "linear-gradient(135deg, #0f0f23 0%, #1a1a3e 50%, #2d1b4e 100%)",
display: "flex",
flexDirection: "column",
},
loader: {
flex: 1,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontFamily: "'DM Sans', system-ui, sans-serif",
color: "rgba(255, 255, 255, 0.5)",
},
content: {
flex: 1,
padding: "2rem",
maxWidth: "800px",
margin: "0 auto",
width: "100%",
},
pageTitle: {
fontFamily: "'DM Sans', system-ui, sans-serif",
fontSize: "1.75rem",
fontWeight: 600,
color: "#fff",
marginBottom: "0.5rem",
},
pageSubtitle: {
fontFamily: "'DM Sans', system-ui, sans-serif",
color: "rgba(255, 255, 255, 0.5)",
fontSize: "0.9rem",
marginBottom: "1.5rem",
},
section: {
marginBottom: "2rem",
},
sectionTitle: {
fontFamily: "'DM Sans', system-ui, sans-serif",
fontSize: "1.1rem",
fontWeight: 500,
color: "#fff",
marginBottom: "1rem",
},
sectionTitleMuted: {
fontFamily: "'DM Sans', system-ui, sans-serif",
fontSize: "1.1rem",
fontWeight: 500,
color: "rgba(255, 255, 255, 0.5)",
marginBottom: "1rem",
},
appointmentList: {
display: "flex",
flexDirection: "column",
gap: "0.75rem",
},
appointmentCard: {
background: "rgba(255, 255, 255, 0.03)",
border: "1px solid rgba(255, 255, 255, 0.08)",
borderRadius: "12px",
padding: "1.25rem",
transition: "all 0.2s",
},
appointmentCardPast: {
opacity: 0.6,
background: "rgba(255, 255, 255, 0.01)",
},
appointmentHeader: {
display: "flex",
justifyContent: "space-between",
alignItems: "flex-start",
gap: "1rem",
},
appointmentTime: {
fontFamily: "'DM Sans', system-ui, sans-serif",
fontSize: "1rem",
fontWeight: 500,
color: "#fff",
marginBottom: "0.25rem",
},
appointmentNote: {
fontFamily: "'DM Sans', system-ui, sans-serif",
fontSize: "0.875rem",
color: "rgba(255, 255, 255, 0.5)",
marginBottom: "0.5rem",
},
statusBadge: {
fontFamily: "'DM Sans', system-ui, sans-serif",
fontSize: "0.75rem",
fontWeight: 500,
padding: "0.25rem 0.75rem",
borderRadius: "9999px",
display: "inline-block",
},
buttonGroup: {
display: "flex",
gap: "0.5rem",
},
cancelButton: {
fontFamily: "'DM Sans', system-ui, sans-serif",
padding: "0.35rem 0.75rem",
fontSize: "0.75rem",
background: "rgba(255, 255, 255, 0.05)",
border: "1px solid rgba(255, 255, 255, 0.1)",
borderRadius: "6px",
color: "rgba(255, 255, 255, 0.7)",
cursor: "pointer",
transition: "all 0.2s",
},
confirmButton: {
fontFamily: "'DM Sans', system-ui, sans-serif",
padding: "0.35rem 0.75rem",
fontSize: "0.75rem",
background: "rgba(239, 68, 68, 0.2)",
border: "1px solid rgba(239, 68, 68, 0.3)",
borderRadius: "6px",
color: "#f87171",
cursor: "pointer",
transition: "all 0.2s",
},
emptyState: {
fontFamily: "'DM Sans', system-ui, sans-serif",
color: "rgba(255, 255, 255, 0.4)",
textAlign: "center",
padding: "3rem",
},
emptyStateLink: {
color: "#a78bfa",
textDecoration: "none",
},
};
const styles = { ...sharedStyles, ...pageStyles };
export default function AppointmentsPage() {
const { user, isLoading, isAuthorized } = useRequireAuth({
requiredPermission: Permission.VIEW_OWN_APPOINTMENTS,
@ -195,8 +66,8 @@ export default function AppointmentsPage() {
if (isLoading) {
return (
<main style={styles.main}>
<div style={styles.loader}>Loading...</div>
<main style={layoutStyles.main}>
<div style={layoutStyles.loader}>Loading...</div>
</main>
);
}
@ -213,13 +84,13 @@ export default function AppointmentsPage() {
);
return (
<main style={styles.main}>
<main style={layoutStyles.main}>
<Header currentPage="appointments" />
<div style={styles.content}>
<h1 style={styles.pageTitle}>My Appointments</h1>
<p style={styles.pageSubtitle}>View and manage your booked appointments</p>
<h1 style={typographyStyles.pageTitle}>My Appointments</h1>
<p style={typographyStyles.pageSubtitle}>View and manage your booked appointments</p>
{error && <div style={styles.errorBanner}>{error}</div>}
{error && <div style={bannerStyles.errorBanner}>{error}</div>}
{isLoadingAppointments ? (
<div style={styles.emptyState}>Loading appointments...</div>
@ -249,7 +120,7 @@ export default function AppointmentsPage() {
{apt.note && <div style={styles.appointmentNote}>{apt.note}</div>}
<span
style={{
...styles.statusBadge,
...badgeStyles.badge,
background: status.bgColor,
color: status.textColor,
}}
@ -271,7 +142,7 @@ export default function AppointmentsPage() {
</button>
<button
onClick={() => setConfirmCancelId(null)}
style={styles.cancelButton}
style={buttonStyles.secondaryButton}
>
No
</button>
@ -279,7 +150,7 @@ export default function AppointmentsPage() {
) : (
<button
onClick={() => setConfirmCancelId(apt.id)}
style={styles.cancelButton}
style={buttonStyles.secondaryButton}
>
Cancel
</button>
@ -297,7 +168,7 @@ export default function AppointmentsPage() {
{/* Past/Cancelled Appointments */}
{pastOrCancelledAppointments.length > 0 && (
<div style={styles.section}>
<h2 style={styles.sectionTitleMuted}>
<h2 style={typographyStyles.sectionTitleMuted}>
Past & Cancelled ({pastOrCancelledAppointments.length})
</h2>
<div style={styles.appointmentList}>
@ -312,7 +183,7 @@ export default function AppointmentsPage() {
{apt.note && <div style={styles.appointmentNote}>{apt.note}</div>}
<span
style={{
...styles.statusBadge,
...badgeStyles.badge,
background: status.bgColor,
color: status.textColor,
}}
@ -331,3 +202,84 @@ export default function AppointmentsPage() {
</main>
);
}
// Page-specific styles
const styles: Record<string, React.CSSProperties> = {
content: {
flex: 1,
padding: "2rem",
maxWidth: "800px",
margin: "0 auto",
width: "100%",
},
section: {
marginBottom: "2rem",
},
sectionTitle: {
fontFamily: "'DM Sans', system-ui, sans-serif",
fontSize: "1.1rem",
fontWeight: 500,
color: "#fff",
marginBottom: "1rem",
},
appointmentList: {
display: "flex",
flexDirection: "column",
gap: "0.75rem",
},
appointmentCard: {
background: "rgba(255, 255, 255, 0.03)",
border: "1px solid rgba(255, 255, 255, 0.08)",
borderRadius: "12px",
padding: "1.25rem",
transition: "all 0.2s",
},
appointmentCardPast: {
opacity: 0.6,
background: "rgba(255, 255, 255, 0.01)",
},
appointmentHeader: {
display: "flex",
justifyContent: "space-between",
alignItems: "flex-start",
gap: "1rem",
},
appointmentTime: {
fontFamily: "'DM Sans', system-ui, sans-serif",
fontSize: "1rem",
fontWeight: 500,
color: "#fff",
marginBottom: "0.25rem",
},
appointmentNote: {
fontFamily: "'DM Sans', system-ui, sans-serif",
fontSize: "0.875rem",
color: "rgba(255, 255, 255, 0.5)",
marginBottom: "0.5rem",
},
buttonGroup: {
display: "flex",
gap: "0.5rem",
},
confirmButton: {
fontFamily: "'DM Sans', system-ui, sans-serif",
padding: "0.35rem 0.75rem",
fontSize: "0.75rem",
background: "rgba(239, 68, 68, 0.2)",
border: "1px solid rgba(239, 68, 68, 0.3)",
borderRadius: "6px",
color: "#f87171",
cursor: "pointer",
transition: "all 0.2s",
},
emptyState: {
fontFamily: "'DM Sans', system-ui, sans-serif",
color: "rgba(255, 255, 255, 0.4)",
textAlign: "center",
padding: "3rem",
},
emptyStateLink: {
color: "#a78bfa",
textDecoration: "none",
},
};