From 74bb163e8409c9d410c62fff95120177a11cff42 Mon Sep 17 00:00:00 2001 From: Jacob Maynard Date: Sun, 18 Jan 2026 21:11:12 -0600 Subject: [PATCH 01/12] add plan --- .../audits/color-token-migration-audit.md | 177 ++++++++++++++++++ packages/web/src/global.css | 97 +++++++++- 2 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 packages/docs/audits/color-token-migration-audit.md diff --git a/packages/docs/audits/color-token-migration-audit.md b/packages/docs/audits/color-token-migration-audit.md new file mode 100644 index 000000000..5db1e4cf8 --- /dev/null +++ b/packages/docs/audits/color-token-migration-audit.md @@ -0,0 +1,177 @@ +# Color Token Migration Audit + +This document summarizes the color usage patterns across the codebase and provides migration guidance for adopting the semantic token system. + +## Summary + +| Category | Occurrences | Files | Priority | +|----------|-------------|-------|----------| +| `gray-*` colors (should be `slate-*`) | 309+ | 50+ | High | +| `bg-blue-50` (app/hover backgrounds) | 33+ | 21 | High | +| `hover:bg-blue-50` patterns | 28 | 21 | Medium | +| `bg-blue-500/600/700` (primary buttons) | 143 | 88 | Medium | +| Status colors (`emerald/amber/red-50`) | 70+ | 30+ | Low | +| Focus ring patterns | 183 | 90 | Low | + +## New Tokens Added + +The following tokens were added to `global.css` to support common patterns: + +```css +/* Light mode */ +--color-primary-subtle: oklch(0.97 0.014 254.604); /* blue-50 */ +--color-destructive-subtle: oklch(0.971 0.013 17.38); /* red-50 */ +--color-success-subtle: oklch(0.979 0.021 166.113); /* emerald-50 */ +--color-warning-subtle: oklch(0.987 0.022 95.277); /* amber-50 */ + +/* Dark mode */ +--color-primary-subtle: oklch(0.282 0.091 267.935); /* blue-950 */ +--color-destructive-subtle: oklch(0.258 0.092 26.042); /* red-950 */ +--color-success-subtle: oklch(0.262 0.051 172.552); /* emerald-950 */ +--color-warning-subtle: oklch(0.344 0.075 66.288); /* amber-950 */ +``` + +## Migration Mappings + +### High Priority: Gray to Slate + +The codebase mixes `gray-*` and `slate-*` colors inconsistently. Slate has a subtle blue undertone that matches the primary brand color better. + +| Current | Token Replacement | Direct Replacement | +|---------|-------------------|-------------------| +| `bg-gray-50` | `bg-background` | `bg-slate-50` | +| `bg-gray-100` | `bg-muted` | `bg-slate-100` | +| `bg-gray-200` | `bg-secondary` | `bg-slate-200` | +| `text-gray-500` | `text-muted-foreground` | `text-slate-500` | +| `text-gray-600` | - | `text-slate-600` | +| `text-gray-700` | `text-secondary-foreground` | `text-slate-700` | +| `text-gray-900` | `text-foreground` | `text-slate-900` | +| `border-gray-200` | `border-border` | `border-slate-200` | +| `hover:bg-gray-50` | `hover:bg-muted` | `hover:bg-slate-50` | +| `hover:bg-gray-100` | `hover:bg-muted` | `hover:bg-slate-100` | + +### High Priority: Blue-50 Backgrounds + +`bg-blue-50` is used extensively for: +- Main app background (Layout.jsx) +- Selected/active states +- Hover states +- Icon container backgrounds +- Info banners + +| Current | Token Replacement | +|---------|-------------------| +| `bg-blue-50` | `bg-primary-subtle` | +| `hover:bg-blue-50` | `hover:bg-primary-subtle` | +| `group-hover:bg-blue-50` | `group-hover:bg-primary-subtle` | + +**Key file:** `Layout.jsx:80` uses `bg-blue-50` for the main app background. + +### Medium Priority: Primary Button Colors + +| Current | Token Replacement | +|---------|-------------------| +| `bg-blue-600` | `bg-primary` | +| `bg-blue-700` | - (use `hover:bg-primary/90`) | +| `hover:bg-blue-700` | `hover:bg-primary/90` | +| `text-blue-600` | `text-primary` | +| `text-blue-700` | `text-primary` | +| `border-blue-600` | `border-primary` | +| `ring-blue-600` | `ring-ring` | +| `focus:ring-blue-500` | `focus:ring-ring` | +| `focus:border-blue-500` | `focus:border-primary` | + +### Low Priority: Status Colors + +For status indicators, banners, and badges: + +| Current | Token Replacement | +|---------|-------------------| +| `bg-emerald-50` | `bg-success-subtle` | +| `bg-green-50` | `bg-success-subtle` | +| `bg-emerald-600` | `bg-success` | +| `text-emerald-600` | `text-success` | +| `bg-amber-50` | `bg-warning-subtle` | +| `bg-yellow-50` | `bg-warning-subtle` | +| `bg-amber-500` | `bg-warning` | +| `text-amber-600` | `text-warning` | +| `bg-red-50` | `bg-destructive-subtle` | +| `bg-red-600` | `bg-destructive` | +| `text-red-600` | `text-destructive` | + +## Files by Category + +### Layout and Core (migrate first) + +- `Layout.jsx` - Main app background (`bg-blue-50`) +- `Navbar.jsx` - Navigation styling +- `components/sidebar/*` - Sidebar components + +### Settings Pages (already partially migrated) + +- `ProfileSettings.jsx` - Uses tokens +- `PersonaSection.jsx` - Uses some tokens +- `AcademicInfoSection.jsx` - Uses some tokens +- Other settings pages - Need migration + +### UI Components + +- `components/ui/*.tsx` - Base UI components (buttons, inputs, etc.) +- These should use tokens for maximum theme flexibility + +### Project Views + +- `components/project/*` - Heavy use of blue-50 and gray colors +- `components/dashboard/*` - Mixed gray/slate usage + +### Mocks (low priority) + +- `components/mocks/*` - Demo/prototype components +- Can be migrated last or left as-is + +## Suggested Migration Order + +1. **Layout.jsx** - Change `bg-blue-50` to `bg-primary-subtle` +2. **UI components** (`components/ui/`) - Ensure base components use tokens +3. **Settings pages** - Complete token migration +4. **Dashboard components** - Migrate gray to slate/tokens +5. **Project components** - Largest surface area +6. **Remaining files** - Mocks and edge cases + +## Considerations + +### Gray vs Slate Decision + +The codebase should standardize on either: +- **Option A:** Use `slate-*` everywhere (has blue undertone, matches brand) +- **Option B:** Use `gray-*` everywhere (pure neutral) + +Recommendation: Use `slate-*` since it complements the blue primary color. + +### When NOT to Use Tokens + +Some cases may warrant keeping direct color references: +- One-off decorative elements +- Complex gradients +- Third-party component overrides +- Status badge colors that need to remain consistent regardless of theme + +### Opacity Variants + +For hover/focus states, prefer opacity variants over separate colors: +```jsx +// Preferred +className="bg-primary hover:bg-primary/90" + +// Avoid +className="bg-blue-600 hover:bg-blue-700" +``` + +## Testing Dark Mode + +After migration, test dark mode by adding `class="dark"` to the `` element: +```js +document.documentElement.classList.add('dark'); +``` + +All token-based colors should automatically switch to their dark variants. diff --git a/packages/web/src/global.css b/packages/web/src/global.css index 416529b23..7a6ca847e 100644 --- a/packages/web/src/global.css +++ b/packages/web/src/global.css @@ -13,17 +13,108 @@ --breakpoint-xs: 30rem; /* 480px */ - /* Brand color scale */ + /* Brand color scale --color-brand-50: #f4f7ff; --color-brand-100: #e6ecff; --color-brand-200: #cdd9ff; --color-brand-300: #aabaff; --color-brand-400: #7f93ff; - --color-brand-500: #5a6bff; /* brand anchor */ + --color-brand-500: #5a6bff; --color-brand-600: #4756e6; --color-brand-700: #3b46bf; --color-brand-800: #313a99; - --color-brand-900: #2a327a; + --color-brand-900: #2a327a; */ +} + +/* Tailwind theme extension - semantic color tokens using OKLCH */ +@theme { + /* Light theme colors (default) */ + --color-background: oklch(0.984 0.003 247.858); /* slate-50 */ + --color-foreground: oklch(0.208 0.042 265.755); /* slate-900 */ + + --color-card: oklch(1 0 0); /* white */ + --color-card-foreground: oklch(0.208 0.042 265.755); /* slate-900 */ + + --color-popover: oklch(1 0 0); /* white */ + --color-popover-foreground: oklch(0.208 0.042 265.755); /* slate-900 */ + + --color-primary: oklch(0.546 0.245 262.881); /* blue-600 */ + --color-primary-foreground: oklch(1 0 0); /* white */ + + --color-secondary: oklch(0.968 0.007 247.896); /* slate-100 */ + --color-secondary-foreground: oklch(0.372 0.044 257.287); /* slate-700 */ + + --color-muted: oklch(0.968 0.007 247.896); /* slate-100 */ + --color-muted-foreground: oklch(0.554 0.046 257.417); /* slate-500 */ + + --color-accent: oklch(0.968 0.007 247.896); /* slate-100 */ + --color-accent-foreground: oklch(0.208 0.042 265.755); /* slate-900 */ + + --color-destructive: oklch(0.577 0.245 27.325); /* red-600 */ + --color-destructive-foreground: oklch(1 0 0); /* white */ + + --color-success: oklch(0.596 0.145 163.225); /* emerald-600 */ + --color-success-foreground: oklch(1 0 0); /* white */ + + --color-warning: oklch(0.769 0.188 70.08); /* amber-500 */ + --color-warning-foreground: oklch(1 0 0); /* white */ + + --color-border: oklch(0.929 0.013 255.508); /* slate-200 */ + --color-border-subtle: oklch(0.968 0.007 247.896); /* slate-100 */ + + --color-input: oklch(0.929 0.013 255.508); /* slate-200 */ + --color-ring: oklch(0.546 0.245 262.881); /* blue-600 */ + + /* Subtle variants - light tinted backgrounds for states and banners */ + --color-primary-subtle: oklch(0.97 0.014 254.604); /* blue-50 */ + --color-destructive-subtle: oklch(0.971 0.013 17.38); /* red-50 */ + --color-success-subtle: oklch(0.979 0.021 166.113); /* emerald-50 */ + --color-warning-subtle: oklch(0.987 0.022 95.277); /* amber-50 */ +} + +/* Dark theme overrides */ +.dark { + --color-background: oklch(0.208 0.042 265.755); /* slate-900 */ + --color-foreground: oklch(0.984 0.003 247.858); /* slate-50 */ + + --color-card: oklch(0.279 0.041 260.031); /* slate-800 */ + --color-card-foreground: oklch(0.984 0.003 247.858); /* slate-50 */ + + --color-popover: oklch(0.279 0.041 260.031); /* slate-800 */ + --color-popover-foreground: oklch(0.984 0.003 247.858); /* slate-50 */ + + --color-primary: oklch(0.707 0.165 254.624); /* blue-400 */ + --color-primary-foreground: oklch(0.208 0.042 265.755); /* slate-900 */ + + --color-secondary: oklch(0.372 0.044 257.287); /* slate-700 */ + --color-secondary-foreground: oklch(0.929 0.013 255.508); /* slate-200 */ + + --color-muted: oklch(0.372 0.044 257.287); /* slate-700 */ + --color-muted-foreground: oklch(0.704 0.04 256.788); /* slate-400 */ + + --color-accent: oklch(0.372 0.044 257.287); /* slate-700 */ + --color-accent-foreground: oklch(0.984 0.003 247.858); /* slate-50 */ + + --color-destructive: oklch(0.637 0.237 25.331); /* red-500 */ + --color-destructive-foreground: oklch(1 0 0); /* white */ + + --color-success: oklch(0.765 0.177 163.223); /* emerald-400 */ + --color-success-foreground: oklch(0.208 0.042 265.755); /* slate-900 */ + + --color-warning: oklch(0.828 0.189 84.429); /* amber-400 */ + --color-warning-foreground: oklch(0.208 0.042 265.755); /* slate-900 */ + + --color-border: oklch(0.372 0.044 257.287); /* slate-700 */ + --color-border-subtle: oklch(0.279 0.041 260.031); /* slate-800 */ + + --color-input: oklch(0.372 0.044 257.287); /* slate-700 */ + --color-ring: oklch(0.707 0.165 254.624); /* blue-400 */ + + /* Subtle variants - dark tinted backgrounds for states and banners */ + --color-primary-subtle: oklch(0.282 0.091 267.935); /* blue-950 */ + --color-destructive-subtle: oklch(0.258 0.092 26.042); /* red-950 */ + --color-success-subtle: oklch(0.262 0.051 172.552); /* emerald-950 */ + --color-warning-subtle: oklch(0.344 0.075 66.288); /* amber-950 */ } /* scrollbar for sidebar */ From 3e1ab2cbba4daf52bc1ada9aad3551d2cdfe34ed Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 19 Jan 2026 03:12:53 +0000 Subject: [PATCH 02/12] Apply Prettier formatting --- .../audits/color-token-migration-audit.md | 121 +++++++++--------- 1 file changed, 63 insertions(+), 58 deletions(-) diff --git a/packages/docs/audits/color-token-migration-audit.md b/packages/docs/audits/color-token-migration-audit.md index 5db1e4cf8..c61d1b3fe 100644 --- a/packages/docs/audits/color-token-migration-audit.md +++ b/packages/docs/audits/color-token-migration-audit.md @@ -4,14 +4,14 @@ This document summarizes the color usage patterns across the codebase and provid ## Summary -| Category | Occurrences | Files | Priority | -|----------|-------------|-------|----------| -| `gray-*` colors (should be `slate-*`) | 309+ | 50+ | High | -| `bg-blue-50` (app/hover backgrounds) | 33+ | 21 | High | -| `hover:bg-blue-50` patterns | 28 | 21 | Medium | -| `bg-blue-500/600/700` (primary buttons) | 143 | 88 | Medium | -| Status colors (`emerald/amber/red-50`) | 70+ | 30+ | Low | -| Focus ring patterns | 183 | 90 | Low | +| Category | Occurrences | Files | Priority | +| --------------------------------------- | ----------- | ----- | -------- | +| `gray-*` colors (should be `slate-*`) | 309+ | 50+ | High | +| `bg-blue-50` (app/hover backgrounds) | 33+ | 21 | High | +| `hover:bg-blue-50` patterns | 28 | 21 | Medium | +| `bg-blue-500/600/700` (primary buttons) | 143 | 88 | Medium | +| Status colors (`emerald/amber/red-50`) | 70+ | 30+ | Low | +| Focus ring patterns | 183 | 90 | Low | ## New Tokens Added @@ -19,16 +19,16 @@ The following tokens were added to `global.css` to support common patterns: ```css /* Light mode */ ---color-primary-subtle: oklch(0.97 0.014 254.604); /* blue-50 */ ---color-destructive-subtle: oklch(0.971 0.013 17.38); /* red-50 */ ---color-success-subtle: oklch(0.979 0.021 166.113); /* emerald-50 */ ---color-warning-subtle: oklch(0.987 0.022 95.277); /* amber-50 */ +--color-primary-subtle: oklch(0.97 0.014 254.604); /* blue-50 */ +--color-destructive-subtle: oklch(0.971 0.013 17.38); /* red-50 */ +--color-success-subtle: oklch(0.979 0.021 166.113); /* emerald-50 */ +--color-warning-subtle: oklch(0.987 0.022 95.277); /* amber-50 */ /* Dark mode */ ---color-primary-subtle: oklch(0.282 0.091 267.935); /* blue-950 */ ---color-destructive-subtle: oklch(0.258 0.092 26.042); /* red-950 */ ---color-success-subtle: oklch(0.262 0.051 172.552); /* emerald-950 */ ---color-warning-subtle: oklch(0.344 0.075 66.288); /* amber-950 */ +--color-primary-subtle: oklch(0.282 0.091 267.935); /* blue-950 */ +--color-destructive-subtle: oklch(0.258 0.092 26.042); /* red-950 */ +--color-success-subtle: oklch(0.262 0.051 172.552); /* emerald-950 */ +--color-warning-subtle: oklch(0.344 0.075 66.288); /* amber-950 */ ``` ## Migration Mappings @@ -37,67 +37,68 @@ The following tokens were added to `global.css` to support common patterns: The codebase mixes `gray-*` and `slate-*` colors inconsistently. Slate has a subtle blue undertone that matches the primary brand color better. -| Current | Token Replacement | Direct Replacement | -|---------|-------------------|-------------------| -| `bg-gray-50` | `bg-background` | `bg-slate-50` | -| `bg-gray-100` | `bg-muted` | `bg-slate-100` | -| `bg-gray-200` | `bg-secondary` | `bg-slate-200` | -| `text-gray-500` | `text-muted-foreground` | `text-slate-500` | -| `text-gray-600` | - | `text-slate-600` | -| `text-gray-700` | `text-secondary-foreground` | `text-slate-700` | -| `text-gray-900` | `text-foreground` | `text-slate-900` | -| `border-gray-200` | `border-border` | `border-slate-200` | -| `hover:bg-gray-50` | `hover:bg-muted` | `hover:bg-slate-50` | -| `hover:bg-gray-100` | `hover:bg-muted` | `hover:bg-slate-100` | +| Current | Token Replacement | Direct Replacement | +| ------------------- | --------------------------- | -------------------- | +| `bg-gray-50` | `bg-background` | `bg-slate-50` | +| `bg-gray-100` | `bg-muted` | `bg-slate-100` | +| `bg-gray-200` | `bg-secondary` | `bg-slate-200` | +| `text-gray-500` | `text-muted-foreground` | `text-slate-500` | +| `text-gray-600` | - | `text-slate-600` | +| `text-gray-700` | `text-secondary-foreground` | `text-slate-700` | +| `text-gray-900` | `text-foreground` | `text-slate-900` | +| `border-gray-200` | `border-border` | `border-slate-200` | +| `hover:bg-gray-50` | `hover:bg-muted` | `hover:bg-slate-50` | +| `hover:bg-gray-100` | `hover:bg-muted` | `hover:bg-slate-100` | ### High Priority: Blue-50 Backgrounds `bg-blue-50` is used extensively for: + - Main app background (Layout.jsx) - Selected/active states - Hover states - Icon container backgrounds - Info banners -| Current | Token Replacement | -|---------|-------------------| -| `bg-blue-50` | `bg-primary-subtle` | -| `hover:bg-blue-50` | `hover:bg-primary-subtle` | +| Current | Token Replacement | +| ------------------------ | ------------------------------- | +| `bg-blue-50` | `bg-primary-subtle` | +| `hover:bg-blue-50` | `hover:bg-primary-subtle` | | `group-hover:bg-blue-50` | `group-hover:bg-primary-subtle` | **Key file:** `Layout.jsx:80` uses `bg-blue-50` for the main app background. ### Medium Priority: Primary Button Colors -| Current | Token Replacement | -|---------|-------------------| -| `bg-blue-600` | `bg-primary` | -| `bg-blue-700` | - (use `hover:bg-primary/90`) | -| `hover:bg-blue-700` | `hover:bg-primary/90` | -| `text-blue-600` | `text-primary` | -| `text-blue-700` | `text-primary` | -| `border-blue-600` | `border-primary` | -| `ring-blue-600` | `ring-ring` | -| `focus:ring-blue-500` | `focus:ring-ring` | -| `focus:border-blue-500` | `focus:border-primary` | +| Current | Token Replacement | +| ----------------------- | ----------------------------- | +| `bg-blue-600` | `bg-primary` | +| `bg-blue-700` | - (use `hover:bg-primary/90`) | +| `hover:bg-blue-700` | `hover:bg-primary/90` | +| `text-blue-600` | `text-primary` | +| `text-blue-700` | `text-primary` | +| `border-blue-600` | `border-primary` | +| `ring-blue-600` | `ring-ring` | +| `focus:ring-blue-500` | `focus:ring-ring` | +| `focus:border-blue-500` | `focus:border-primary` | ### Low Priority: Status Colors For status indicators, banners, and badges: -| Current | Token Replacement | -|---------|-------------------| -| `bg-emerald-50` | `bg-success-subtle` | -| `bg-green-50` | `bg-success-subtle` | -| `bg-emerald-600` | `bg-success` | -| `text-emerald-600` | `text-success` | -| `bg-amber-50` | `bg-warning-subtle` | -| `bg-yellow-50` | `bg-warning-subtle` | -| `bg-amber-500` | `bg-warning` | -| `text-amber-600` | `text-warning` | -| `bg-red-50` | `bg-destructive-subtle` | -| `bg-red-600` | `bg-destructive` | -| `text-red-600` | `text-destructive` | +| Current | Token Replacement | +| ------------------ | ----------------------- | +| `bg-emerald-50` | `bg-success-subtle` | +| `bg-green-50` | `bg-success-subtle` | +| `bg-emerald-600` | `bg-success` | +| `text-emerald-600` | `text-success` | +| `bg-amber-50` | `bg-warning-subtle` | +| `bg-yellow-50` | `bg-warning-subtle` | +| `bg-amber-500` | `bg-warning` | +| `text-amber-600` | `text-warning` | +| `bg-red-50` | `bg-destructive-subtle` | +| `bg-red-600` | `bg-destructive` | +| `text-red-600` | `text-destructive` | ## Files by Category @@ -143,6 +144,7 @@ For status indicators, banners, and badges: ### Gray vs Slate Decision The codebase should standardize on either: + - **Option A:** Use `slate-*` everywhere (has blue undertone, matches brand) - **Option B:** Use `gray-*` everywhere (pure neutral) @@ -151,6 +153,7 @@ Recommendation: Use `slate-*` since it complements the blue primary color. ### When NOT to Use Tokens Some cases may warrant keeping direct color references: + - One-off decorative elements - Complex gradients - Third-party component overrides @@ -159,17 +162,19 @@ Some cases may warrant keeping direct color references: ### Opacity Variants For hover/focus states, prefer opacity variants over separate colors: + ```jsx // Preferred -className="bg-primary hover:bg-primary/90" +className = 'bg-primary hover:bg-primary/90'; // Avoid -className="bg-blue-600 hover:bg-blue-700" +className = 'bg-blue-600 hover:bg-blue-700'; ``` ## Testing Dark Mode After migration, test dark mode by adding `class="dark"` to the `` element: + ```js document.documentElement.classList.add('dark'); ``` From 7de1b39e99df0d01202d660346e361bf12da7faa Mon Sep 17 00:00:00 2001 From: Jacob Maynard Date: Sun, 18 Jan 2026 22:19:22 -0600 Subject: [PATCH 03/12] update design system for most of settings. change avatar handling for oauth provider. change name handling. --- .../audits/color-token-migration-audit.md | 141 +++++++----- packages/web/src/Layout.jsx | 4 +- packages/web/src/components/Navbar.jsx | 14 +- .../web/src/components/admin/UserDetail.jsx | 6 +- .../web/src/components/admin/UserTable.jsx | 8 +- .../billing-observability/StripeToolsPage.jsx | 4 +- .../src/components/auth/CompleteProfile.jsx | 39 ++-- .../components/dashboard/DashboardHeader.jsx | 5 +- .../src/components/project/ProjectContext.jsx | 2 +- .../all-studies-tab/AssignReviewersModal.jsx | 3 +- .../study-card/StudyCardHeader.jsx | 3 +- .../project/overview-tab/AddMemberModal.jsx | 25 +- .../project/overview-tab/ChartSection.jsx | 2 +- .../project/overview-tab/OverviewTab.jsx | 11 +- .../overview-tab/ReviewerAssignment.jsx | 6 +- .../reconcile-tab/ReconciliationWrapper.jsx | 2 +- .../components/settings/SettingsSidebar.jsx | 30 +-- .../settings/pages/AcademicInfoSection.jsx | 40 ++-- .../settings/pages/AccountProviderCard.jsx | 25 +- .../settings/pages/BillingSettings.jsx | 50 ++-- .../settings/pages/GoogleDriveSettings.jsx | 10 +- .../settings/pages/IntegrationsSettings.jsx | 38 +-- .../settings/pages/LinkedAccountsSection.jsx | 62 ++--- .../settings/pages/MergeAccountsDialog.jsx | 56 ++--- .../settings/pages/NotificationsSettings.jsx | 40 ++-- .../settings/pages/PersonaSection.jsx | 14 +- .../settings/pages/PlansSettings.jsx | 20 +- .../settings/pages/ProfileInfoSection.jsx | 65 +++--- .../settings/pages/SecuritySettings.jsx | 78 +++---- .../settings/pages/SessionManagement.jsx | 38 +-- .../settings/pages/TwoFactorSetup.jsx | 126 +++++----- .../web/src/components/ui/alert-dialog.tsx | 32 ++- packages/web/src/components/ui/button.tsx | 14 +- packages/web/src/components/ui/checkbox.tsx | 12 +- packages/web/src/components/ui/dialog.tsx | 14 +- .../web/src/components/ui/file-upload.tsx | 25 +- packages/web/src/components/ui/menu.tsx | 15 +- packages/web/src/components/ui/popover.tsx | 10 +- packages/web/src/components/ui/progress.tsx | 8 +- packages/web/src/components/ui/select.tsx | 26 +-- packages/web/src/components/ui/steps.tsx | 2 +- packages/web/src/components/ui/switch.tsx | 6 +- packages/web/src/components/ui/tabs.tsx | 8 +- packages/web/src/primitives/avatarCache.js | 95 +------- packages/web/src/primitives/db.js | 1 + .../web/src/primitives/useProject/sync.js | 3 +- ...sputin.sql => 0000_productive_cardiac.sql} | 3 +- .../migrations/meta/0000_snapshot.json | 217 +++++++++++++----- .../workers/migrations/meta/_journal.json | 6 +- .../workers/src/__tests__/factories/user.js | 3 +- packages/workers/src/__tests__/helpers.js | 3 +- .../workers/src/__tests__/seed-schemas.js | 3 +- packages/workers/src/auth/config.ts | 77 ++++++- .../workers/src/commands/members/addMember.ts | 12 +- .../src/commands/projects/createProject.ts | 6 +- packages/workers/src/db/schema.ts | 3 +- .../workers/src/durable-objects/ProjectDoc.ts | 34 ++- .../src/durable-objects/dev-handlers.ts | 9 +- packages/workers/src/lib/avatar-copy.ts | 179 +++++++++++++++ packages/workers/src/lib/mock-templates.ts | 18 +- .../routes/__tests__/account-merge.test.js | 3 +- .../src/routes/__tests__/avatars.test.js | 3 +- .../src/routes/__tests__/google-drive.test.js | 3 +- .../src/routes/__tests__/members.test.js | 3 +- .../src/routes/__tests__/org-auth.test.js | 3 +- .../routes/__tests__/orgs-management.test.js | 3 +- .../workers/src/routes/__tests__/pdfs.test.js | 3 +- .../__tests__/project-invitations.test.js | 3 +- .../src/routes/__tests__/projects.test.js | 3 +- .../src/routes/__tests__/users.test.js | 9 +- packages/workers/src/routes/admin/database.ts | 14 +- packages/workers/src/routes/admin/projects.ts | 10 +- .../workers/src/routes/admin/stripe-tools.ts | 8 +- packages/workers/src/routes/admin/users.ts | 11 +- .../billing/handlers/invoiceHandlers.ts | 4 +- packages/workers/src/routes/database.ts | 4 +- packages/workers/src/routes/invitations.ts | 6 +- packages/workers/src/routes/members.ts | 28 ++- .../workers/src/routes/orgs/invitations.ts | 10 +- packages/workers/src/routes/orgs/members.ts | 19 +- packages/workers/src/routes/orgs/pdfs.ts | 6 +- packages/workers/src/routes/users.ts | 18 +- packages/workers/src/types/context.ts | 3 +- 83 files changed, 1180 insertions(+), 808 deletions(-) rename packages/workers/migrations/{0000_ambitious_mikhail_rasputin.sql => 0000_productive_cardiac.sql} (99%) create mode 100644 packages/workers/src/lib/avatar-copy.ts diff --git a/packages/docs/audits/color-token-migration-audit.md b/packages/docs/audits/color-token-migration-audit.md index 5db1e4cf8..243ec3ed4 100644 --- a/packages/docs/audits/color-token-migration-audit.md +++ b/packages/docs/audits/color-token-migration-audit.md @@ -2,16 +2,36 @@ This document summarizes the color usage patterns across the codebase and provides migration guidance for adopting the semantic token system. +## Migration Progress + +### Completed + +- [x] `Layout.jsx` - Main app background (`bg-primary-subtle`) +- [x] `Navbar.jsx` - User dropdown menu +- [x] **All UI components** (`components/ui/`): + - button.tsx, checkbox.tsx, switch.tsx, tabs.tsx + - dialog.tsx, alert-dialog.tsx, select.tsx, progress.tsx + - file-upload.tsx, steps.tsx, menu.tsx, popover.tsx + +### Remaining + +- [ ] Settings pages (`components/settings/`) +- [ ] Dashboard components (`components/dashboard/`) +- [ ] Project components (`components/project/`) +- [ ] Auth components (`components/auth/`) +- [ ] Sidebar components (`components/sidebar/`) +- [ ] Checklist components (`components/checklist/`) + ## Summary -| Category | Occurrences | Files | Priority | -|----------|-------------|-------|----------| -| `gray-*` colors (should be `slate-*`) | 309+ | 50+ | High | -| `bg-blue-50` (app/hover backgrounds) | 33+ | 21 | High | -| `hover:bg-blue-50` patterns | 28 | 21 | Medium | -| `bg-blue-500/600/700` (primary buttons) | 143 | 88 | Medium | -| Status colors (`emerald/amber/red-50`) | 70+ | 30+ | Low | -| Focus ring patterns | 183 | 90 | Low | +| Category | Occurrences | Files | Priority | +| --------------------------------------- | ----------- | ----- | -------- | +| `gray-*` colors (should be `slate-*`) | 309+ | 50+ | High | +| `bg-blue-50` (app/hover backgrounds) | 33+ | 21 | High | +| `hover:bg-blue-50` patterns | 28 | 21 | Medium | +| `bg-blue-500/600/700` (primary buttons) | 143 | 88 | Medium | +| Status colors (`emerald/amber/red-50`) | 70+ | 30+ | Low | +| Focus ring patterns | 183 | 90 | Low | ## New Tokens Added @@ -19,16 +39,16 @@ The following tokens were added to `global.css` to support common patterns: ```css /* Light mode */ ---color-primary-subtle: oklch(0.97 0.014 254.604); /* blue-50 */ ---color-destructive-subtle: oklch(0.971 0.013 17.38); /* red-50 */ ---color-success-subtle: oklch(0.979 0.021 166.113); /* emerald-50 */ ---color-warning-subtle: oklch(0.987 0.022 95.277); /* amber-50 */ +--color-primary-subtle: oklch(0.97 0.014 254.604); /* blue-50 */ +--color-destructive-subtle: oklch(0.971 0.013 17.38); /* red-50 */ +--color-success-subtle: oklch(0.979 0.021 166.113); /* emerald-50 */ +--color-warning-subtle: oklch(0.987 0.022 95.277); /* amber-50 */ /* Dark mode */ ---color-primary-subtle: oklch(0.282 0.091 267.935); /* blue-950 */ ---color-destructive-subtle: oklch(0.258 0.092 26.042); /* red-950 */ ---color-success-subtle: oklch(0.262 0.051 172.552); /* emerald-950 */ ---color-warning-subtle: oklch(0.344 0.075 66.288); /* amber-950 */ +--color-primary-subtle: oklch(0.282 0.091 267.935); /* blue-950 */ +--color-destructive-subtle: oklch(0.258 0.092 26.042); /* red-950 */ +--color-success-subtle: oklch(0.262 0.051 172.552); /* emerald-950 */ +--color-warning-subtle: oklch(0.344 0.075 66.288); /* amber-950 */ ``` ## Migration Mappings @@ -37,67 +57,68 @@ The following tokens were added to `global.css` to support common patterns: The codebase mixes `gray-*` and `slate-*` colors inconsistently. Slate has a subtle blue undertone that matches the primary brand color better. -| Current | Token Replacement | Direct Replacement | -|---------|-------------------|-------------------| -| `bg-gray-50` | `bg-background` | `bg-slate-50` | -| `bg-gray-100` | `bg-muted` | `bg-slate-100` | -| `bg-gray-200` | `bg-secondary` | `bg-slate-200` | -| `text-gray-500` | `text-muted-foreground` | `text-slate-500` | -| `text-gray-600` | - | `text-slate-600` | -| `text-gray-700` | `text-secondary-foreground` | `text-slate-700` | -| `text-gray-900` | `text-foreground` | `text-slate-900` | -| `border-gray-200` | `border-border` | `border-slate-200` | -| `hover:bg-gray-50` | `hover:bg-muted` | `hover:bg-slate-50` | -| `hover:bg-gray-100` | `hover:bg-muted` | `hover:bg-slate-100` | +| Current | Token Replacement | Direct Replacement | +| ------------------- | --------------------------- | -------------------- | +| `bg-gray-50` | `bg-background` | `bg-slate-50` | +| `bg-gray-100` | `bg-muted` | `bg-slate-100` | +| `bg-gray-200` | `bg-secondary` | `bg-slate-200` | +| `text-gray-500` | `text-muted-foreground` | `text-slate-500` | +| `text-gray-600` | - | `text-slate-600` | +| `text-gray-700` | `text-secondary-foreground` | `text-slate-700` | +| `text-gray-900` | `text-foreground` | `text-slate-900` | +| `border-gray-200` | `border-border` | `border-slate-200` | +| `hover:bg-gray-50` | `hover:bg-muted` | `hover:bg-slate-50` | +| `hover:bg-gray-100` | `hover:bg-muted` | `hover:bg-slate-100` | ### High Priority: Blue-50 Backgrounds `bg-blue-50` is used extensively for: + - Main app background (Layout.jsx) - Selected/active states - Hover states - Icon container backgrounds - Info banners -| Current | Token Replacement | -|---------|-------------------| -| `bg-blue-50` | `bg-primary-subtle` | -| `hover:bg-blue-50` | `hover:bg-primary-subtle` | +| Current | Token Replacement | +| ------------------------ | ------------------------------- | +| `bg-blue-50` | `bg-primary-subtle` | +| `hover:bg-blue-50` | `hover:bg-primary-subtle` | | `group-hover:bg-blue-50` | `group-hover:bg-primary-subtle` | **Key file:** `Layout.jsx:80` uses `bg-blue-50` for the main app background. ### Medium Priority: Primary Button Colors -| Current | Token Replacement | -|---------|-------------------| -| `bg-blue-600` | `bg-primary` | -| `bg-blue-700` | - (use `hover:bg-primary/90`) | -| `hover:bg-blue-700` | `hover:bg-primary/90` | -| `text-blue-600` | `text-primary` | -| `text-blue-700` | `text-primary` | -| `border-blue-600` | `border-primary` | -| `ring-blue-600` | `ring-ring` | -| `focus:ring-blue-500` | `focus:ring-ring` | -| `focus:border-blue-500` | `focus:border-primary` | +| Current | Token Replacement | +| ----------------------- | ----------------------------- | +| `bg-blue-600` | `bg-primary` | +| `bg-blue-700` | - (use `hover:bg-primary/90`) | +| `hover:bg-blue-700` | `hover:bg-primary/90` | +| `text-blue-600` | `text-primary` | +| `text-blue-700` | `text-primary` | +| `border-blue-600` | `border-primary` | +| `ring-blue-600` | `ring-ring` | +| `focus:ring-blue-500` | `focus:ring-ring` | +| `focus:border-blue-500` | `focus:border-primary` | ### Low Priority: Status Colors For status indicators, banners, and badges: -| Current | Token Replacement | -|---------|-------------------| -| `bg-emerald-50` | `bg-success-subtle` | -| `bg-green-50` | `bg-success-subtle` | -| `bg-emerald-600` | `bg-success` | -| `text-emerald-600` | `text-success` | -| `bg-amber-50` | `bg-warning-subtle` | -| `bg-yellow-50` | `bg-warning-subtle` | -| `bg-amber-500` | `bg-warning` | -| `text-amber-600` | `text-warning` | -| `bg-red-50` | `bg-destructive-subtle` | -| `bg-red-600` | `bg-destructive` | -| `text-red-600` | `text-destructive` | +| Current | Token Replacement | +| ------------------ | ----------------------- | +| `bg-emerald-50` | `bg-success-subtle` | +| `bg-green-50` | `bg-success-subtle` | +| `bg-emerald-600` | `bg-success` | +| `text-emerald-600` | `text-success` | +| `bg-amber-50` | `bg-warning-subtle` | +| `bg-yellow-50` | `bg-warning-subtle` | +| `bg-amber-500` | `bg-warning` | +| `text-amber-600` | `text-warning` | +| `bg-red-50` | `bg-destructive-subtle` | +| `bg-red-600` | `bg-destructive` | +| `text-red-600` | `text-destructive` | ## Files by Category @@ -143,6 +164,7 @@ For status indicators, banners, and badges: ### Gray vs Slate Decision The codebase should standardize on either: + - **Option A:** Use `slate-*` everywhere (has blue undertone, matches brand) - **Option B:** Use `gray-*` everywhere (pure neutral) @@ -151,6 +173,7 @@ Recommendation: Use `slate-*` since it complements the blue primary color. ### When NOT to Use Tokens Some cases may warrant keeping direct color references: + - One-off decorative elements - Complex gradients - Third-party component overrides @@ -159,17 +182,19 @@ Some cases may warrant keeping direct color references: ### Opacity Variants For hover/focus states, prefer opacity variants over separate colors: + ```jsx // Preferred -className="bg-primary hover:bg-primary/90" +className = 'bg-primary hover:bg-primary/90'; // Avoid -className="bg-blue-600 hover:bg-blue-700" +className = 'bg-blue-600 hover:bg-blue-700'; ``` ## Testing Dark Mode After migration, test dark mode by adding `class="dark"` to the `` element: + ```js document.documentElement.classList.add('dark'); ``` diff --git a/packages/web/src/Layout.jsx b/packages/web/src/Layout.jsx index 0d6d4c8a0..4e97d169e 100644 --- a/packages/web/src/Layout.jsx +++ b/packages/web/src/Layout.jsx @@ -77,7 +77,7 @@ export default function Layout(props) { return (
@@ -99,7 +99,7 @@ export default function Layout(props) { onWidthChange={handleWidthChange} /> -
{props.children}
+
{props.children}
{/* Dev Panel - global, context-aware */} diff --git a/packages/web/src/components/Navbar.jsx b/packages/web/src/components/Navbar.jsx index ffe5475ae..d84b73d88 100644 --- a/packages/web/src/components/Navbar.jsx +++ b/packages/web/src/components/Navbar.jsx @@ -164,21 +164,21 @@ export default function Navbar(props) { -
-
-
{user()?.name || 'User'}
-
{user()?.email}
+
+
+
{user()?.name || 'User'}
+
{user()?.email}
setShowUserMenu(false)} > Profile setShowUserMenu(false)} > Settings @@ -188,7 +188,7 @@ export default function Navbar(props) { setShowUserMenu(false); handleSignOut(); }} - class='block w-full px-4 py-2 text-left text-sm text-red-600 hover:bg-gray-100' + class='text-destructive hover:bg-muted block w-full px-4 py-2 text-left text-sm' > Sign Out diff --git a/packages/web/src/components/admin/UserDetail.jsx b/packages/web/src/components/admin/UserDetail.jsx index 0d4f67940..430a12c68 100644 --- a/packages/web/src/components/admin/UserDetail.jsx +++ b/packages/web/src/components/admin/UserDetail.jsx @@ -269,13 +269,11 @@ export default function UserDetail() {
-

- {userData().user.displayName || userData().user.name} -

+

{userData().user.name}

{userData().user.email}

diff --git a/packages/web/src/components/admin/UserTable.jsx b/packages/web/src/components/admin/UserTable.jsx index b7b420c4f..ce1246796 100644 --- a/packages/web/src/components/admin/UserTable.jsx +++ b/packages/web/src/components/admin/UserTable.jsx @@ -52,18 +52,14 @@ export default function UserTable(props) { const user = info.row.original; return (
- +
e.stopPropagation()} > - {user.displayName || user.name || 'Unknown'} + {user.name || 'Unknown'}

@{user.username}

diff --git a/packages/web/src/components/admin/billing-observability/StripeToolsPage.jsx b/packages/web/src/components/admin/billing-observability/StripeToolsPage.jsx index 91b460e67..f5434171c 100644 --- a/packages/web/src/components/admin/billing-observability/StripeToolsPage.jsx +++ b/packages/web/src/components/admin/billing-observability/StripeToolsPage.jsx @@ -381,9 +381,7 @@ export default function StripeToolsPage() { class='inline-flex items-center text-sm text-blue-600 hover:text-blue-700' > - {customerData().linkedUser.displayName || - customerData().linkedUser.name || - customerData().linkedUser.email} + {customerData().linkedUser.name || customerData().linkedUser.email}
= 2) { - setFirstName(nameParts[0]); - setLastName(nameParts.slice(1).join(' ')); - } else if (nameParts.length === 1) { - // If magic link created a placeholder name (e.g. the email), keep last name empty so user must provide it. - setFirstName(nameParts[0]); + // Use structured givenName/familyName from OAuth if available + if (!firstName().trim() && !lastName().trim()) { + if (currentUser?.givenName || currentUser?.familyName) { + setFirstName(currentUser.givenName || ''); + setLastName(currentUser.familyName || ''); + setHasAutofilledName(true); + } else if (currentUser?.name) { + // Fallback: split the name field + const nameParts = String(currentUser.name).trim().split(/\s+/).filter(Boolean); + if (nameParts.length >= 2) { + setFirstName(nameParts[0]); + setLastName(nameParts.slice(1).join(' ')); + } else if (nameParts.length === 1) { + setFirstName(nameParts[0]); + } + setHasAutofilledName(true); } - - setHasAutofilledName(true); } }); @@ -148,8 +154,8 @@ export default function CompleteProfile() { // Validate step 1 and proceed to step 2 function validateStep1() { setError(''); - if (!firstName().trim() || !lastName().trim()) { - setError('Please enter your first and last name'); + if (!firstName().trim()) { + setError('Please enter your first name'); return false; } return true; @@ -188,10 +194,14 @@ export default function CompleteProfile() { const urlParams = new URLSearchParams(window.location.search); try { - const fullName = `${firstName().trim()} ${lastName().trim()}`; + const givenName = firstName().trim(); + const familyName = lastName().trim(); + const fullName = [givenName, familyName].filter(Boolean).join(' '); await updateProfile({ name: fullName, + givenName: givenName || null, + familyName: familyName || null, persona: selectedPersona || 'other', profileCompletedAt: Math.floor(Date.now() / 1000), title: title() || null, @@ -348,7 +358,7 @@ export default function CompleteProfile() { class='mb-1 block text-xs font-semibold text-gray-700 sm:text-sm' for='last-name-input' > - Last Name + Last Name (optional) { - const name = props.user?.name || ''; - return name.split(' ')[0] || ''; + // Use structured givenName if available, fallback to name + if (props.user?.givenName) return props.user.givenName; + return props.user?.name || ''; }; return ( diff --git a/packages/web/src/components/project/ProjectContext.jsx b/packages/web/src/components/project/ProjectContext.jsx index 319ed0bb2..cfa46570d 100644 --- a/packages/web/src/components/project/ProjectContext.jsx +++ b/packages/web/src/components/project/ProjectContext.jsx @@ -43,7 +43,7 @@ export function ProjectProvider(props) { const getAssigneeName = userId => { if (!userId) return 'Unassigned'; const member = members().find(m => m.userId === userId); - return member?.displayName || member?.name || member?.email || 'Unknown'; + return member?.name || member?.email || 'Unknown'; }; const getMember = userId => { diff --git a/packages/web/src/components/project/all-studies-tab/AssignReviewersModal.jsx b/packages/web/src/components/project/all-studies-tab/AssignReviewersModal.jsx index 107b083cc..cea0413f1 100644 --- a/packages/web/src/components/project/all-studies-tab/AssignReviewersModal.jsx +++ b/packages/web/src/components/project/all-studies-tab/AssignReviewersModal.jsx @@ -50,8 +50,7 @@ export default function AssignReviewersModal(props) { // Convert members to SimpleSelect items format const memberItems = createMemo(() => { - const getMemberName = member => - member?.displayName || member?.name || member?.email || 'Unknown'; + const getMemberName = member => member?.name || member?.email || 'Unknown'; return [ { label: 'Unassigned', value: '' }, diff --git a/packages/web/src/components/project/all-studies-tab/study-card/StudyCardHeader.jsx b/packages/web/src/components/project/all-studies-tab/study-card/StudyCardHeader.jsx index 8787a0720..a64a1dfcc 100644 --- a/packages/web/src/components/project/all-studies-tab/study-card/StudyCardHeader.jsx +++ b/packages/web/src/components/project/all-studies-tab/study-card/StudyCardHeader.jsx @@ -79,8 +79,7 @@ export default function StudyCardHeader(props) { }; // Get member display name - const getMemberDisplayName = member => - member?.displayName || member?.name || member?.email || 'Unknown'; + const getMemberDisplayName = member => member?.name || member?.email || 'Unknown'; // Get avatar image source - only return URL if image exists, otherwise undefined for fallback const getAvatarSrc = member => { diff --git a/packages/web/src/components/project/overview-tab/AddMemberModal.jsx b/packages/web/src/components/project/overview-tab/AddMemberModal.jsx index c739df114..475bb2d25 100644 --- a/packages/web/src/components/project/overview-tab/AddMemberModal.jsx +++ b/packages/web/src/components/project/overview-tab/AddMemberModal.jsx @@ -83,7 +83,7 @@ export default function AddMemberModal(props) { const handleSimpleSelectUser = user => { setSimpleSelectedUser(user); - setSearchQuery(user.displayName || user.name || user.email); + setSearchQuery(user.name || user.email); setSearchResults([]); }; @@ -223,18 +223,13 @@ export default function AddMemberModal(props) { class='flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-blue-50' > - + - {getInitials(user.displayName || user.name || user.email)} + {getInitials(user.name || user.email)}
-

- {user.displayName || user.name || 'Unknown'} -

+

{user.name || 'Unknown'}

{user.email}

@@ -281,20 +276,14 @@ export default function AddMemberModal(props) { - {getInitials( - selectedUser().displayName || selectedUser().name || selectedUser().email, - )} + {getInitials(selectedUser().name || selectedUser().email)}
-

- {selectedUser().displayName || selectedUser().name || 'Unknown'} -

+

{selectedUser().name || 'Unknown'}

{selectedUser().email}

diff --git a/packages/web/src/components/project/overview-tab/ChartSection.jsx b/packages/web/src/components/project/overview-tab/ChartSection.jsx index c31f2dfbe..f9ab07098 100644 --- a/packages/web/src/components/project/overview-tab/ChartSection.jsx +++ b/packages/web/src/components/project/overview-tab/ChartSection.jsx @@ -141,7 +141,7 @@ export default function ChartSection(props) { const getAssigneeName = (userId, membersList) => { if (!userId) return 'Unassigned'; const member = membersList.find(m => m.userId === userId); - return member?.displayName || member?.name || member?.email || 'Unknown'; + return member?.name || member?.email || 'Unknown'; }; // Build raw chart data from studies and their checklists diff --git a/packages/web/src/components/project/overview-tab/OverviewTab.jsx b/packages/web/src/components/project/overview-tab/OverviewTab.jsx index 704262346..2579f67a5 100644 --- a/packages/web/src/components/project/overview-tab/OverviewTab.jsx +++ b/packages/web/src/components/project/overview-tab/OverviewTab.jsx @@ -348,15 +348,15 @@ export default function OverviewTab() { : member.image : undefined } - alt={member.displayName || member.name || member.email} + alt={member.name || member.email} /> - {getInitials(member.displayName || member.name || member.email)} + {getInitials(member.name || member.email)}

- {member.displayName || member.name || 'Unknown'} + {member.name || 'Unknown'} {isSelf && (you)}

0}> @@ -373,10 +373,7 @@ export default function OverviewTab() { @@ -219,7 +219,7 @@ export default function AcademicInfoSection() { diff --git a/packages/web/src/components/settings/pages/AccountProviderCard.jsx b/packages/web/src/components/settings/pages/AccountProviderCard.jsx index b2fe32767..5a8db0e81 100644 --- a/packages/web/src/components/settings/pages/AccountProviderCard.jsx +++ b/packages/web/src/components/settings/pages/AccountProviderCard.jsx @@ -64,11 +64,14 @@ export default function AccountProviderCard(props) { }); return ( -
+
{/* Provider icon */} -
- }> +
+ } + > {props.provider?.name}
@@ -76,11 +79,11 @@ export default function AccountProviderCard(props) { {/* Provider info */}
-

+

{props.provider?.name || props.account.providerId}

- + Primary @@ -88,13 +91,13 @@ export default function AccountProviderCard(props) {
-

Linked on {linkedDate()}

+

Linked on {linkedDate()}

@@ -115,7 +118,9 @@ export default function AccountProviderCard(props) { - Only sign-in method + + Only sign-in method + @@ -130,7 +135,7 @@ export default function AccountProviderCard(props) { @@ -264,15 +264,15 @@ export default function LinkedAccountsSection() { {/* Loading skeleton */}
-
+
-
+
-
-
+
+
-
+
@@ -295,8 +295,8 @@ export default function LinkedAccountsSection() { {/* Available providers to link */} 0}> -
0 ? 'mt-4 border-t border-gray-200 pt-4' : ''}> -

+

0 ? 'border-border mt-4 border-t pt-4' : ''}> +

{accounts()?.length > 0 ? 'Link another account:' : 'Link an account:'}

@@ -305,7 +305,7 @@ export default function LinkedAccountsSection() { @@ -350,7 +350,7 @@ export default function LinkedAccountsSection() { setMergeConflictProvider('google'); setShowMergeDialog(true); }} - class='rounded bg-amber-600 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-amber-700' + class='bg-warning hover:bg-warning/90 rounded px-3 py-1.5 text-xs font-medium text-white transition-colors' > Test Google Merge @@ -381,34 +381,34 @@ export default function LinkedAccountsSection() {
-

+

You won't be able to sign in with {unlinkProviderName()} anymore. Your CoRATES data will not be affected.

-

+

Note: If you sign in with {unlinkProviderName()} later without linking first, a new separate account will be created.

{/* Error message */} -
-

{unlinkError()}

+
+

{unlinkError()}

diff --git a/packages/web/src/components/settings/pages/MergeAccountsDialog.jsx b/packages/web/src/components/settings/pages/MergeAccountsDialog.jsx index bfbd6ffa4..e01060c14 100644 --- a/packages/web/src/components/settings/pages/MergeAccountsDialog.jsx +++ b/packages/web/src/components/settings/pages/MergeAccountsDialog.jsx @@ -274,7 +274,7 @@ export default function MergeAccountsDialog(props) {
-

+

Merging will combine all your projects, data, and sign-in methods into this account. The other account will be deleted.

@@ -282,13 +282,13 @@ export default function MergeAccountsDialog(props) {

@@ -392,7 +392,7 @@ export default function MergeAccountsDialog(props) {
-
-

After merging, you'll have:

-