refactor: migrate page components to Design System v4

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-03-22 02:04:16 -04:00
parent fd28921373
commit e4ef904707
58 changed files with 1416 additions and 1416 deletions

View File

@@ -264,15 +264,15 @@ export default function SurveyPage() {
if (tokenLoading) {
return (
<div className="min-h-screen bg-background flex items-center justify-center">
<div className="text-muted-foreground text-sm">Loading...</div>
<div className="min-h-screen bg-[#0c0d10] flex items-center justify-center">
<div className="text-[#848b9b] text-sm">Loading...</div>
</div>
)
}
if (alreadyCompleted) {
return (
<div className="min-h-screen bg-background text-foreground">
<div className="min-h-screen bg-[#0c0d10] text-[#e2e5eb]">
<div className="pointer-events-none fixed inset-0 overflow-hidden" aria-hidden="true">
<div className="absolute -top-32 right-0 h-[500px] w-[500px] rounded-full opacity-[0.03]" style={{ background: 'radial-gradient(circle, var(--color-primary), transparent 70%)' }} />
<div className="absolute -bottom-32 left-0 h-[400px] w-[400px] rounded-full opacity-[0.02]" style={{ background: 'radial-gradient(circle, #a855f7, transparent 70%)' }} />
@@ -283,16 +283,16 @@ export default function SurveyPage() {
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="var(--color-primary)" strokeWidth="2.5"><path d="M20 6L9 17l-5-5"/></svg>
</div>
<h2 className="font-heading text-2xl font-bold mb-2.5">Already Submitted</h2>
<p className="text-muted-foreground text-sm max-w-[440px] mx-auto leading-relaxed mb-3">
<p className="text-[#848b9b] text-sm max-w-[440px] mx-auto leading-relaxed mb-3">
{inviteName ? `Thanks ${inviteName} — y` : 'Y'}our response has already been recorded. We appreciate your time!
</p>
<p className="text-sm text-muted-foreground/70 max-w-[400px] mx-auto leading-relaxed mb-8">
<p className="text-sm text-[#848b9b]/70 max-w-[400px] mx-auto leading-relaxed mb-8">
You can safely close this browser window now.
</p>
<div className="glass-card-static p-4 sm:p-5 max-w-[400px] mx-auto text-center">
<p className="text-xs text-muted-foreground leading-relaxed">
<div className="card-flat p-4 sm:p-5 max-w-[400px] mx-auto text-center">
<p className="text-xs text-[#848b9b] leading-relaxed">
Have feedback unrelated to the survey?{' '}
<a href="mailto:feedback@resolutionflow.com" className="text-primary hover:underline font-medium">
<a href="mailto:feedback@resolutionflow.com" className="text-[#22d3ee] hover:underline font-medium">
feedback@resolutionflow.com
</a>
</p>
@@ -304,7 +304,7 @@ export default function SurveyPage() {
}
return (
<div ref={topRef} className="min-h-screen bg-background text-foreground">
<div ref={topRef} className="min-h-screen bg-[#0c0d10] text-[#e2e5eb]">
<PageMeta
title="Product Survey"
description="Help shape the future of ResolutionFlow by sharing your feedback"
@@ -322,17 +322,17 @@ export default function SurveyPage() {
</div>
{/* Top bar */}
<div className="sticky top-0 z-50" style={{ backdropFilter: 'blur(20px)', WebkitBackdropFilter: 'blur(20px)', background: 'rgba(16, 17, 20, 0.85)', borderBottom: '1px solid var(--glass-border)' }}>
<div className="sticky top-0 z-50" style={{ WebkitBackdropFilter: 'blur(20px)', background: 'rgba(16, 17, 20, 0.85)', borderBottom: '1px solid var(--glass-border)' }}>
<div className="mx-auto flex max-w-[680px] items-center justify-between gap-3 px-4 py-3 sm:px-5 sm:py-3.5">
<a href="https://resolutionflow.com" target="_blank" rel="noreferrer" className="flex items-center gap-2 sm:gap-2.5 text-sm font-heading font-bold text-muted-foreground no-underline shrink-0">
<a href="https://resolutionflow.com" target="_blank" rel="noreferrer" className="flex items-center gap-2 sm:gap-2.5 text-sm font-heading font-bold text-[#848b9b] no-underline shrink-0">
<BrandLogo size="sm" />
<span className="hidden sm:inline">Resolution<span className="text-gradient-brand">Flow</span></span>
<span className="hidden sm:inline">Resolution<span className="text-[#67e8f9]">Flow</span></span>
</a>
<div className="flex flex-1 items-center gap-2 sm:gap-2.5" style={{ maxWidth: '280px' }}>
<div className="flex-1 h-[3px] rounded-full overflow-hidden" style={{ background: 'var(--color-border)' }}>
<div className="h-full rounded-full bg-gradient-brand transition-[width] duration-500" style={{ width: `${progressPct}%` }} />
<div className="h-full rounded-full bg-[#22d3ee] transition-[width] duration-500" style={{ width: `${progressPct}%` }} />
</div>
<span className="text-[11px] font-label text-muted-foreground whitespace-nowrap tabular-nums">{answeredCount}/{TOTAL_QUESTIONS}</span>
<span className="text-[11px] font-sans text-xs text-[#848b9b] whitespace-nowrap tabular-nums">{answeredCount}/{TOTAL_QUESTIONS}</span>
</div>
</div>
</div>
@@ -341,17 +341,17 @@ export default function SurveyPage() {
{/* Hero — visible only on first slide */}
{currentSlide === 0 && !isComplete && (
<div className="text-center pt-10 pb-8 sm:pt-[72px] sm:pb-10 animate-fade-in-up">
<div className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[10px] sm:text-[11px] font-semibold font-label uppercase tracking-widest mb-4 sm:mb-5" style={{ background: 'rgba(6, 182, 212, 0.1)', border: '1px solid rgba(6, 182, 212, 0.15)', color: 'var(--color-primary)' }}>
<div className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[10px] sm:text-[11px] font-semibold font-sans text-xs uppercase tracking-widest mb-4 sm:mb-5" style={{ background: 'rgba(6, 182, 212, 0.1)', border: '1px solid rgba(6, 182, 212, 0.15)', color: 'var(--color-primary)' }}>
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
FlowPilot Research
</div>
<h1 className="font-heading text-[clamp(24px,5vw,36px)] font-extrabold leading-tight mb-3">
Help Build an AI That<br/>Thinks Like <span className="text-gradient-brand">You</span>
Help Build an AI That<br/>Thinks Like <span className="text-[#67e8f9]">You</span>
</h1>
<p className="text-[14px] sm:text-[15px] text-muted-foreground max-w-[500px] mx-auto leading-relaxed">
<p className="text-[14px] sm:text-[15px] text-[#848b9b] max-w-[500px] mx-auto leading-relaxed">
We're building an AI assistant for MSP engineers. Your expertise shapes how it thinks. Takes about 5 minutes.
</p>
<div className="flex flex-wrap justify-center gap-4 sm:gap-7 mt-4 sm:mt-5 text-[11px] sm:text-[12px] text-muted-foreground">
<div className="flex flex-wrap justify-center gap-4 sm:gap-7 mt-4 sm:mt-5 text-[11px] sm:text-[12px] text-[#848b9b]">
<span className="flex items-center gap-1.5">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="var(--color-primary)" strokeWidth="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
~5 minutes
@@ -393,18 +393,18 @@ export default function SurveyPage() {
))}
<div className="flex justify-between mt-6 sm:mt-7 gap-3">
{si > 0 ? (
<button onClick={() => goSlide(si - 1)} className="inline-flex items-center gap-2 px-4 py-2.5 sm:px-6 sm:py-3 rounded-[10px] text-sm font-semibold text-muted-foreground transition-all duration-150 hover:text-foreground" style={{ background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.06)' }}>
<button onClick={() => goSlide(si - 1)} className="inline-flex items-center gap-2 px-4 py-2.5 sm:px-6 sm:py-3 rounded-lg text-sm font-semibold text-[#848b9b] transition-all duration-150 hover:text-[#e2e5eb]" style={{ background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.06)' }}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M19 12H5"/><path d="M12 19l-7-7 7-7"/></svg>
Back
</button>
) : <div />}
{si < SLIDES.length - 1 ? (
<button onClick={() => goSlide(si + 1)} className="inline-flex items-center gap-2 px-5 py-2.5 sm:px-6 sm:py-3 rounded-[10px] text-sm font-semibold bg-gradient-brand text-brand-dark shadow-lg shadow-primary/20 transition-all duration-150 hover:opacity-90 active:scale-[0.97]">
<button onClick={() => goSlide(si + 1)} className="inline-flex items-center gap-2 px-5 py-2.5 sm:px-6 sm:py-3 rounded-lg text-sm font-semibold bg-[#22d3ee] text-brand-dark transition-all duration-150 hover:brightness-110 active:scale-[0.98]">
Next
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M5 12h14"/><path d="M12 5l7 7-7 7"/></svg>
</button>
) : (
<button onClick={handleSubmit} disabled={isSubmitting} className="inline-flex items-center gap-2 px-5 py-2.5 sm:px-6 sm:py-3 rounded-[10px] text-sm font-semibold bg-gradient-brand text-brand-dark shadow-lg shadow-primary/20 transition-all duration-150 hover:opacity-90 active:scale-[0.97] disabled:opacity-40 disabled:cursor-not-allowed">
<button onClick={handleSubmit} disabled={isSubmitting} className="inline-flex items-center gap-2 px-5 py-2.5 sm:px-6 sm:py-3 rounded-lg text-sm font-semibold bg-[#22d3ee] text-brand-dark transition-all duration-150 hover:brightness-110 active:scale-[0.98] disabled:opacity-40 disabled:cursor-not-allowed">
{isSubmitting ? 'Submitting...' : 'Submit'}
{!isSubmitting && <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M20 6L9 17l-5-5"/></svg>}
</button>
@@ -413,7 +413,7 @@ export default function SurveyPage() {
{submitError && (
<div className="mt-4 p-3 rounded-lg text-sm text-rose-400" style={{ background: 'rgba(244, 63, 94, 0.1)', border: '1px solid rgba(244, 63, 94, 0.2)' }}>
{submitError}
<button onClick={copyAll} className="ml-3 underline text-muted-foreground hover:text-foreground">Copy responses instead</button>
<button onClick={copyAll} className="ml-3 underline text-[#848b9b] hover:text-[#e2e5eb]">Copy responses instead</button>
</div>
)}
</div>
@@ -427,13 +427,13 @@ export default function SurveyPage() {
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="oklch(0.76 0.15 163)" strokeWidth="2.5"><path d="M20 6L9 17l-5-5"/></svg>
</div>
<h2 className="font-heading text-2xl font-bold mb-2.5">Response Submitted!</h2>
<p className="text-muted-foreground text-sm max-w-[440px] mx-auto mb-6 sm:mb-8 leading-relaxed">
<p className="text-[#848b9b] text-sm max-w-[440px] mx-auto mb-6 sm:mb-8 leading-relaxed">
Your answers will directly shape how FlowPilot troubleshoots. Would you like a copy of your responses?
</p>
{/* Email a copy */}
<div className="glass-card-static p-4 sm:p-6 max-w-[420px] mx-auto mb-5">
<p className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground mb-3">
<div className="card-flat p-4 sm:p-6 max-w-[420px] mx-auto mb-5">
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] mb-3">
Email a copy to yourself
</p>
{!emailSent ? (
@@ -443,7 +443,7 @@ export default function SurveyPage() {
value={emailInput}
onChange={e => setEmailInput(e.target.value)}
placeholder="your@email.com"
className="flex-1 rounded-[9px] px-3.5 py-2.5 text-sm text-foreground placeholder:text-brand-text-muted focus:outline-hidden"
className="flex-1 rounded-[9px] px-3.5 py-2.5 text-sm text-[#e2e5eb] placeholder:text-brand-text-muted focus:outline-hidden"
style={{ background: 'rgba(16, 17, 20, 0.6)', border: '1px solid var(--glass-border)' }}
onFocus={e => { e.currentTarget.style.borderColor = 'var(--color-primary)' }}
onBlur={e => { e.currentTarget.style.borderColor = 'var(--glass-border)' }}
@@ -473,7 +473,7 @@ export default function SurveyPage() {
}
}}
disabled={!emailInput.trim() || emailSending}
className="inline-flex items-center justify-center gap-2 px-5 py-2.5 rounded-[9px] text-sm font-semibold bg-gradient-brand text-brand-dark transition-all duration-150 hover:opacity-90 active:scale-[0.97] disabled:opacity-40 disabled:cursor-not-allowed whitespace-nowrap"
className="inline-flex items-center justify-center gap-2 px-5 py-2.5 rounded-[9px] text-sm font-semibold bg-[#22d3ee] text-brand-dark transition-all duration-150 hover:brightness-110 active:scale-[0.98] disabled:opacity-40 disabled:cursor-not-allowed whitespace-nowrap"
>
{emailSending ? (
<>
@@ -496,13 +496,13 @@ export default function SurveyPage() {
{/* Copy + Finish buttons */}
<div className="flex gap-2.5 justify-center flex-wrap">
<button onClick={copyAll} className="inline-flex items-center gap-2 px-4 py-2.5 sm:px-5 rounded-[10px] text-sm font-semibold transition-all duration-150 text-muted-foreground hover:text-foreground" style={{ background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.06)' }}>
<button onClick={copyAll} className="inline-flex items-center gap-2 px-4 py-2.5 sm:px-5 rounded-lg text-sm font-semibold transition-all duration-150 text-[#848b9b] hover:text-[#e2e5eb]" style={{ background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.06)' }}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
Copy to Clipboard
</button>
<button
onClick={() => navigate('/survey/thank-you')}
className="inline-flex items-center gap-2 px-5 py-2.5 sm:px-6 rounded-[10px] text-sm font-semibold bg-gradient-brand text-brand-dark shadow-lg shadow-primary/20 transition-all duration-150 hover:opacity-90 active:scale-[0.97]"
className="inline-flex items-center gap-2 px-5 py-2.5 sm:px-6 rounded-lg text-sm font-semibold bg-[#22d3ee] text-brand-dark transition-all duration-150 hover:brightness-110 active:scale-[0.98]"
>
Finish
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M5 12h14"/><path d="M12 5l7 7-7 7"/></svg>
@@ -519,15 +519,15 @@ export default function SurveyPage() {
function ScenarioBox({ scenario }: { scenario: { title: string; symptom: string; details: string } }) {
return (
<div className="rounded-[10px] p-3.5 px-4 sm:p-4 sm:px-5 mb-4 text-[13px]" style={{ background: 'linear-gradient(135deg, rgba(6, 182, 212, 0.06), rgba(139, 92, 246, 0.03))', border: '1px solid color-mix(in srgb, var(--color-primary) 12%, transparent)' }}>
<div className="font-label text-[10px] uppercase tracking-widest mb-2 font-semibold" style={{ color: 'var(--color-primary)' }}>{scenario.title}</div>
<div className="rounded-lg p-3.5 px-4 sm:p-4 sm:px-5 mb-4 text-[13px]" style={{ background: 'linear-gradient(135deg, rgba(6, 182, 212, 0.06), rgba(139, 92, 246, 0.03))', border: '1px solid color-mix(in srgb, var(--color-primary) 12%, transparent)' }}>
<div className="font-sans text-xs text-[10px] uppercase tracking-widest mb-2 font-semibold" style={{ color: 'var(--color-primary)' }}>{scenario.title}</div>
<div className="sm:flex gap-2 mb-1">
<span className="text-muted-foreground font-medium whitespace-nowrap">Symptom:</span>
<span className="text-muted-foreground/80">{scenario.symptom}</span>
<span className="text-[#848b9b] font-medium whitespace-nowrap">Symptom:</span>
<span className="text-[#848b9b]/80">{scenario.symptom}</span>
</div>
<div className="sm:flex gap-2">
<span className="text-muted-foreground font-medium whitespace-nowrap">Details:</span>
<span className="text-muted-foreground/80">{scenario.details}</span>
<span className="text-[#848b9b] font-medium whitespace-nowrap">Details:</span>
<span className="text-[#848b9b]/80">{scenario.details}</span>
</div>
</div>
)
@@ -535,10 +535,10 @@ function ScenarioBox({ scenario }: { scenario: { title: string; symptom: string;
function QuestionCard({ question: q, answer, setAnswer }: { question: SurveyQuestion; answer?: string | string[]; setAnswer: (id: string, val: string | string[]) => void }) {
return (
<div className="glass-card-static p-4 sm:p-7 mb-3 sm:mb-4 transition-[border-color] duration-200 focus-within:border-primary/25!">
<div className="font-label text-[11px] mb-1.5 font-medium" style={{ color: 'var(--color-primary)' }}>Q{q.num}</div>
<div className="font-heading text-[14px] sm:text-[15px] font-semibold text-foreground leading-snug mb-1">{q.text}</div>
{q.hint && <div className="text-[12px] text-muted-foreground mb-3 sm:mb-4 leading-snug">{q.hint}</div>}
<div className="card-flat p-4 sm:p-7 mb-3 sm:mb-4 transition-[border-color] duration-200 focus-within:border-primary/25!">
<div className="font-sans text-xs text-[11px] mb-1.5 font-medium" style={{ color: 'var(--color-primary)' }}>Q{q.num}</div>
<div className="font-heading text-[14px] sm:text-[15px] font-semibold text-[#e2e5eb] leading-snug mb-1">{q.text}</div>
{q.hint && <div className="text-[12px] text-[#848b9b] mb-3 sm:mb-4 leading-snug">{q.hint}</div>}
{!q.hint && <div className="mb-3 sm:mb-4" />}
{q.type === 'mc' && q.options && (
@@ -600,7 +600,7 @@ function QuestionCard({ question: q, answer, setAnswer }: { question: SurveyQues
value={(answer as string) || ''}
onChange={e => setAnswer(q.id, e.target.value)}
placeholder="Type your answer here..."
className="w-full min-h-[100px] rounded-[9px] p-3 sm:p-3.5 text-[13px] sm:text-sm text-foreground leading-relaxed resize-y transition-all duration-200 placeholder:text-brand-text-muted focus:outline-hidden"
className="w-full min-h-[100px] rounded-[9px] p-3 sm:p-3.5 text-[13px] sm:text-sm text-[#e2e5eb] leading-relaxed resize-y transition-all duration-200 placeholder:text-brand-text-muted focus:outline-hidden"
style={{
background: 'rgba(16, 17, 20, 0.6)',
border: '1px solid var(--glass-border)',
@@ -621,7 +621,7 @@ function RangeInput({ question: q, value, onChange }: { question: SurveyQuestion
const numVal = value ? parseInt(value) : q.min || 0
return (
<div className="py-2">
<div className="text-center font-label text-2xl font-semibold mb-3" style={{ color: 'var(--color-primary)' }}>
<div className="text-center font-sans text-xs text-2xl font-semibold mb-3" style={{ color: 'var(--color-primary)' }}>
{numVal}{q.suffix || ''}
</div>
<input
@@ -636,7 +636,7 @@ function RangeInput({ question: q, value, onChange }: { question: SurveyQuestion
background: `linear-gradient(to right, var(--color-primary) 0%, var(--color-primary) ${((numVal - (q.min || 0)) / ((q.max || 10) - (q.min || 0))) * 100}%, var(--color-border) ${((numVal - (q.min || 0)) / ((q.max || 10) - (q.min || 0))) * 100}%, var(--color-border) 100%)`,
}}
/>
<div className="flex justify-between text-[11px] text-muted-foreground mt-2.5">
<div className="flex justify-between text-[11px] text-[#848b9b] mt-2.5">
<span>{q.low_label}</span>
<span>{q.high_label}</span>
</div>
@@ -739,7 +739,7 @@ function DragRank({ items, onChange }: { items: string[]; onChange: (items: stri
<div className="shrink-0 text-brand-text-muted">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="9" cy="6" r="1"/><circle cx="15" cy="6" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="18" r="1"/><circle cx="15" cy="18" r="1"/></svg>
</div>
<div className="font-label text-[11px] font-semibold w-5 text-center shrink-0" style={{ color: 'var(--color-primary)' }}>{idx + 1}</div>
<div className="font-sans text-xs text-[11px] font-semibold w-5 text-center shrink-0" style={{ color: 'var(--color-primary)' }}>{idx + 1}</div>
<div className="flex-1 leading-snug">{item}</div>
</div>
))}