diff --git a/apps/web/app/(chat)/layout.tsx b/apps/web/app/(chat)/layout.tsx index 5fc1452..beb609a 100644 --- a/apps/web/app/(chat)/layout.tsx +++ b/apps/web/app/(chat)/layout.tsx @@ -6,7 +6,8 @@ import Layout from "@/components/layout/Layout"; import { ChatModal } from "@/components/chat/modal/ChatModal"; import { MinimizedPill } from "@/components/chat/modal/MinimizedPill"; import { SearchModal } from "@/components/search/SearchModal"; -import { CreateProjectModal } from "@/components/project/CreateProjectModal"; +import { BriefModal } from "@/components/brief/BriefModal"; +import { Toaster } from "@/components/ui/sonner"; export default function ChatLayout({ children }: { children: React.ReactNode }) { const { status } = useAuth(); @@ -22,7 +23,8 @@ export default function ChatLayout({ children }: { children: React.ReactNode }) - + + ); diff --git a/apps/web/src/components/brief/BriefFieldInput.tsx b/apps/web/src/components/brief/BriefFieldInput.tsx new file mode 100644 index 0000000..af814eb --- /dev/null +++ b/apps/web/src/components/brief/BriefFieldInput.tsx @@ -0,0 +1,679 @@ +"use client" + +import { useId, useState, type ReactNode } from "react" +import { + AUDIENCE_SEGMENTS, + AVAILABILITY, + BRIEF_OPTION_LABELS, + BRIEF_SITE_TYPES, + BRIEF_UI_COPY, + CTA_TYPES, + FIELD_DEFS, + LEGAL_MODES, + NicheCategoryEnum, + PRICE_INTERVALS, + PRICING_MODES, + PRIMARY_GOALS, + PRODUCT_TYPES, + SOCIAL_NETWORKS, + TECH_LEVELS, + TONES, + TRAFFIC_SOURCES, + type BriefData, + type FieldId, +} from "@repo/schemas" +import { BUILDER_CAPABILITIES } from "@repo/site-builder" +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" +import { Switch } from "@/components/ui/switch" +import { cn } from "@/lib/utils" + +type Change = (value: unknown) => void +type Item = NonNullable[number] + +export type FieldRenderProps = { + id: FieldId + data: BriefData + onChange: Change + onBlur?: () => void + disabled?: boolean + describedBy: string + invalid: boolean + logoUrl?: string + onUpload?: (file: File) => void + uploading?: boolean +} + +const control = "text-[13px]" + +function Counter({ value, max }: { value: string; max: number }) { + return ( + max ? "text-red-600 dark:text-red-400" : "text-mut")} aria-hidden="true"> + {value.length}/{max} + + ) +} + +function TextControl(props: { + inputId: string + value: string | undefined + max: number + multiline?: boolean + placeholder?: string + type?: string + onChange: (v: string) => void + onBlur?: () => void + disabled?: boolean + describedBy?: string + invalid?: boolean + label?: string + autoComplete?: string +}) { + const value = props.value ?? "" + const common = { + id: props.inputId, + value, + placeholder: props.placeholder, + disabled: props.disabled, + onBlur: props.onBlur, + "aria-describedby": props.describedBy, + "aria-invalid": props.invalid || undefined, + "aria-label": props.label, + autoComplete: props.autoComplete ?? "off", + } + return ( +
+ {props.multiline ? ( +