From 0ec3d62aced15e87a11ec670d115e98ce762a3d1 Mon Sep 17 00:00:00 2001
From: Michael Chihlas
Date: Sat, 7 Mar 2026 22:32:56 -0500
Subject: [PATCH 1/3] refactor: enforce shared Modal component in remaining
custom modals
- ShareSessionModal: replaced custom modal markup with
- CreateCategoryModal: replaced custom modal markup with
- EditCategoryModal: replaced custom modal markup with
- All now get focus trapping, Escape close, body scroll lock for free
Co-Authored-By: Claude Opus 4.6
---
.../components/admin/CreateCategoryModal.tsx | 169 +++----
.../components/admin/EditCategoryModal.tsx | 168 +++----
.../components/session/ShareSessionModal.tsx | 469 +++++++++---------
3 files changed, 379 insertions(+), 427 deletions(-)
diff --git a/frontend/src/components/admin/CreateCategoryModal.tsx b/frontend/src/components/admin/CreateCategoryModal.tsx
index adda04b5..4c2f5e33 100644
--- a/frontend/src/components/admin/CreateCategoryModal.tsx
+++ b/frontend/src/components/admin/CreateCategoryModal.tsx
@@ -1,6 +1,6 @@
import { useState } from 'react'
-import { X } from 'lucide-react'
import { cn } from '@/lib/utils'
+import { Modal } from '@/components/common/Modal'
interface CreateCategoryModalProps {
isOpen: boolean
@@ -19,8 +19,6 @@ export function CreateCategoryModal({
const [description, setDescription] = useState('')
const [error, setError] = useState('')
- if (!isOpen) return null
-
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError('')
@@ -40,7 +38,6 @@ export function CreateCategoryModal({
name: name.trim(),
description: description.trim()
})
- // Reset form on success
setName('')
setDescription('')
} catch {
@@ -58,102 +55,90 @@ export function CreateCategoryModal({
}
return (
-
-
- {/* Header */}
-
-
Create Category
+
-
+ Cancel
+
+
+ {isSaving ? 'Creating...' : 'Create Category'}
-
- {/* Form */}
-
+
+
+ Description (optional)
+
+ setDescription(e.target.value)}
+ disabled={isSaving}
+ rows={3}
+ placeholder="Brief description of this category..."
+ className={cn(
+ 'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
+ 'placeholder:text-muted-foreground',
+ 'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
+ 'disabled:opacity-50'
+ )}
+ />
+
+
+
)
}
diff --git a/frontend/src/components/admin/EditCategoryModal.tsx b/frontend/src/components/admin/EditCategoryModal.tsx
index 9af88445..c51d6f0c 100644
--- a/frontend/src/components/admin/EditCategoryModal.tsx
+++ b/frontend/src/components/admin/EditCategoryModal.tsx
@@ -1,7 +1,7 @@
import { useState } from 'react'
-import { X } from 'lucide-react'
import { cn } from '@/lib/utils'
import type { StepCategoryListItem } from '@/types'
+import { Modal } from '@/components/common/Modal'
interface EditCategoryModalProps {
isOpen: boolean
@@ -33,7 +33,7 @@ export function EditCategoryModal({
setPrevCategoryId(null)
}
- if (!isOpen || !category) return null
+ if (!category) return null
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
@@ -67,102 +67,90 @@ export function EditCategoryModal({
}
return (
-
-
- {/* Header */}
-
-
Edit Category
+
-
+ Cancel
+
+
+ {isSaving ? 'Saving...' : 'Save Changes'}
-
- {/* Form */}
-
- {/* Error Message */}
- {error && (
-
- {error}
-
- )}
-
- {/* Name Field */}
-
-
- Category Name *
-
-
setName(e.target.value)}
- disabled={isSaving}
- maxLength={100}
- placeholder="e.g., Network Troubleshooting"
- required
- className={cn(
- 'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
- 'placeholder:text-muted-foreground',
- 'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
- 'disabled:opacity-50'
- )}
- />
-
- {name.length}/100 characters
-
+ }
+ >
+
+ {error && (
+
+ {error}
+ )}
- {/* Description Field */}
-
-
- Description (optional)
-
- setDescription(e.target.value)}
- disabled={isSaving}
- rows={3}
- placeholder="Brief description of this category..."
- className={cn(
- 'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
- 'placeholder:text-muted-foreground',
- 'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
- 'disabled:opacity-50'
- )}
- />
-
+
+
+ Category Name *
+
+
setName(e.target.value)}
+ disabled={isSaving}
+ maxLength={100}
+ placeholder="e.g., Network Troubleshooting"
+ required
+ className={cn(
+ 'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
+ 'placeholder:text-muted-foreground',
+ 'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
+ 'disabled:opacity-50'
+ )}
+ />
+
+ {name.length}/100 characters
+
+
- {/* Actions */}
-
-
- Cancel
-
-
- {isSaving ? 'Saving...' : 'Save Changes'}
-
-
-
-
-
+
+
+ Description (optional)
+
+ setDescription(e.target.value)}
+ disabled={isSaving}
+ rows={3}
+ placeholder="Brief description of this category..."
+ className={cn(
+ 'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
+ 'placeholder:text-muted-foreground',
+ 'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
+ 'disabled:opacity-50'
+ )}
+ />
+
+
+
)
}
diff --git a/frontend/src/components/session/ShareSessionModal.tsx b/frontend/src/components/session/ShareSessionModal.tsx
index 7ea2b23f..338875b3 100644
--- a/frontend/src/components/session/ShareSessionModal.tsx
+++ b/frontend/src/components/session/ShareSessionModal.tsx
@@ -1,15 +1,16 @@
import { useState, useEffect, useCallback } from 'react'
-import { X, Copy, Check, Globe, Users, Clock, Trash2, Link2 } from 'lucide-react'
+import { Copy, Check, Globe, Users, Clock, Trash2, Link2 } from 'lucide-react'
import type { SessionShare, SessionShareVisibility } from '@/types'
import { sessionsApi } from '@/api/sessions'
import { buildSessionShareUrl, filterSharesForSession } from '@/lib/sessionShare'
import { cn } from '@/lib/utils'
import { toast } from '@/lib/toast'
import { Spinner } from '@/components/common/Spinner'
+import { Modal } from '@/components/common/Modal'
interface ShareSessionModalProps {
sessionId: string
- sessionLabel: string // e.g. ticket number or "Session Details"
+ sessionLabel: string
isOpen: boolean
onClose: () => void
}
@@ -76,7 +77,6 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
const [isGenerating, setIsGenerating] = useState(false)
const [copiedShareId, setCopiedShareId] = useState
(null)
- // Form state
const [visibility, setVisibility] = useState('account')
const [shareName, setShareName] = useState('')
const [expirationPreset, setExpirationPreset] = useState('never')
@@ -88,7 +88,6 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
try {
const allShares = await sessionsApi.listMyShares()
const sessionShares = filterSharesForSession(allShares, sessionId)
- // Sort newest first
sessionShares.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
setShares(sessionShares)
} catch (err) {
@@ -101,7 +100,6 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
useEffect(() => {
if (isOpen) {
loadShares()
- // Reset form state
setVisibility('account')
setShareName('')
setExpirationPreset('never')
@@ -123,7 +121,6 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
})
setShares([newShare, ...shares])
toast.success('Share link generated')
- // Reset form
setShareName('')
setExpirationPreset('never')
setCustomDatetime('')
@@ -167,8 +164,6 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
}
}
- if (!isOpen) return null
-
const presetButtons: { value: ExpirationPreset; label: string }[] = [
{ value: 'never', label: 'Never' },
{ value: '1day', label: '1 day' },
@@ -178,242 +173,13 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
]
return (
-
- {/* Backdrop */}
-
-
- {/* Modal */}
-
- {/* Header */}
-
-
-
Share Session
-
{sessionLabel}
-
-
-
-
-
-
- {/* Body */}
-
- {/* Create Share Form */}
-
- {/* Visibility */}
-
-
- Visibility
-
-
-
{ setVisibility('account'); setVisibilityError(null) }}
- className={cn(
- 'flex w-full items-center gap-3 rounded-md border px-4 py-3 text-left transition-colors',
- visibility === 'account'
- ? 'border-primary/30 bg-primary/10 text-foreground'
- : 'border-border bg-transparent text-muted-foreground hover:border-border hover:bg-accent'
- )}
- >
-
-
-
Account Only
-
Visible to your team
-
- {visibility === 'account' && (
-
- )}
-
-
{ setVisibility('public'); setVisibilityError(null) }}
- className={cn(
- 'flex w-full items-center gap-3 rounded-md border px-4 py-3 text-left transition-colors',
- visibility === 'public'
- ? 'border-primary/30 bg-primary/10 text-foreground'
- : 'border-border bg-transparent text-muted-foreground hover:border-border hover:bg-accent'
- )}
- >
-
-
-
Public
-
Anyone with the link
-
- {visibility === 'public' && (
-
- )}
-
-
- {visibilityError && (
-
{visibilityError}
- )}
-
-
- {/* Share Name */}
-
-
- Share Name (optional)
-
- setShareName(e.target.value.slice(0, 100))}
- placeholder="e.g. Training link, Customer escalation"
- className={cn(
- 'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground placeholder-muted-foreground',
- 'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
- )}
- maxLength={100}
- />
-
-
- {/* Expiration */}
-
-
- Expiration
-
-
- {presetButtons.map((preset) => (
- setExpirationPreset(preset.value)}
- className={cn(
- 'rounded-md border px-3 py-1.5 text-sm transition-colors',
- expirationPreset === preset.value
- ? 'border-primary/30 bg-primary/10 text-foreground'
- : 'border-border text-muted-foreground hover:border-border hover:bg-accent'
- )}
- >
- {preset.label}
-
- ))}
-
- {expirationPreset === 'custom' && (
-
setCustomDatetime(e.target.value)}
- className={cn(
- 'mt-2 w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
- 'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
- 'scheme-dark'
- )}
- />
- )}
-
-
- {/* Generate Button */}
-
-
- {isGenerating ? 'Generating...' : 'Generate Link'}
-
-
-
- {/* Existing Shares */}
- {shares.length > 0 && (
-
-
- Active Shares ({shares.length})
-
-
- {shares.map((share) => {
- const expiration = getExpirationLabel(share.expires_at)
- const isCopied = copiedShareId === share.id
- return (
-
-
-
-
-
- {share.visibility === 'public' ? (
-
- ) : (
-
- )}
- {share.visibility === 'public' ? 'Public' : 'Account'}
-
-
- {share.share_name || 'Untitled share'}
-
-
-
- {getRelativeTime(share.created_at)}
-
- {share.view_count > 0
- ? `${share.view_count} view${share.view_count === 1 ? '' : 's'}`
- : 'Not viewed yet'}
-
-
-
- {expiration.text}
-
-
-
-
- handleCopyUrl(share)}
- title="Copy share URL"
- className={cn(
- 'rounded-md border border-border p-1.5 text-sm transition-colors',
- isCopied
- ? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-400'
- : 'text-muted-foreground hover:bg-accent hover:text-foreground'
- )}
- >
- {isCopied ? (
-
- ) : (
-
- )}
-
- handleRevoke(share.id)}
- title="Revoke share"
- className="rounded-md border border-border p-1.5 text-muted-foreground hover:bg-red-500/10 hover:border-red-500/30 hover:text-red-400 transition-colors"
- >
-
-
-
-
-
- )
- })}
-
-
- )}
-
- {/* Loading state */}
- {isLoadingShares && shares.length === 0 && (
-
-
-
- )}
-
-
- {/* Footer */}
-
+
+ }
+ >
+ {/* Subtitle */}
+
{sessionLabel}
+
+
+ {/* Create Share Form */}
+
+ {/* Visibility */}
+
+
+ Visibility
+
+
+
{ setVisibility('account'); setVisibilityError(null) }}
+ className={cn(
+ 'flex w-full items-center gap-3 rounded-md border px-4 py-3 text-left transition-colors',
+ visibility === 'account'
+ ? 'border-primary/30 bg-primary/10 text-foreground'
+ : 'border-border bg-transparent text-muted-foreground hover:border-border hover:bg-accent'
+ )}
+ >
+
+
+
Account Only
+
Visible to your team
+
+ {visibility === 'account' && (
+
+ )}
+
+
{ setVisibility('public'); setVisibilityError(null) }}
+ className={cn(
+ 'flex w-full items-center gap-3 rounded-md border px-4 py-3 text-left transition-colors',
+ visibility === 'public'
+ ? 'border-primary/30 bg-primary/10 text-foreground'
+ : 'border-border bg-transparent text-muted-foreground hover:border-border hover:bg-accent'
+ )}
+ >
+
+
+
Public
+
Anyone with the link
+
+ {visibility === 'public' && (
+
+ )}
+
+
+ {visibilityError && (
+
{visibilityError}
+ )}
+
+
+ {/* Share Name */}
+
+
+ Share Name (optional)
+
+ setShareName(e.target.value.slice(0, 100))}
+ placeholder="e.g. Training link, Customer escalation"
+ className={cn(
+ 'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground placeholder-muted-foreground',
+ 'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
+ )}
+ maxLength={100}
+ />
+
+
+ {/* Expiration */}
+
+
+ Expiration
+
+
+ {presetButtons.map((preset) => (
+ setExpirationPreset(preset.value)}
+ className={cn(
+ 'rounded-md border px-3 py-1.5 text-sm transition-colors',
+ expirationPreset === preset.value
+ ? 'border-primary/30 bg-primary/10 text-foreground'
+ : 'border-border text-muted-foreground hover:border-border hover:bg-accent'
+ )}
+ >
+ {preset.label}
+
+ ))}
+
+ {expirationPreset === 'custom' && (
+
setCustomDatetime(e.target.value)}
+ className={cn(
+ 'mt-2 w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
+ 'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
+ 'scheme-dark'
+ )}
+ />
+ )}
+
+
+ {/* Generate Button */}
+
+
+ {isGenerating ? 'Generating...' : 'Generate Link'}
+
+
+
+ {/* Existing Shares */}
+ {shares.length > 0 && (
+
+
+ Active Shares ({shares.length})
+
+
+ {shares.map((share) => {
+ const expiration = getExpirationLabel(share.expires_at)
+ const isCopied = copiedShareId === share.id
+ return (
+
+
+
+
+
+ {share.visibility === 'public' ? (
+
+ ) : (
+
+ )}
+ {share.visibility === 'public' ? 'Public' : 'Account'}
+
+
+ {share.share_name || 'Untitled share'}
+
+
+
+ {getRelativeTime(share.created_at)}
+
+ {share.view_count > 0
+ ? `${share.view_count} view${share.view_count === 1 ? '' : 's'}`
+ : 'Not viewed yet'}
+
+
+
+ {expiration.text}
+
+
+
+
+ handleCopyUrl(share)}
+ title="Copy share URL"
+ className={cn(
+ 'rounded-md border border-border p-1.5 text-sm transition-colors',
+ isCopied
+ ? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-400'
+ : 'text-muted-foreground hover:bg-accent hover:text-foreground'
+ )}
+ >
+ {isCopied ? (
+
+ ) : (
+
+ )}
+
+ handleRevoke(share.id)}
+ title="Revoke share"
+ className="rounded-md border border-border p-1.5 text-muted-foreground hover:bg-red-500/10 hover:border-red-500/30 hover:text-red-400 transition-colors"
+ >
+
+
+
+
+
+ )
+ })}
+
+
+ )}
+
+ {/* Loading state */}
+ {isLoadingShares && shares.length === 0 && (
+
+
+
+ )}
-
+
)
}
--
2.49.1
From b158eddbcbbad4696927898a8b90e2d34d4df202 Mon Sep 17 00:00:00 2001
From: Michael Chihlas
Date: Sat, 7 Mar 2026 22:53:20 -0500
Subject: [PATCH 2/3] refactor: adopt shared Button component in 18 modal
components
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Replace raw elements with from ui/Button.tsx:
- Primary buttons (bg-gradient-brand) →
- Secondary buttons (border-border) →
- Ghost buttons →
- Loading states use loading prop instead of manual Loader2 spinner
Co-Authored-By: Claude Opus 4.6
---
.../components/admin/CreateCategoryModal.tsx | 23 +++++------
.../components/admin/EditCategoryModal.tsx | 23 +++++------
.../components/library/ExportFlowModal.tsx | 17 ++++-----
.../components/library/FolderEditModal.tsx | 20 ++++------
frontend/src/components/library/ForkModal.tsx | 17 +++++----
.../components/library/ImportFlowModal.tsx | 25 ++++++------
.../src/components/library/ShareTreeModal.tsx | 24 ++++--------
.../components/procedural/IntakeFormModal.tsx | 14 +++----
.../src/components/session/ForkTreeModal.tsx | 38 ++++++-------------
.../session/SaveSessionAsTreeModal.tsx | 22 ++++-------
.../session/SessionOutcomeModal.tsx | 22 ++++-------
.../components/session/ShareSessionModal.tsx | 25 +++++-------
.../components/session/StepRatingModal.tsx | 22 ++++-------
.../session/VariablePromptModal.tsx | 19 ++++------
.../step-library/StepDetailModal.tsx | 17 ++++-----
.../src/components/step-library/StepForm.tsx | 18 +++++----
.../tree-editor/AIFixReviewModal.tsx | 28 ++++++--------
.../tree-editor/NodeEditorModal.tsx | 12 +++---
18 files changed, 155 insertions(+), 231 deletions(-)
diff --git a/frontend/src/components/admin/CreateCategoryModal.tsx b/frontend/src/components/admin/CreateCategoryModal.tsx
index 4c2f5e33..752d0ce5 100644
--- a/frontend/src/components/admin/CreateCategoryModal.tsx
+++ b/frontend/src/components/admin/CreateCategoryModal.tsx
@@ -1,6 +1,7 @@
import { useState } from 'react'
import { cn } from '@/lib/utils'
import { Modal } from '@/components/common/Modal'
+import { Button } from '@/components/ui/Button'
interface CreateCategoryModalProps {
isOpen: boolean
@@ -62,28 +63,22 @@ export function CreateCategoryModal({
size="sm"
footer={
-
Cancel
-
-
+
- {isSaving ? 'Creating...' : 'Create Category'}
-
+ Create Category
+
}
>
diff --git a/frontend/src/components/admin/EditCategoryModal.tsx b/frontend/src/components/admin/EditCategoryModal.tsx
index c51d6f0c..fb586940 100644
--- a/frontend/src/components/admin/EditCategoryModal.tsx
+++ b/frontend/src/components/admin/EditCategoryModal.tsx
@@ -2,6 +2,7 @@ import { useState } from 'react'
import { cn } from '@/lib/utils'
import type { StepCategoryListItem } from '@/types'
import { Modal } from '@/components/common/Modal'
+import { Button } from '@/components/ui/Button'
interface EditCategoryModalProps {
isOpen: boolean
@@ -74,28 +75,22 @@ export function EditCategoryModal({
size="sm"
footer={
-
Cancel
-
-
+
- {isSaving ? 'Saving...' : 'Save Changes'}
-
+ Save Changes
+
}
>
diff --git a/frontend/src/components/library/ExportFlowModal.tsx b/frontend/src/components/library/ExportFlowModal.tsx
index 5d044dce..e241491c 100644
--- a/frontend/src/components/library/ExportFlowModal.tsx
+++ b/frontend/src/components/library/ExportFlowModal.tsx
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'
import { Download, X } from 'lucide-react'
import { flowTransferApi } from '@/api/flowTransfer'
import { toast } from '@/lib/toast'
+import { Button } from '@/components/ui/Button'
interface ExportFlowModalProps {
treeId: string
@@ -79,20 +80,16 @@ export function ExportFlowModal({ treeId, treeName, onClose }: ExportFlowModalPr
{/* Footer */}
-
+
Cancel
-
-
+
- {isExporting ? 'Exporting…' : 'Download .rfflow'}
-
+ Download .rfflow
+
diff --git a/frontend/src/components/library/FolderEditModal.tsx b/frontend/src/components/library/FolderEditModal.tsx
index 63b8415c..25a3c596 100644
--- a/frontend/src/components/library/FolderEditModal.tsx
+++ b/frontend/src/components/library/FolderEditModal.tsx
@@ -4,6 +4,7 @@ import { foldersApi } from '@/api/folders'
import type { FolderListItem, FolderCreate, FolderUpdate } from '@/types'
import { cn } from '@/lib/utils'
import { toast } from '@/lib/toast'
+import { Button } from '@/components/ui/Button'
// Predefined color options
const FOLDER_COLORS = [
@@ -259,24 +260,19 @@ export function FolderEditModal({
{/* Actions */}
-
Cancel
-
-
+
- {isSubmitting ? 'Saving...' : isEditMode ? 'Save Changes' : 'Create Folder'}
-
+ {isEditMode ? 'Save Changes' : 'Create Folder'}
+
diff --git a/frontend/src/components/library/ForkModal.tsx b/frontend/src/components/library/ForkModal.tsx
index c56b532e..7e7f1f31 100644
--- a/frontend/src/components/library/ForkModal.tsx
+++ b/frontend/src/components/library/ForkModal.tsx
@@ -4,6 +4,7 @@ import { treesApi } from '@/api/trees'
import { toast } from '@/lib/toast'
import { cn } from '@/lib/utils'
import { useNavigate } from 'react-router-dom'
+import { Button } from '@/components/ui/Button'
interface ForkModalProps {
treeId: string
@@ -116,20 +117,20 @@ export function ForkModal({ treeId, treeName, onClose }: ForkModalProps) {
{/* Footer */}
-
Cancel
-
-
+
- {isSubmitting ? 'Forking…' : 'Fork Flow'}
-
+ Fork Flow
+
diff --git a/frontend/src/components/library/ImportFlowModal.tsx b/frontend/src/components/library/ImportFlowModal.tsx
index ec44fe46..45f6aad0 100644
--- a/frontend/src/components/library/ImportFlowModal.tsx
+++ b/frontend/src/components/library/ImportFlowModal.tsx
@@ -7,6 +7,7 @@ import { toast } from '@/lib/toast'
import { cn } from '@/lib/utils'
import { useNavigate } from 'react-router-dom'
import { getTreeEditorPath } from '@/lib/routing'
+import { Button } from '@/components/ui/Button'
interface ImportFlowModalProps {
onClose: () => void
@@ -227,28 +228,26 @@ export function ImportFlowModal({ onClose }: ImportFlowModalProps) {
{/* Footer */}
{step === 'preview' && (
- { setStep('pick'); setParsed(null); setParseError(null) }}
- className="mr-auto rounded-lg border border-border px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
+ className="mr-auto"
>
Back
-
+
)}
-
+
Cancel
-
+
{step === 'preview' && (
-
- {isImporting ? 'Importing…' : 'Import as Draft'}
-
+ Import as Draft
+
)}
diff --git a/frontend/src/components/library/ShareTreeModal.tsx b/frontend/src/components/library/ShareTreeModal.tsx
index 3fab9b62..cc97b08f 100644
--- a/frontend/src/components/library/ShareTreeModal.tsx
+++ b/frontend/src/components/library/ShareTreeModal.tsx
@@ -4,6 +4,7 @@ import type { TreeListItem, TreeShare, TreeVisibility } from '@/types'
import { treesApi } from '@/api/trees'
import { cn } from '@/lib/utils'
import { toast } from '@/lib/toast'
+import { Button } from '@/components/ui/Button'
interface ShareTreeModalProps {
tree: TreeListItem
@@ -201,16 +202,13 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
{/* Generate Button */}
{!activeShare && (
-
- {isGenerating ? 'Generating...' : 'Generate Share Link'}
-
+ Generate Share Link
+
)}
{/* Active Share Link */}
@@ -263,15 +261,9 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
{/* Footer */}
-
+
Close
-
+
diff --git a/frontend/src/components/procedural/IntakeFormModal.tsx b/frontend/src/components/procedural/IntakeFormModal.tsx
index d3b09355..19fae7ff 100644
--- a/frontend/src/components/procedural/IntakeFormModal.tsx
+++ b/frontend/src/components/procedural/IntakeFormModal.tsx
@@ -2,6 +2,7 @@ import { useState } from 'react'
import type { IntakeFormField } from '@/types'
import { PasswordInput } from '@/components/common/PasswordInput'
import { cn } from '@/lib/utils'
+import { Button } from '@/components/ui/Button'
interface IntakeFormModalProps {
isOpen: boolean
@@ -240,19 +241,16 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
{/* Footer */}
-
Cancel
-
-
+
+
Start Procedure
-
+
diff --git a/frontend/src/components/session/ForkTreeModal.tsx b/frontend/src/components/session/ForkTreeModal.tsx
index 34a1d210..a5dba031 100644
--- a/frontend/src/components/session/ForkTreeModal.tsx
+++ b/frontend/src/components/session/ForkTreeModal.tsx
@@ -1,7 +1,8 @@
import { useState } from 'react'
import { Modal } from '@/components/common/Modal'
-import { GitFork, Loader2 } from 'lucide-react'
+import { GitFork } from 'lucide-react'
import { cn } from '@/lib/utils'
+import { Button } from '@/components/ui/Button'
interface ForkTreeModalProps {
isOpen: boolean
@@ -44,38 +45,21 @@ export function ForkTreeModal({
const footer = (
-
Skip
-
-
+
- {isSaving ? (
- <>
-
- Saving...
- >
- ) : (
- <>
-
- Save as Personal Tree
- >
- )}
-
+
+ Save as Personal Tree
+
)
diff --git a/frontend/src/components/session/SaveSessionAsTreeModal.tsx b/frontend/src/components/session/SaveSessionAsTreeModal.tsx
index aceaa836..11f5635b 100644
--- a/frontend/src/components/session/SaveSessionAsTreeModal.tsx
+++ b/frontend/src/components/session/SaveSessionAsTreeModal.tsx
@@ -1,6 +1,7 @@
import { useState } from 'react'
import { X } from 'lucide-react'
import { cn } from '@/lib/utils'
+import { Button } from '@/components/ui/Button'
interface SaveSessionAsTreeModalProps {
isOpen: boolean
@@ -130,27 +131,20 @@ export function SaveSessionAsTreeModal({
{/* Actions */}
-
Cancel
-
-
+
- {isSaving ? 'Saving...' : 'Save as Tree'}
-
+ Save as Tree
+
diff --git a/frontend/src/components/session/SessionOutcomeModal.tsx b/frontend/src/components/session/SessionOutcomeModal.tsx
index ac5e1f31..b011444e 100644
--- a/frontend/src/components/session/SessionOutcomeModal.tsx
+++ b/frontend/src/components/session/SessionOutcomeModal.tsx
@@ -2,6 +2,7 @@ import { useRef } from 'react'
import { Modal } from '@/components/common/Modal'
import { cn } from '@/lib/utils'
import type { SessionOutcome } from '@/types'
+import { Button } from '@/components/ui/Button'
interface SessionOutcomeModalProps {
isOpen: boolean
@@ -46,28 +47,21 @@ export function SessionOutcomeModal({
title="Session Outcome"
footer={(
-
Cancel
-
-
+
- {isSubmitting ? 'Completing...' : 'Complete Session'}
-
+ Complete Session
+
)}
>
diff --git a/frontend/src/components/session/ShareSessionModal.tsx b/frontend/src/components/session/ShareSessionModal.tsx
index 338875b3..d3ebd42b 100644
--- a/frontend/src/components/session/ShareSessionModal.tsx
+++ b/frontend/src/components/session/ShareSessionModal.tsx
@@ -7,6 +7,7 @@ import { cn } from '@/lib/utils'
import { toast } from '@/lib/toast'
import { Spinner } from '@/components/common/Spinner'
import { Modal } from '@/components/common/Modal'
+import { Button } from '@/components/ui/Button'
interface ShareSessionModalProps {
sessionId: string
@@ -180,15 +181,9 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
size="lg"
footer={
-
+
Close
-
+
}
>
@@ -300,17 +295,15 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
{/* Generate Button */}
-
- {isGenerating ? 'Generating...' : 'Generate Link'}
-
+ Generate Link
+
{/* Existing Shares */}
diff --git a/frontend/src/components/session/StepRatingModal.tsx b/frontend/src/components/session/StepRatingModal.tsx
index 1d1b604f..06966d72 100644
--- a/frontend/src/components/session/StepRatingModal.tsx
+++ b/frontend/src/components/session/StepRatingModal.tsx
@@ -3,6 +3,7 @@ import { X, ThumbsUp, ThumbsDown } from 'lucide-react'
import { StarRating } from '@/components/common/StarRating'
import { cn } from '@/lib/utils'
import type { Step } from '@/types'
+import { Button } from '@/components/ui/Button'
interface StepRatingData {
rating: number
@@ -190,28 +191,21 @@ export function StepRatingModal({
{/* Footer */}
-
Skip
-
-
+
- {isSaving ? 'Submitting...' : 'Submit Ratings'}
-
+ Submit Ratings
+
diff --git a/frontend/src/components/session/VariablePromptModal.tsx b/frontend/src/components/session/VariablePromptModal.tsx
index 9182b9aa..8467548b 100644
--- a/frontend/src/components/session/VariablePromptModal.tsx
+++ b/frontend/src/components/session/VariablePromptModal.tsx
@@ -1,5 +1,6 @@
import { useState } from 'react'
import { cn } from '@/lib/utils'
+import { Button } from '@/components/ui/Button'
interface VariablePromptModalProps {
/** The prompt text from [USER_INPUT:prompt] */
@@ -45,26 +46,20 @@ export function VariablePromptModal({ prompt, onSubmit, onCancel }: VariableProm
/>
-
Continue
-
-
+
Skip
-
+
diff --git a/frontend/src/components/step-library/StepDetailModal.tsx b/frontend/src/components/step-library/StepDetailModal.tsx
index 56fe83c2..2b13975e 100644
--- a/frontend/src/components/step-library/StepDetailModal.tsx
+++ b/frontend/src/components/step-library/StepDetailModal.tsx
@@ -4,6 +4,7 @@ import { cn } from '@/lib/utils'
import { MarkdownContent } from '@/components/ui/MarkdownContent'
import { stepsApi } from '@/api/steps'
import type { Step, Review } from '@/types/step'
+import { Button } from '@/components/ui/Button'
interface StepDetailModalProps {
stepId: string
@@ -318,22 +319,20 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
{/* Footer - Actions */}
-
Cancel
-
-
+
Insert Into Session
-
+
diff --git a/frontend/src/components/step-library/StepForm.tsx b/frontend/src/components/step-library/StepForm.tsx
index df73d3fa..5dc01dd9 100644
--- a/frontend/src/components/step-library/StepForm.tsx
+++ b/frontend/src/components/step-library/StepForm.tsx
@@ -3,6 +3,7 @@ import { Plus, X, HelpCircle, Zap, CheckCircle } from 'lucide-react'
import { cn } from '@/lib/utils'
import { stepCategoriesApi } from '@/api/stepCategories'
import type { StepCreate, StepCategory, StepCommand } from '@/types/step'
+import { Button } from '@/components/ui/Button'
interface StepFormProps {
onSubmit: (data: StepCreate) => void
@@ -369,20 +370,21 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
{/* Actions */}
-
Cancel
-
-
+
- {isSubmitting ? 'Saving...' : (submitLabel ?? 'Insert Step')}
-
+ {submitLabel ?? 'Insert Step'}
+
)
diff --git a/frontend/src/components/tree-editor/AIFixReviewModal.tsx b/frontend/src/components/tree-editor/AIFixReviewModal.tsx
index 1f3a096e..17f21a64 100644
--- a/frontend/src/components/tree-editor/AIFixReviewModal.tsx
+++ b/frontend/src/components/tree-editor/AIFixReviewModal.tsx
@@ -2,6 +2,7 @@ import { useState } from 'react'
import { X, Check, SkipForward, Sparkles, ChevronDown, ChevronUp } from 'lucide-react'
import { cn } from '@/lib/utils'
import type { AIFixProposal } from '@/types'
+import { Button } from '@/components/ui/Button'
interface AIFixReviewModalProps {
fixes: AIFixProposal[]
@@ -125,20 +126,21 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
{/* Action buttons */}
- handleApply(fix)}
- className="flex items-center gap-1 rounded-md bg-gradient-brand px-3 py-1.5 text-xs font-medium text-white shadow-xs shadow-primary/20 hover:opacity-90"
>
Apply
-
-
+ handleSkip(fix)}
- className="flex items-center gap-1 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
>
Skip
-
+
>
)}
@@ -149,19 +151,13 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
{/* Footer */}
-
+
{allHandled ? 'Done' : 'Cancel'}
-
+
{!allHandled && (
-
+
Apply All ({pendingFixes.length})
-
+
)}
diff --git a/frontend/src/components/tree-editor/NodeEditorModal.tsx b/frontend/src/components/tree-editor/NodeEditorModal.tsx
index 96fbc31e..43312d96 100644
--- a/frontend/src/components/tree-editor/NodeEditorModal.tsx
+++ b/frontend/src/components/tree-editor/NodeEditorModal.tsx
@@ -5,6 +5,7 @@ import { NodeFormDecision } from './NodeFormDecision'
import { NodeFormAction } from './NodeFormAction'
import { NodeFormResolution } from './NodeFormResolution'
import type { TreeStructure } from '@/types'
+import { Button } from '@/components/ui/Button'
interface NodeEditorModalProps {
node: TreeStructure
@@ -65,20 +66,19 @@ export function NodeEditorModal({ node, onClose, isNewNode = false }: NodeEditor
const footerContent = (
-
Cancel
-
-
+
Done
-
+
)
--
2.49.1
From 44f02d06055bcf3f865a736d984869e373c6b692 Mon Sep 17 00:00:00 2001
From: Michael Chihlas
Date: Sat, 7 Mar 2026 23:18:45 -0500
Subject: [PATCH 3/3] refactor: adopt shared Button component in 20
page/component files
Replace raw elements with across pages and remaining
components. 38 total files now use the shared Button component consistently.
Co-Authored-By: Claude Opus 4.6
---
.../src/components/common/ErrorBoundary.tsx | 12 +--
frontend/src/components/common/RouteError.tsx | 22 ++---
.../procedural/CompletionSummary.tsx | 15 ++--
.../step-library/StepLibraryBrowser.tsx | 15 ++--
frontend/src/pages/AccountSettingsPage.tsx | 76 ++++++----------
frontend/src/pages/MySharesPage.tsx | 34 +++----
frontend/src/pages/MyTreesPage.tsx | 46 ++++------
frontend/src/pages/ProceduralEditorPage.tsx | 14 +--
frontend/src/pages/SessionDetailPage.tsx | 22 ++---
frontend/src/pages/StepLibraryPage.tsx | 24 ++---
frontend/src/pages/TreeEditorPage.tsx | 69 ++++----------
frontend/src/pages/TreeLibraryPage.tsx | 23 ++---
frontend/src/pages/TreeNavigationPage.tsx | 32 ++-----
.../src/pages/account/TeamCategoriesPage.tsx | 13 +--
frontend/src/pages/admin/FeatureFlagsPage.tsx | 17 ++--
.../src/pages/admin/GlobalCategoriesPage.tsx | 13 +--
frontend/src/pages/admin/InviteCodesPage.tsx | 26 ++----
frontend/src/pages/admin/PlanLimitsPage.tsx | 16 ++--
frontend/src/pages/admin/UserDetailPage.tsx | 90 ++++---------------
frontend/src/pages/admin/UsersPage.tsx | 81 ++++-------------
20 files changed, 211 insertions(+), 449 deletions(-)
diff --git a/frontend/src/components/common/ErrorBoundary.tsx b/frontend/src/components/common/ErrorBoundary.tsx
index bdee3079..e2f4a371 100644
--- a/frontend/src/components/common/ErrorBoundary.tsx
+++ b/frontend/src/components/common/ErrorBoundary.tsx
@@ -1,5 +1,5 @@
import { Component, type ReactNode } from 'react'
-import { cn } from '@/lib/utils'
+import { Button } from '@/components/ui/Button'
interface Props {
children: ReactNode
@@ -45,15 +45,9 @@ export class ErrorBoundary extends Component {
{this.state.error.message}
)}
- window.location.reload()}
- className={cn(
- 'rounded-xl bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
- 'hover:opacity-90'
- )}
- >
+ window.location.reload()}>
Refresh Page
-
+
)
diff --git a/frontend/src/components/common/RouteError.tsx b/frontend/src/components/common/RouteError.tsx
index 71e9ae63..01931aea 100644
--- a/frontend/src/components/common/RouteError.tsx
+++ b/frontend/src/components/common/RouteError.tsx
@@ -1,6 +1,6 @@
import { useEffect } from 'react'
import { useRouteError, isRouteErrorResponse, useNavigate } from 'react-router-dom'
-import { cn } from '@/lib/utils'
+import { Button } from '@/components/ui/Button'
function isChunkLoadError(error: unknown): boolean {
if (!(error instanceof Error)) return false
@@ -55,24 +55,12 @@ export function RouteError() {
{errorDetails}
)}
- navigate(-1)}
- className={cn(
- 'rounded-xl border border-border px-4 py-2 text-sm font-medium text-muted-foreground',
- 'hover:bg-accent hover:text-foreground'
- )}
- >
+ navigate(-1)}>
Go Back
-
- navigate('/trees')}
- className={cn(
- 'rounded-xl bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
- 'hover:opacity-90'
- )}
- >
+
+ navigate('/trees')}>
Go Home
-
+
diff --git a/frontend/src/components/procedural/CompletionSummary.tsx b/frontend/src/components/procedural/CompletionSummary.tsx
index a563e2eb..0f283c5e 100644
--- a/frontend/src/components/procedural/CompletionSummary.tsx
+++ b/frontend/src/components/procedural/CompletionSummary.tsx
@@ -1,4 +1,5 @@
import { CheckCircle2, Clock, FileText, Download } from 'lucide-react'
+import { Button } from '@/components/ui/Button'
import type { ProceduralStep } from '@/types'
interface StepCompletion {
@@ -128,19 +129,13 @@ export function CompletionSummary({
{/* Actions */}
-
+
Export Report
-
-
+
+
Done
-
+
)
diff --git a/frontend/src/components/step-library/StepLibraryBrowser.tsx b/frontend/src/components/step-library/StepLibraryBrowser.tsx
index ad71e7da..3f28805e 100644
--- a/frontend/src/components/step-library/StepLibraryBrowser.tsx
+++ b/frontend/src/components/step-library/StepLibraryBrowser.tsx
@@ -1,5 +1,6 @@
import { useState, useEffect, useMemo } from 'react'
import { Search, ChevronDown, ChevronUp, Loader2 } from 'lucide-react'
+import { Button } from '@/components/ui/Button'
import { cn } from '@/lib/utils'
import { stepsApi } from '@/api/steps'
import { stepCategoriesApi } from '@/api/stepCategories'
@@ -253,12 +254,9 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
) : error ? (
{error}
-
setRetryCount(c => c + 1)}
- className="rounded-md border border-border px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
- >
+ setRetryCount(c => c + 1)}>
Try again
-
+
) : steps.length === 0 ? (
@@ -374,12 +372,9 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
{/* Footer - Optional Create Button */}
{showCreateButton && onCreateNew && (
-
+
+ Create New Step
-
+
)}
diff --git a/frontend/src/pages/AccountSettingsPage.tsx b/frontend/src/pages/AccountSettingsPage.tsx
index 156a9c6e..51bd8bdb 100644
--- a/frontend/src/pages/AccountSettingsPage.tsx
+++ b/frontend/src/pages/AccountSettingsPage.tsx
@@ -6,6 +6,7 @@ import type { Account, AccountMember, AccountInvite } from '@/types'
import { TransferOwnershipModal } from '@/components/account/TransferOwnershipModal'
import { LeaveAccountModal } from '@/components/account/LeaveAccountModal'
import { DeleteAccountModal } from '@/components/account/DeleteAccountModal'
+import { Button } from '@/components/ui/Button'
import { Spinner } from '@/components/common/Spinner'
import { cn } from '@/lib/utils'
import { usePermissions } from '@/hooks/usePermissions'
@@ -200,29 +201,23 @@ export function AccountSettingsPage() {
}
}}
/>
-
- {isSavingName ? (
-
- ) : (
-
- )}
-
-
+
+
{
setEditedName(account?.name ?? '')
setIsEditingName(false)
}}
- className="rounded-md border border-border p-2 text-muted-foreground hover:bg-accent"
>
-
+
) : (
@@ -429,23 +424,13 @@ export function AccountSettingsPage() {
Engineer
Viewer
-
- {isInviting ? (
-
-
- Sending...
-
- ) : (
- 'Send Invite'
- )}
-
+ {isInviting ? 'Sending...' : 'Send Invite'}
+
{inviteError && (
@@ -633,30 +618,27 @@ export function AccountSettingsPage() {
Transfer Ownership
Make another member the account owner
- setShowTransferModal(true)}
- className={cn(
- 'rounded-[10px] px-3 py-1.5 text-sm font-medium',
- 'border border-amber-500/30 text-amber-400 hover:bg-amber-500/10'
- )}
+ className="border-amber-500/30 text-amber-400 hover:bg-amber-500/10"
>
Transfer
-
+
Delete Account
Permanently delete your account and all data
-
setShowDeleteModal(true)}
- className={cn(
- 'rounded-[10px] px-3 py-1.5 text-sm font-medium',
- 'border border-rose-500/30 text-rose-400 hover:bg-rose-500/10'
- )}
>
Delete
-
+
>
) : (
@@ -665,15 +647,13 @@ export function AccountSettingsPage() {
Leave Account
Leave this account and create a personal one
- setShowLeaveModal(true)}
- className={cn(
- 'rounded-[10px] px-3 py-1.5 text-sm font-medium',
- 'border border-rose-500/30 text-rose-400 hover:bg-rose-500/10'
- )}
>
Leave
-
+
)}
diff --git a/frontend/src/pages/MySharesPage.tsx b/frontend/src/pages/MySharesPage.tsx
index 17ea9a4a..020f7bfd 100644
--- a/frontend/src/pages/MySharesPage.tsx
+++ b/frontend/src/pages/MySharesPage.tsx
@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { Globe, Users, Copy, Check, Link2, ExternalLink, Trash2, ArrowLeft } from 'lucide-react'
+import { Button } from '@/components/ui/Button'
import { Spinner } from '@/components/common/Spinner'
import { EmptyState } from '@/components/common/EmptyState'
import { ConfirmDialog } from '@/components/common/ConfirmDialog'
@@ -110,12 +111,9 @@ export default function MySharesPage() {
{error}
-
+
Try again
-
+
@@ -147,12 +145,9 @@ export default function MySharesPage() {
title="No shared sessions"
description="Share a session from the session detail page to create a link"
action={
- navigate('/sessions')}
- className="bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90 rounded-md px-4 py-2 text-sm font-medium transition-colors"
- >
+ navigate('/sessions')}>
Go to Sessions
-
+
}
/>
@@ -201,14 +196,10 @@ export default function MySharesPage() {
{/* Actions */}
- handleCopyLink(share)}
- className={cn(
- 'inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
- isCopied
- ? 'bg-emerald-400/10 text-emerald-400'
- : 'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90'
- )}
+ className={isCopied ? 'bg-emerald-400/10 text-emerald-400 shadow-none hover:opacity-100' : ''}
>
{isCopied ? (
@@ -216,7 +207,7 @@ export default function MySharesPage() {
)}
{isCopied ? 'Copied' : 'Copy Link'}
-
+
- setRevokeTarget(share)}
- className="inline-flex items-center gap-1.5 text-red-400 hover:text-red-300 hover:bg-red-400/10 rounded-md px-3 py-1.5 text-sm transition-colors"
>
Revoke
-
+
)
diff --git a/frontend/src/pages/MyTreesPage.tsx b/frontend/src/pages/MyTreesPage.tsx
index 38e6df6f..44dabe7f 100644
--- a/frontend/src/pages/MyTreesPage.tsx
+++ b/frontend/src/pages/MyTreesPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react'
import { useNavigate, Link } from 'react-router-dom'
import { Play, Pencil, Share2, Trash2, GitBranch, Clock, TrendingUp, FolderTree, Plus, ListOrdered, ChevronDown, Wrench } from 'lucide-react'
+import { Button } from '@/components/ui/Button'
import { treesApi } from '@/api/trees'
import { sessionsApi } from '@/api/sessions'
import type { TreeListItem } from '@/types'
@@ -125,14 +126,13 @@ export function MyTreesPage() {
{canCreateTrees && (
-
setShowCreateMenu(!showCreateMenu)}
- className="flex items-center gap-2 rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-4 py-2 text-sm font-medium hover:opacity-90"
>
Create New
-
+
{showCreateMenu && (
<>
setShowCreateMenu(false)} />
@@ -297,17 +297,13 @@ export function MyTreesPage() {
{/* Actions */}
-
handleStartSession(tree)}
- className={cn(
- 'flex flex-1 items-center justify-center gap-2 rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-3 py-2 text-sm font-medium',
- 'hover:opacity-90'
- )}
+ className="flex-1"
>
Start
-
+
{canEditTree({ author_id: tree.author_id, account_id: tree.account_id }) && (
)}
-
{
setTreeToShare(tree)
setShowShareModal(true)
}}
- className={cn(
- 'rounded-md border border-border p-2 text-muted-foreground',
- 'hover:bg-accent hover:text-foreground'
- )}
title="Share tree"
>
-
-
+ setForkTarget(tree)}
- className="rounded-md border border-border p-2 text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
title="Fork flow"
>
-
-
+ {
setTreeToDelete(tree)
setShowDeleteConfirm(true)
}}
- className={cn(
- 'rounded-md border border-border p-2 text-muted-foreground',
- 'hover:bg-red-400/10 hover:text-red-400'
- )}
title="Delete tree"
>
-
+
))}
diff --git a/frontend/src/pages/ProceduralEditorPage.tsx b/frontend/src/pages/ProceduralEditorPage.tsx
index 5419b0ce..b1c6a73e 100644
--- a/frontend/src/pages/ProceduralEditorPage.tsx
+++ b/frontend/src/pages/ProceduralEditorPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState, useCallback } from 'react'
import { useParams, useNavigate, useSearchParams } from 'react-router-dom'
import { Save, ArrowLeft, ListOrdered, Wrench, Settings, FileText, Calendar, Sparkles, Layers } from 'lucide-react'
+import { Button } from '@/components/ui/Button'
import { treesApi } from '@/api/trees'
import { useProceduralEditorStore } from '@/store/proceduralEditorStore'
import { CollapsibleEditorSection } from '@/components/procedural-editor/CollapsibleEditorSection'
@@ -220,21 +221,20 @@ export function ProceduralEditorPage() {
AI Assist
-
handleSave('draft')}
disabled={isSaving}
- className="flex items-center gap-1.5 rounded-md border border-border px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50"
>
Save Draft
-
-
+ handleSave('published')}
- disabled={isSaving}
- className="flex items-center gap-1.5 rounded-md bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20 hover:opacity-90 disabled:opacity-50"
+ loading={isSaving}
>
{isSaving ? 'Saving...' : 'Publish'}
-
+
diff --git a/frontend/src/pages/SessionDetailPage.tsx b/frontend/src/pages/SessionDetailPage.tsx
index 0c1a6074..71710514 100644
--- a/frontend/src/pages/SessionDetailPage.tsx
+++ b/frontend/src/pages/SessionDetailPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { Copy, Check, Eye, Save, Share2, CheckCircle2, AlertTriangle, ArrowUpRight, HelpCircle, Flag } from 'lucide-react'
+import { Button } from '@/components/ui/Button'
import { sessionsApi } from '@/api/sessions'
import { stepsApi } from '@/api/steps'
import { ExportPreviewModal } from '@/components/session/ExportPreviewModal'
@@ -391,13 +392,10 @@ export function SessionDetailPage() {
{/* Primary action: Copy for Ticket */}
-
+
{copiedPsa ? : }
{copiedPsa ? 'Copied!' : 'Copy for Ticket'}
-
+
) : !session.completed_at ? (
@@ -410,12 +408,9 @@ export function SessionDetailPage() {
Set an outcome to finalize this session and generate documentation.
- setShowOutcomeModal(true)}
- className="shrink-0 rounded-lg bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20 hover:opacity-90"
- >
+ setShowOutcomeModal(true)} className="shrink-0">
Complete Session
-
+
) : null}
@@ -462,14 +457,15 @@ export function SessionDetailPage() {
>
{copied ? : }
-
{isExporting ? 'Loading...' : 'Preview'}
-
+
{/* Copy for ticket (secondary position when session is complete) */}
{session.completed_at && (
{canCreateSteps && (
- setCreateOpen(true)}
- className="rounded-md bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20 hover:opacity-90"
- >
+ setCreateOpen(true)}>
+ Create Step
-
+
)}
@@ -147,20 +145,22 @@ export default function StepLibraryPage() {
{deleteError}
)}
- { setDeletingStep(null); setDeleteError(null) }}
disabled={isDeleting}
- className="flex-1 rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50"
+ className="flex-1"
>
Cancel
-
-
+
{isDeleting ? 'Deleting...' : 'Delete'}
-
+
diff --git a/frontend/src/pages/TreeEditorPage.tsx b/frontend/src/pages/TreeEditorPage.tsx
index 344a5284..2b47b0ee 100644
--- a/frontend/src/pages/TreeEditorPage.tsx
+++ b/frontend/src/pages/TreeEditorPage.tsx
@@ -2,6 +2,7 @@ import { useEffect, useState, useCallback, useRef } from 'react'
import { useParams, useNavigate, useBlocker } from 'react-router-dom'
import { useStore } from 'zustand'
import { Undo2, Redo2, Save, CheckCircle2, Monitor, FileText, Code2, LayoutList, BarChart3, Settings, Download, Sparkles } from 'lucide-react'
+import { Button } from '@/components/ui/Button'
import { getMonacoEditor } from '@/components/tree-editor/code-mode'
import { treesApi } from '@/api/trees'
import { treeMarkdownApi } from '@/api/treeMarkdown'
@@ -508,15 +509,9 @@ export function TreeEditorPage() {
The tree editor requires a larger screen for the best experience. Please open this page on a desktop or tablet in landscape mode.
- navigate('/trees')}
- className={cn(
- 'rounded-md bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
- 'hover:opacity-90'
- )}
- >
+ navigate('/trees')}>
Back to Library
-
+
)
}
@@ -535,24 +530,12 @@ export function TreeEditorPage() {
You have an unsaved draft from a previous session. Would you like to restore it?
-
+
Restore Draft
-
-
+
+
Start Fresh
-
+
@@ -567,24 +550,12 @@ export function TreeEditorPage() {
You have unsaved changes. Are you sure you want to leave?
-
+
Stay
-
-
+
+
Leave Without Saving
-
+
@@ -787,32 +758,26 @@ export function TreeEditorPage() {
{/* Save Draft */}
-
Save Draft
-
+
{/* Publish */}
-
{isSaving ? 'Publishing...' : 'Publish'}
-
+
diff --git a/frontend/src/pages/TreeLibraryPage.tsx b/frontend/src/pages/TreeLibraryPage.tsx
index aed0be7d..1865314c 100644
--- a/frontend/src/pages/TreeLibraryPage.tsx
+++ b/frontend/src/pages/TreeLibraryPage.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState, useCallback, useMemo } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { X, RotateCcw, Play, FileUp } from 'lucide-react'
+import { Button } from '@/components/ui/Button'
import { treesApi } from '@/api/trees'
import { categoriesApi } from '@/api/categories'
import { foldersApi } from '@/api/folders'
@@ -283,13 +284,13 @@ export function TreeLibraryPage() {
{canCreateTrees && (
- setShowImportModal(true)}
- className="flex items-center gap-2 rounded-lg border border-border bg-[rgba(255,255,255,0.04)] px-4 py-2 text-sm font-medium text-foreground hover:border-[rgba(255,255,255,0.12)] transition-colors"
>
Import
-
+
-
+
Search
-
+
-
navigate(getSessionResumePath(s.tree_id, s.tree_snapshot?.tree_type), { state: { sessionId: s.id } })}
- className="flex items-center gap-1.5 rounded-md bg-gradient-brand px-3 py-1.5 text-sm font-medium text-white shadow-lg shadow-primary/20 hover:opacity-90"
>
Resume
-
+
dismissSession(s.id)}
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground"
diff --git a/frontend/src/pages/TreeNavigationPage.tsx b/frontend/src/pages/TreeNavigationPage.tsx
index 1de7c257..c273e680 100644
--- a/frontend/src/pages/TreeNavigationPage.tsx
+++ b/frontend/src/pages/TreeNavigationPage.tsx
@@ -21,6 +21,7 @@ import { StepFeedback } from '@/components/session/StepFeedback'
import { buildSessionShareUrl, getLatestActiveShareForSession } from '@/lib/sessionShare'
import { CopilotPanel } from '@/components/copilot/CopilotPanel'
import { CopilotToggle } from '@/components/copilot/CopilotToggle'
+import { Button } from '@/components/ui/Button'
interface LocationState {
sessionId?: string
@@ -601,15 +602,9 @@ export function TreeNavigationPage() {
/>
-
+
Start Troubleshooting
-
+
)
@@ -940,17 +935,14 @@ export function TreeNavigationPage() {
const targetLabel = targetNode?.question || targetNode?.title || 'next step'
return (
-
Continue to: {targetLabel.length > 50 ? `${targetLabel.slice(0, 50)}...` : targetLabel}
-
+
)
})()}
@@ -1063,15 +1055,9 @@ export function TreeNavigationPage() {
)}
{currentNode.next_node_id && (
- handleContinue()}
- className={cn(
- 'rounded-md bg-gradient-brand px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
- 'hover:opacity-90'
- )}
- >
+ handleContinue()}>
Continue
-
+
)}
>
)}
diff --git a/frontend/src/pages/account/TeamCategoriesPage.tsx b/frontend/src/pages/account/TeamCategoriesPage.tsx
index c2665cdc..ffa5c3b9 100644
--- a/frontend/src/pages/account/TeamCategoriesPage.tsx
+++ b/frontend/src/pages/account/TeamCategoriesPage.tsx
@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback } from 'react'
import { Plus, Trash2, Pencil, FolderTree } from 'lucide-react'
+import { Button } from '@/components/ui/Button'
import { cn } from '@/lib/utils'
import { toast } from '@/lib/toast'
import { Modal } from '@/components/common/Modal'
@@ -86,10 +87,10 @@ export function TeamCategoriesPage() {
title="Team Categories"
description="Manage tree categories for your team"
action={(
- setCreateOpen(true)} className={cn('flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium', 'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90')}>
+ setCreateOpen(true)}>
Create Category
-
+
)}
/>
@@ -132,8 +133,8 @@ export function TeamCategoriesPage() {
setCreateOpen(false)} title="Create Category" size="sm"
footer={
- setCreateOpen(false)} className="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground">Cancel
- Create
+ setCreateOpen(false)}>Cancel
+ Create
}
>
@@ -157,8 +158,8 @@ export function TeamCategoriesPage() {
setEditCategory(null)} title="Edit Category" size="sm"
footer={
- setEditCategory(null)} className="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground">Cancel
- Save
+ setEditCategory(null)}>Cancel
+ Save
}
>
diff --git a/frontend/src/pages/admin/FeatureFlagsPage.tsx b/frontend/src/pages/admin/FeatureFlagsPage.tsx
index 26f0d077..42ecf482 100644
--- a/frontend/src/pages/admin/FeatureFlagsPage.tsx
+++ b/frontend/src/pages/admin/FeatureFlagsPage.tsx
@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback } from 'react'
import { Plus, Trash2, ToggleLeft } from 'lucide-react'
+import { Button } from '@/components/ui/Button'
import { DataTable, PageHeader, StatusBadge, ActionMenu, EmptyState } from '@/components/admin'
import type { Column } from '@/components/admin'
import { Modal } from '@/components/common/Modal'
@@ -153,10 +154,10 @@ export function FeatureFlagsPage() {
title="Feature Flags"
description="Manage feature availability per plan and account"
action={
- setCreateOpen(true)} className={cn('flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium', 'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90')}>
+ setCreateOpen(true)}>
Create Flag
-
+
}
/>
@@ -172,10 +173,10 @@ export function FeatureFlagsPage() {
Account Overrides
-
setOverrideOpen(true)} className={cn('flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium', 'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90')}>
+ setOverrideOpen(true)}>
Add Override
-
+
o.id} isLoading={loading}
@@ -188,8 +189,8 @@ export function FeatureFlagsPage() {
setCreateOpen(false)} title="Create Feature Flag" size="sm"
footer={
- setCreateOpen(false)} className="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground">Cancel
- Create
+ setCreateOpen(false)}>Cancel
+ Create
}
>
@@ -213,8 +214,8 @@ export function FeatureFlagsPage() {
setOverrideOpen(false)} title="Add Account Override" size="sm"
footer={
- setOverrideOpen(false)} className="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground">Cancel
- Create
+ setOverrideOpen(false)}>Cancel
+ Create
}
>
diff --git a/frontend/src/pages/admin/GlobalCategoriesPage.tsx b/frontend/src/pages/admin/GlobalCategoriesPage.tsx
index cc42e109..aa1d8158 100644
--- a/frontend/src/pages/admin/GlobalCategoriesPage.tsx
+++ b/frontend/src/pages/admin/GlobalCategoriesPage.tsx
@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback } from 'react'
import { Plus, Trash2, Pencil, FolderTree } from 'lucide-react'
+import { Button } from '@/components/ui/Button'
import { DataTable, PageHeader, ActionMenu, EmptyState } from '@/components/admin'
import type { Column } from '@/components/admin'
import { Modal } from '@/components/common/Modal'
@@ -95,10 +96,10 @@ export function GlobalCategoriesPage() {
title="Global Categories"
description="Manage tree categories available to all accounts"
action={
- setCreateOpen(true)} className={cn('flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium', 'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90')}>
+ setCreateOpen(true)}>
Create Category
-
+
}
/>
@@ -118,8 +119,8 @@ export function GlobalCategoriesPage() {
size="sm"
footer={
- setCreateOpen(false)} className="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground">Cancel
- Create
+ setCreateOpen(false)}>Cancel
+ Create
}
>
@@ -147,8 +148,8 @@ export function GlobalCategoriesPage() {
size="sm"
footer={
- setEditCategory(null)} className="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground">Cancel
- Save
+ setEditCategory(null)}>Cancel
+ Save
}
>
diff --git a/frontend/src/pages/admin/InviteCodesPage.tsx b/frontend/src/pages/admin/InviteCodesPage.tsx
index 05788ce5..d938d3b7 100644
--- a/frontend/src/pages/admin/InviteCodesPage.tsx
+++ b/frontend/src/pages/admin/InviteCodesPage.tsx
@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback } from 'react'
import { Plus, Copy, Trash2, Ticket, Mail, MailCheck, RefreshCw } from 'lucide-react'
+import { Button } from '@/components/ui/Button'
import { DataTable, PageHeader, StatusBadge, ActionMenu, EmptyState } from '@/components/admin'
import type { Column } from '@/components/admin'
import { Modal } from '@/components/common/Modal'
@@ -215,16 +216,10 @@ export function InviteCodesPage() {
title="Invite Codes"
description="Create and manage registration invite codes with plan assignment"
action={
- setCreateOpen(true)}
- className={cn(
- 'flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium',
- 'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90'
- )}
- >
+ setCreateOpen(true)}>
Create Code
-
+
}
/>
@@ -249,19 +244,10 @@ export function InviteCodesPage() {
size="sm"
footer={
- { setCreateOpen(false); resetForm() }}
- className="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
- >
- Cancel
-
-
+ { setCreateOpen(false); resetForm() }}>Cancel
+
{creating ? 'Creating...' : 'Create'}
-
+
}
>
diff --git a/frontend/src/pages/admin/PlanLimitsPage.tsx b/frontend/src/pages/admin/PlanLimitsPage.tsx
index 63026298..a7778d1e 100644
--- a/frontend/src/pages/admin/PlanLimitsPage.tsx
+++ b/frontend/src/pages/admin/PlanLimitsPage.tsx
@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback } from 'react'
import { Plus, Trash2, Gauge } from 'lucide-react'
+import { Button } from '@/components/ui/Button'
import { DataTable, PageHeader, ActionMenu, EmptyState } from '@/components/admin'
import type { Column } from '@/components/admin'
import { Modal } from '@/components/common/Modal'
@@ -127,13 +128,10 @@ export function PlanLimitsPage() {
Account Overrides
-
setCreateOverride(true)}
- className={cn('flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium', 'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90')}
- >
+ setCreateOverride(true)}>
Add Override
-
+
- setEditPlan(null)} className="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground">Cancel
- Save
+ setEditPlan(null)}>Cancel
+ Save
}
>
@@ -185,8 +183,8 @@ export function PlanLimitsPage() {
size="sm"
footer={
- setCreateOverride(false)} className="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground">Cancel
- Create
+ setCreateOverride(false)}>Cancel
+ Create
}
>
diff --git a/frontend/src/pages/admin/UserDetailPage.tsx b/frontend/src/pages/admin/UserDetailPage.tsx
index 40deb024..019e83ae 100644
--- a/frontend/src/pages/admin/UserDetailPage.tsx
+++ b/frontend/src/pages/admin/UserDetailPage.tsx
@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { ArrowLeft, Shield, Crown, UserCheck, UserX, Clock, Ticket, KeyRound, Copy, Check, Archive, ArchiveRestore, Trash2 } from 'lucide-react'
+import { Button } from '@/components/ui/Button'
import { StatusBadge } from '@/components/admin'
import { Modal } from '@/components/common/Modal'
import { Spinner } from '@/components/common/Spinner'
@@ -205,12 +206,9 @@ export function UserDetailPage() {
title="User not found"
description="This user may have been removed or is unavailable."
action={(
-
navigate('/admin/users')}
- className="rounded-md border border-border px-4 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
- >
+ navigate('/admin/users')}>
Back to Users
-
+
)}
/>
)
@@ -525,18 +523,8 @@ export function UserDetailPage() {
size="sm"
footer={
- setPlanModalOpen(false)}
- className="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent"
- >
- Cancel
-
-
- Update Plan
-
+ setPlanModalOpen(false)}>Cancel
+ Update Plan
}
>
@@ -563,19 +551,10 @@ export function UserDetailPage() {
size="sm"
footer={
- setResetModalOpen(false)}
- className="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent"
- >
- Cancel
-
-
+ setResetModalOpen(false)}>Cancel
+
{resetLoading ? 'Resetting...' : 'Reset Password'}
-
+
}
>
@@ -624,12 +603,7 @@ export function UserDetailPage() {
size="sm"
footer={
- { setResetTempPassword(null); setResetModalOpen(false) }}
- className="rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-4 py-2 text-sm font-medium hover:opacity-90"
- >
- Done
-
+ { setResetTempPassword(null); setResetModalOpen(false) }}>Done
}
>
@@ -663,18 +637,10 @@ export function UserDetailPage() {
size="sm"
footer={
- setTrialModalOpen(false)}
- className="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent"
- >
- Cancel
-
-
+ setTrialModalOpen(false)}>Cancel
+
{user.subscription?.status === 'trialing' ? 'Extend' : 'Start Trial'}
-
+
}
>
@@ -700,23 +666,13 @@ export function UserDetailPage() {
size="sm"
footer={
- setSuperAdminModalOpen(false)}
- className="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent"
- >
- Cancel
-
- setSuperAdminModalOpen(false)}>Cancel
+
{user.is_super_admin ? 'Remove Access' : 'Promote'}
-
+
}
>
@@ -741,19 +697,11 @@ export function UserDetailPage() {
size="sm"
footer={
- setHardDeleteModalOpen(false)}
- className="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent"
- >
- Cancel
-
+ setHardDeleteModalOpen(false)}>Cancel
{hardDeleteBlockers && Object.keys(hardDeleteBlockers).length === 0 && (
-
+
Delete Permanently
-
+
)}
}
diff --git a/frontend/src/pages/admin/UsersPage.tsx b/frontend/src/pages/admin/UsersPage.tsx
index 7a372610..f982acef 100644
--- a/frontend/src/pages/admin/UsersPage.tsx
+++ b/frontend/src/pages/admin/UsersPage.tsx
@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import { UserCheck, UserX, Shield, ArrowRightLeft, ExternalLink, UserPlus, Copy, Check, Mail } from 'lucide-react'
+import { Button } from '@/components/ui/Button'
import { DataTable, Pagination, SearchInput, PageHeader, StatusBadge, ActionMenu } from '@/components/admin'
import type { Column } from '@/components/admin'
import { Modal } from '@/components/common/Modal'
@@ -266,20 +267,14 @@ export function UsersPage() {
- setShowInviteModal(true)}
- className="flex items-center gap-2 rounded-lg border border-border px-4 py-2 text-sm font-medium text-foreground hover:bg-accent transition-colors"
- >
+ setShowInviteModal(true)}>
Invite User
-
- setShowCreateModal(true)}
- className="flex items-center gap-2 rounded-lg bg-gradient-brand text-white shadow-lg shadow-primary/20 px-4 py-2 text-sm font-medium hover:opacity-90 transition-colors"
- >
+
+ setShowCreateModal(true)}>
Create User
-
+
@@ -324,18 +319,8 @@ export function UsersPage() {
size="sm"
footer={
- setRoleModalUser(null)}
- className="rounded-md border border-border px-4 py-2 text-sm font-medium text-foreground/60 hover:bg-accent hover:text-foreground"
- >
- Cancel
-
-
- Save
-
+ setRoleModalUser(null)}>Cancel
+ Save
}
>
@@ -365,19 +350,8 @@ export function UsersPage() {
size="sm"
footer={
- setMoveModalUser(null)}
- className="rounded-md border border-border px-4 py-2 text-sm font-medium text-foreground/60 hover:bg-accent hover:text-foreground"
- >
- Cancel
-
-
- Move
-
+ setMoveModalUser(null)}>Cancel
+ Move
}
>
@@ -409,19 +383,10 @@ export function UsersPage() {
size="sm"
footer={
- setShowCreateModal(false)}
- className="rounded-md border border-border px-4 py-2 text-sm font-medium text-foreground/60 hover:bg-accent hover:text-foreground"
- >
- Cancel
-
-
+ setShowCreateModal(false)}>Cancel
+
{createLoading ? 'Creating...' : 'Create User'}
-
+
}
>
@@ -520,12 +485,7 @@ export function UsersPage() {
size="sm"
footer={
- setTempPassword(null)}
- className="rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-4 py-2 text-sm font-medium hover:opacity-90"
- >
- Done
-
+ setTempPassword(null)}>Done
}
>
@@ -562,19 +522,10 @@ export function UsersPage() {
size="sm"
footer={
- setShowInviteModal(false)}
- className="rounded-md border border-border px-4 py-2 text-sm font-medium text-foreground/60 hover:bg-accent hover:text-foreground"
- >
- Cancel
-
-
+ setShowInviteModal(false)}>Cancel
+
{inviteLoading ? 'Sending...' : 'Send Invite'}
-
+
}
>
--
2.49.1