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:
parent
4d9edd7fd4
commit
81cd34b0e7
9 changed files with 1148 additions and 1173 deletions
|
|
@ -3,11 +3,18 @@
|
|||
import { useEffect, useState, useCallback, useRef } from "react";
|
||||
import { bech32 } from "bech32";
|
||||
import { api, ApiError } from "../api";
|
||||
import { sharedStyles } from "../styles/shared";
|
||||
import { Header } from "../components/Header";
|
||||
import { useRequireAuth } from "../hooks/useRequireAuth";
|
||||
import { components } from "../generated/api";
|
||||
import constants from "../../../shared/constants.json";
|
||||
import {
|
||||
layoutStyles,
|
||||
cardStyles,
|
||||
formStyles,
|
||||
buttonStyles,
|
||||
toastStyles,
|
||||
utilityStyles,
|
||||
} from "../styles/shared";
|
||||
|
||||
// Use generated type from OpenAPI schema
|
||||
type ProfileData = components["schemas"]["ProfileResponse"];
|
||||
|
|
@ -253,8 +260,8 @@ export default function ProfilePage() {
|
|||
|
||||
if (isLoading || isLoadingProfile) {
|
||||
return (
|
||||
<main style={styles.main}>
|
||||
<div style={styles.loader}>Loading...</div>
|
||||
<main style={layoutStyles.main}>
|
||||
<div style={layoutStyles.loader}>Loading...</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
|
@ -266,13 +273,13 @@ export default function ProfilePage() {
|
|||
const canSubmit = hasChanges() && isValid() && !isSubmitting;
|
||||
|
||||
return (
|
||||
<main style={styles.main}>
|
||||
<main style={layoutStyles.main}>
|
||||
{/* Toast notification */}
|
||||
{toast && (
|
||||
<div
|
||||
style={{
|
||||
...styles.toast,
|
||||
...(toast.type === "success" ? styles.toastSuccess : styles.toastError),
|
||||
...toastStyles.toast,
|
||||
...(toast.type === "success" ? toastStyles.toastSuccess : toastStyles.toastError),
|
||||
}}
|
||||
>
|
||||
{toast.message}
|
||||
|
|
@ -281,44 +288,46 @@ export default function ProfilePage() {
|
|||
|
||||
<Header currentPage="profile" />
|
||||
|
||||
<div style={styles.content}>
|
||||
<div style={layoutStyles.contentCentered}>
|
||||
<div style={styles.profileCard}>
|
||||
<div style={styles.cardHeader}>
|
||||
<h1 style={styles.cardTitle}>My Profile</h1>
|
||||
<p style={styles.cardSubtitle}>Manage your contact information</p>
|
||||
<div style={cardStyles.cardHeader}>
|
||||
<h1 style={cardStyles.cardTitle}>My Profile</h1>
|
||||
<p style={cardStyles.cardSubtitle}>Manage your contact information</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} style={styles.form}>
|
||||
<form onSubmit={handleSubmit} style={formStyles.form}>
|
||||
{/* Login email - read only */}
|
||||
<div style={styles.field}>
|
||||
<label style={styles.label}>
|
||||
<div style={formStyles.field}>
|
||||
<label style={styles.labelWithBadge}>
|
||||
Login Email
|
||||
<span style={styles.readOnlyBadge}>Read only</span>
|
||||
<span style={utilityStyles.readOnlyBadge}>Read only</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
value={user.email}
|
||||
style={{ ...styles.input, ...styles.inputReadOnly }}
|
||||
style={{ ...formStyles.input, ...formStyles.inputReadOnly }}
|
||||
disabled
|
||||
/>
|
||||
<span style={styles.hint}>This is your login email and cannot be changed here.</span>
|
||||
<span style={formStyles.hint}>
|
||||
This is your login email and cannot be changed here.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Godfather - shown if user was invited */}
|
||||
{godfatherEmail && (
|
||||
<div style={styles.field}>
|
||||
<label style={styles.label}>
|
||||
<div style={formStyles.field}>
|
||||
<label style={styles.labelWithBadge}>
|
||||
Invited By
|
||||
<span style={styles.readOnlyBadge}>Read only</span>
|
||||
<span style={utilityStyles.readOnlyBadge}>Read only</span>
|
||||
</label>
|
||||
<div style={styles.godfatherBox}>
|
||||
<span style={styles.godfatherEmail}>{godfatherEmail}</span>
|
||||
</div>
|
||||
<span style={styles.hint}>The user who invited you to join.</span>
|
||||
<span style={formStyles.hint}>The user who invited you to join.</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={styles.divider} />
|
||||
<div style={utilityStyles.divider} />
|
||||
|
||||
<p style={styles.sectionLabel}>Contact Details</p>
|
||||
<p style={styles.sectionHint}>
|
||||
|
|
@ -326,8 +335,8 @@ export default function ProfilePage() {
|
|||
</p>
|
||||
|
||||
{/* Contact email */}
|
||||
<div style={styles.field}>
|
||||
<label htmlFor="contact_email" style={styles.label}>
|
||||
<div style={formStyles.field}>
|
||||
<label htmlFor="contact_email" style={formStyles.label}>
|
||||
Contact Email
|
||||
</label>
|
||||
<input
|
||||
|
|
@ -336,17 +345,19 @@ export default function ProfilePage() {
|
|||
value={formData.contact_email}
|
||||
onChange={handleInputChange("contact_email")}
|
||||
style={{
|
||||
...styles.input,
|
||||
...(errors.contact_email ? styles.inputError : {}),
|
||||
...formStyles.input,
|
||||
...(errors.contact_email ? formStyles.inputError : {}),
|
||||
}}
|
||||
placeholder="alternate@example.com"
|
||||
/>
|
||||
{errors.contact_email && <span style={styles.errorText}>{errors.contact_email}</span>}
|
||||
{errors.contact_email && (
|
||||
<span style={formStyles.errorText}>{errors.contact_email}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Telegram */}
|
||||
<div style={styles.field}>
|
||||
<label htmlFor="telegram" style={styles.label}>
|
||||
<div style={formStyles.field}>
|
||||
<label htmlFor="telegram" style={formStyles.label}>
|
||||
Telegram
|
||||
</label>
|
||||
<input
|
||||
|
|
@ -355,17 +366,17 @@ export default function ProfilePage() {
|
|||
value={formData.telegram}
|
||||
onChange={handleInputChange("telegram")}
|
||||
style={{
|
||||
...styles.input,
|
||||
...(errors.telegram ? styles.inputError : {}),
|
||||
...formStyles.input,
|
||||
...(errors.telegram ? formStyles.inputError : {}),
|
||||
}}
|
||||
placeholder="@username"
|
||||
/>
|
||||
{errors.telegram && <span style={styles.errorText}>{errors.telegram}</span>}
|
||||
{errors.telegram && <span style={formStyles.errorText}>{errors.telegram}</span>}
|
||||
</div>
|
||||
|
||||
{/* Signal */}
|
||||
<div style={styles.field}>
|
||||
<label htmlFor="signal" style={styles.label}>
|
||||
<div style={formStyles.field}>
|
||||
<label htmlFor="signal" style={formStyles.label}>
|
||||
Signal
|
||||
</label>
|
||||
<input
|
||||
|
|
@ -374,17 +385,17 @@ export default function ProfilePage() {
|
|||
value={formData.signal}
|
||||
onChange={handleInputChange("signal")}
|
||||
style={{
|
||||
...styles.input,
|
||||
...(errors.signal ? styles.inputError : {}),
|
||||
...formStyles.input,
|
||||
...(errors.signal ? formStyles.inputError : {}),
|
||||
}}
|
||||
placeholder="username.01"
|
||||
/>
|
||||
{errors.signal && <span style={styles.errorText}>{errors.signal}</span>}
|
||||
{errors.signal && <span style={formStyles.errorText}>{errors.signal}</span>}
|
||||
</div>
|
||||
|
||||
{/* Nostr npub */}
|
||||
<div style={styles.field}>
|
||||
<label htmlFor="nostr_npub" style={styles.label}>
|
||||
<div style={formStyles.field}>
|
||||
<label htmlFor="nostr_npub" style={formStyles.label}>
|
||||
Nostr (npub)
|
||||
</label>
|
||||
<input
|
||||
|
|
@ -393,19 +404,20 @@ export default function ProfilePage() {
|
|||
value={formData.nostr_npub}
|
||||
onChange={handleInputChange("nostr_npub")}
|
||||
style={{
|
||||
...styles.input,
|
||||
...(errors.nostr_npub ? styles.inputError : {}),
|
||||
...formStyles.input,
|
||||
...(errors.nostr_npub ? formStyles.inputError : {}),
|
||||
}}
|
||||
placeholder="npub1..."
|
||||
/>
|
||||
{errors.nostr_npub && <span style={styles.errorText}>{errors.nostr_npub}</span>}
|
||||
{errors.nostr_npub && <span style={formStyles.errorText}>{errors.nostr_npub}</span>}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
style={{
|
||||
...styles.button,
|
||||
...(!canSubmit ? styles.buttonDisabled : {}),
|
||||
...buttonStyles.primaryButton,
|
||||
marginTop: "1rem",
|
||||
...(!canSubmit ? buttonStyles.buttonDisabled : {}),
|
||||
}}
|
||||
disabled={!canSubmit}
|
||||
>
|
||||
|
|
@ -418,45 +430,14 @@ export default function ProfilePage() {
|
|||
);
|
||||
}
|
||||
|
||||
const pageStyles: Record<string, React.CSSProperties> = {
|
||||
// Page-specific styles
|
||||
const styles: Record<string, React.CSSProperties> = {
|
||||
profileCard: {
|
||||
background: "rgba(255, 255, 255, 0.03)",
|
||||
backdropFilter: "blur(10px)",
|
||||
border: "1px solid rgba(255, 255, 255, 0.08)",
|
||||
borderRadius: "24px",
|
||||
padding: "2.5rem",
|
||||
...cardStyles.card,
|
||||
width: "100%",
|
||||
maxWidth: "480px",
|
||||
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)",
|
||||
},
|
||||
cardHeader: {
|
||||
marginBottom: "2rem",
|
||||
},
|
||||
cardTitle: {
|
||||
fontFamily: "'Instrument Serif', Georgia, serif",
|
||||
fontSize: "2rem",
|
||||
fontWeight: 400,
|
||||
color: "#fff",
|
||||
margin: 0,
|
||||
letterSpacing: "-0.02em",
|
||||
},
|
||||
cardSubtitle: {
|
||||
fontFamily: "'DM Sans', system-ui, sans-serif",
|
||||
color: "rgba(255, 255, 255, 0.5)",
|
||||
marginTop: "0.5rem",
|
||||
fontSize: "0.95rem",
|
||||
},
|
||||
form: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "1.25rem",
|
||||
},
|
||||
field: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "0.5rem",
|
||||
},
|
||||
label: {
|
||||
labelWithBadge: {
|
||||
fontFamily: "'DM Sans', system-ui, sans-serif",
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
fontSize: "0.875rem",
|
||||
|
|
@ -465,32 +446,6 @@ const pageStyles: Record<string, React.CSSProperties> = {
|
|||
alignItems: "center",
|
||||
gap: "0.5rem",
|
||||
},
|
||||
readOnlyBadge: {
|
||||
fontSize: "0.7rem",
|
||||
fontWeight: 500,
|
||||
color: "rgba(255, 255, 255, 0.4)",
|
||||
background: "rgba(255, 255, 255, 0.08)",
|
||||
padding: "0.15rem 0.5rem",
|
||||
borderRadius: "4px",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: "0.05em",
|
||||
},
|
||||
input: {
|
||||
fontFamily: "'DM Sans', system-ui, sans-serif",
|
||||
padding: "0.875rem 1rem",
|
||||
fontSize: "1rem",
|
||||
background: "rgba(255, 255, 255, 0.05)",
|
||||
border: "1px solid rgba(255, 255, 255, 0.1)",
|
||||
borderRadius: "12px",
|
||||
color: "#fff",
|
||||
outline: "none",
|
||||
transition: "border-color 0.2s, box-shadow 0.2s",
|
||||
},
|
||||
inputReadOnly: {
|
||||
background: "rgba(255, 255, 255, 0.02)",
|
||||
color: "rgba(255, 255, 255, 0.5)",
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
godfatherBox: {
|
||||
padding: "0.875rem 1rem",
|
||||
background: "rgba(99, 102, 241, 0.08)",
|
||||
|
|
@ -502,26 +457,6 @@ const pageStyles: Record<string, React.CSSProperties> = {
|
|||
fontSize: "1rem",
|
||||
color: "rgba(129, 140, 248, 0.9)",
|
||||
},
|
||||
inputError: {
|
||||
border: "1px solid rgba(239, 68, 68, 0.5)",
|
||||
boxShadow: "0 0 0 2px rgba(239, 68, 68, 0.1)",
|
||||
},
|
||||
hint: {
|
||||
fontFamily: "'DM Sans', system-ui, sans-serif",
|
||||
fontSize: "0.75rem",
|
||||
color: "rgba(255, 255, 255, 0.4)",
|
||||
fontStyle: "italic",
|
||||
},
|
||||
errorText: {
|
||||
fontFamily: "'DM Sans', system-ui, sans-serif",
|
||||
fontSize: "0.75rem",
|
||||
color: "#fca5a5",
|
||||
},
|
||||
divider: {
|
||||
height: "1px",
|
||||
background: "rgba(255, 255, 255, 0.08)",
|
||||
margin: "0.75rem 0",
|
||||
},
|
||||
sectionLabel: {
|
||||
fontFamily: "'DM Sans', system-ui, sans-serif",
|
||||
fontSize: "0.875rem",
|
||||
|
|
@ -538,46 +473,4 @@ const pageStyles: Record<string, React.CSSProperties> = {
|
|||
margin: 0,
|
||||
marginBottom: "0.5rem",
|
||||
},
|
||||
button: {
|
||||
fontFamily: "'DM Sans', system-ui, sans-serif",
|
||||
marginTop: "1rem",
|
||||
padding: "1rem",
|
||||
fontSize: "1rem",
|
||||
fontWeight: 600,
|
||||
background: "linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)",
|
||||
color: "#fff",
|
||||
border: "none",
|
||||
borderRadius: "12px",
|
||||
cursor: "pointer",
|
||||
transition: "transform 0.2s, box-shadow 0.2s",
|
||||
boxShadow: "0 4px 14px rgba(99, 102, 241, 0.4)",
|
||||
},
|
||||
buttonDisabled: {
|
||||
opacity: 0.5,
|
||||
cursor: "not-allowed",
|
||||
boxShadow: "none",
|
||||
},
|
||||
toast: {
|
||||
position: "fixed",
|
||||
top: "1.5rem",
|
||||
right: "1.5rem",
|
||||
padding: "1rem 1.5rem",
|
||||
borderRadius: "12px",
|
||||
fontFamily: "'DM Sans', system-ui, sans-serif",
|
||||
fontSize: "0.875rem",
|
||||
fontWeight: 500,
|
||||
zIndex: 1000,
|
||||
animation: "slideIn 0.3s ease-out",
|
||||
boxShadow: "0 10px 25px rgba(0, 0, 0, 0.3)",
|
||||
},
|
||||
toastSuccess: {
|
||||
background: "rgba(34, 197, 94, 0.9)",
|
||||
color: "#fff",
|
||||
},
|
||||
toastError: {
|
||||
background: "rgba(239, 68, 68, 0.9)",
|
||||
color: "#fff",
|
||||
},
|
||||
};
|
||||
|
||||
const styles = { ...sharedStyles, ...pageStyles };
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue