fix: resolve TypeScript errors in migrated components
This commit is contained in:
@@ -30,7 +30,10 @@
|
||||
"@mosaic/shared": "workspace:*",
|
||||
"@nestjs/common": "^11.1.12",
|
||||
"@nestjs/core": "^11.1.12",
|
||||
"@nestjs/mapped-types": "^2.1.0",
|
||||
"@nestjs/platform-express": "^11.1.12",
|
||||
"@nestjs/platform-socket.io": "^11.1.12",
|
||||
"@nestjs/websockets": "^11.1.12",
|
||||
"@prisma/client": "^6.19.2",
|
||||
"@types/marked": "^6.0.0",
|
||||
"better-auth": "^1.4.17",
|
||||
@@ -40,10 +43,12 @@
|
||||
"marked": "^17.0.1",
|
||||
"marked-gfm-heading-id": "^4.1.3",
|
||||
"marked-highlight": "^2.2.3",
|
||||
"ollama": "^0.6.3",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"sanitize-html": "^2.17.0",
|
||||
"slugify": "^1.6.6"
|
||||
"slugify": "^1.6.6",
|
||||
"socket.io": "^4.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@better-auth/cli": "^1.4.17",
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"@mosaic/ui": "workspace:*",
|
||||
"@tanstack/react-query": "^5.90.20",
|
||||
"@xyflow/react": "^12.5.3",
|
||||
"better-auth": "^1.4.17",
|
||||
"date-fns": "^4.1.0",
|
||||
"elkjs": "^0.9.3",
|
||||
"lucide-react": "^0.563.0",
|
||||
@@ -29,7 +30,8 @@
|
||||
"next": "^16.1.6",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-grid-layout": "^2.2.2"
|
||||
"react-grid-layout": "^2.2.2",
|
||||
"socket.io-client": "^4.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mosaic/config": "workspace:*",
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import type { Domain } from "@mosaic/shared";
|
||||
import { DomainList } from "@/components/domains/DomainList";
|
||||
import { fetchDomains, createDomain, updateDomain, deleteDomain } from "@/lib/api/domains";
|
||||
import { fetchDomains, deleteDomain } from "@/lib/api/domains";
|
||||
|
||||
export default function DomainsPage(): JSX.Element {
|
||||
export default function DomainsPage(): React.ReactElement {
|
||||
const [domains, setDomains] = useState<Domain[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
|
||||
export default function PersonalitiesPage(): JSX.Element {
|
||||
export default function PersonalitiesPage(): React.ReactElement {
|
||||
const [personalities, setPersonalities] = useState<Personality[]>([]);
|
||||
const [selectedPersonality, setSelectedPersonality] = useState<Personality | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { TaskStatus, TaskPriority, type Task } from "@mosaic/shared";
|
||||
* This page demonstrates the GanttChart component with sample data
|
||||
* showing various task states, durations, and interactions.
|
||||
*/
|
||||
export default function GanttDemoPage(): JSX.Element {
|
||||
export default function GanttDemoPage(): React.ReactElement {
|
||||
// Sample tasks for demonstration
|
||||
const baseTasks: Task[] = [
|
||||
{
|
||||
|
||||
@@ -217,12 +217,12 @@ export const Chat = forwardRef<ChatRef, ChatProps>(function Chat({
|
||||
|
||||
// Show a witty loading message after 3 seconds
|
||||
const quipTimerId = setTimeout(() => {
|
||||
setLoadingQuip(getRandomQuip(WAITING_QUIPS));
|
||||
setLoadingQuip(getRandomQuip(WAITING_QUIPS) ?? null);
|
||||
}, 3000);
|
||||
|
||||
// Change quip every 5 seconds if still waiting
|
||||
const quipIntervalId = setInterval(() => {
|
||||
setLoadingQuip(getRandomQuip(WAITING_QUIPS));
|
||||
setLoadingQuip(getRandomQuip(WAITING_QUIPS) ?? null);
|
||||
}, 5000);
|
||||
|
||||
try {
|
||||
|
||||
@@ -45,7 +45,7 @@ export function MessageList({ messages, isLoading, loadingQuip }: MessageListPro
|
||||
<MessageBubble key={message.id} message={message} />
|
||||
))}
|
||||
|
||||
{isLoading && <LoadingIndicator quip={loadingQuip} />}
|
||||
{isLoading && <LoadingIndicator {...(loadingQuip != null && { quip: loadingQuip })} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export function DomainFilter({
|
||||
domains,
|
||||
selectedDomain,
|
||||
onFilterChange,
|
||||
}: DomainFilterProps): JSX.Element {
|
||||
}: DomainFilterProps): React.ReactElement {
|
||||
return (
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<button
|
||||
|
||||
@@ -12,7 +12,7 @@ export function DomainItem({
|
||||
domain,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}: DomainItemProps): JSX.Element {
|
||||
}: DomainItemProps): React.ReactElement {
|
||||
return (
|
||||
<div className="border rounded-lg p-4 hover:shadow-md transition-shadow">
|
||||
<div className="flex items-start justify-between">
|
||||
|
||||
@@ -15,7 +15,7 @@ export function DomainList({
|
||||
isLoading,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}: DomainListProps): JSX.Element {
|
||||
}: DomainListProps): React.ReactElement {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center p-8">
|
||||
|
||||
@@ -16,7 +16,7 @@ export function DomainSelector({
|
||||
onChange,
|
||||
placeholder = "Select a domain",
|
||||
className = "",
|
||||
}: DomainSelectorProps): JSX.Element {
|
||||
}: DomainSelectorProps): React.ReactElement {
|
||||
return (
|
||||
<select
|
||||
value={value ?? ""}
|
||||
|
||||
@@ -215,7 +215,7 @@ export function GanttChart({
|
||||
onTaskClick,
|
||||
height = 400,
|
||||
showDependencies = false,
|
||||
}: GanttChartProps): JSX.Element {
|
||||
}: GanttChartProps): React.ReactElement {
|
||||
// Sort tasks by start date
|
||||
const sortedTasks = useMemo(() => {
|
||||
return [...tasks].sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
|
||||
|
||||
@@ -40,7 +40,7 @@ const columns = [
|
||||
* - Task cards with title, priority badge, assignee avatar
|
||||
* - PATCH /api/tasks/:id on status change
|
||||
*/
|
||||
export function KanbanBoard({ tasks = [], onStatusChange }: KanbanBoardProps): JSX.Element {
|
||||
export function KanbanBoard({ tasks = [], onStatusChange }: KanbanBoardProps): React.ReactElement {
|
||||
const [activeTaskId, setActiveTaskId] = useState<string | null>(null);
|
||||
|
||||
const sensors = useSensors(
|
||||
|
||||
@@ -35,7 +35,7 @@ const statusBadgeColors = {
|
||||
* A droppable column for tasks of a specific status.
|
||||
* Uses @dnd-kit/core for drag-and-drop functionality.
|
||||
*/
|
||||
export function KanbanColumn({ status, title, tasks }: KanbanColumnProps): JSX.Element {
|
||||
export function KanbanColumn({ status, title, tasks }: KanbanColumnProps): React.ReactElement {
|
||||
const { setNodeRef, isOver } = useDroppable({
|
||||
id: status,
|
||||
});
|
||||
|
||||
@@ -48,7 +48,7 @@ function getInitials(name: string): string {
|
||||
* - Assignee avatar (if assigned)
|
||||
* - Due date (if set)
|
||||
*/
|
||||
export function TaskCard({ task }: TaskCardProps): JSX.Element {
|
||||
export function TaskCard({ task }: TaskCardProps): React.ReactElement {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { KanbanBoard } from "./kanban-board";
|
||||
export { KanbanColumn } from "./kanban-column";
|
||||
export { TaskCard } from "./task-card";
|
||||
export { KanbanBoard } from "./KanbanBoard";
|
||||
export { KanbanColumn } from "./KanbanColumn";
|
||||
export { TaskCard } from "./TaskCard";
|
||||
|
||||
@@ -39,7 +39,7 @@ export function MindmapViewer({
|
||||
updateNode,
|
||||
deleteNode,
|
||||
createEdge,
|
||||
} = useGraphData({ rootId, maxDepth });
|
||||
} = useGraphData({ ...(rootId && { rootId }), maxDepth });
|
||||
|
||||
const handleViewModeChange = useCallback(
|
||||
async (mode: ViewMode) => {
|
||||
@@ -170,9 +170,11 @@ export function MindmapViewer({
|
||||
<ReactFlowEditor
|
||||
graphData={graph}
|
||||
onNodeSelect={setSelectedNode}
|
||||
onNodeUpdate={readOnly ? undefined : updateNode}
|
||||
onNodeDelete={readOnly ? undefined : handleDeleteNode}
|
||||
onEdgeCreate={readOnly ? undefined : handleCreateEdge}
|
||||
{...(!readOnly && {
|
||||
onNodeUpdate: updateNode,
|
||||
onNodeDelete: handleDeleteNode,
|
||||
onEdgeCreate: handleCreateEdge,
|
||||
})}
|
||||
readOnly={readOnly}
|
||||
className="h-full"
|
||||
/>
|
||||
|
||||
@@ -249,8 +249,10 @@ export function ReactFlowEditor({
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={readOnly ? undefined : onNodesChange}
|
||||
onEdgesChange={readOnly ? undefined : onEdgesChange}
|
||||
{...(!readOnly && {
|
||||
onNodesChange,
|
||||
onEdgesChange,
|
||||
})}
|
||||
onConnect={onConnect}
|
||||
onNodeClick={onNodeClick}
|
||||
onPaneClick={onPaneClick}
|
||||
|
||||
@@ -40,7 +40,7 @@ const FORMALITY_OPTIONS = [
|
||||
{ value: "VERY_FORMAL", label: "Very Formal" },
|
||||
];
|
||||
|
||||
export function PersonalityForm({ personality, onSubmit, onCancel }: PersonalityFormProps): JSX.Element {
|
||||
export function PersonalityForm({ personality, onSubmit, onCancel }: PersonalityFormProps): React.ReactElement {
|
||||
const [formData, setFormData] = useState<PersonalityFormData>({
|
||||
name: personality?.name || "",
|
||||
description: personality?.description || "",
|
||||
|
||||
@@ -26,7 +26,7 @@ const FORMALITY_LABELS: Record<string, string> = {
|
||||
VERY_FORMAL: "Very Formal",
|
||||
};
|
||||
|
||||
export function PersonalityPreview({ personality }: PersonalityPreviewProps): JSX.Element {
|
||||
export function PersonalityPreview({ personality }: PersonalityPreviewProps): React.ReactElement {
|
||||
const [selectedPrompt, setSelectedPrompt] = useState<string>(SAMPLE_PROMPTS[0]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -25,7 +25,7 @@ export function PersonalitySelector({
|
||||
onChange,
|
||||
label = "Select Personality",
|
||||
className,
|
||||
}: PersonalitySelectorProps): JSX.Element {
|
||||
}: PersonalitySelectorProps): React.ReactElement {
|
||||
const [personalities, setPersonalities] = useState<Personality[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
|
||||
|
||||
121
apps/web/src/components/ui/alert-dialog.tsx
Normal file
121
apps/web/src/components/ui/alert-dialog.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import * as React from "react";
|
||||
|
||||
export interface AlertDialogProps {
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface AlertDialogTriggerProps {
|
||||
children?: React.ReactNode;
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
export interface AlertDialogContentProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface AlertDialogHeaderProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface AlertDialogFooterProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface AlertDialogTitleProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface AlertDialogDescriptionProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface AlertDialogActionProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface AlertDialogCancelProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const AlertDialogContext = React.createContext<{
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}>({});
|
||||
|
||||
export function AlertDialog({ open, onOpenChange, children }: AlertDialogProps) {
|
||||
return (
|
||||
<AlertDialogContext.Provider value={{ open, onOpenChange }}>
|
||||
{children}
|
||||
</AlertDialogContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function AlertDialogTrigger({ children, asChild }: AlertDialogTriggerProps) {
|
||||
const { onOpenChange } = React.useContext(AlertDialogContext);
|
||||
|
||||
if (asChild && React.isValidElement(children)) {
|
||||
return React.cloneElement(children, {
|
||||
onClick: () => onOpenChange?.(true),
|
||||
} as React.HTMLAttributes<HTMLElement>);
|
||||
}
|
||||
|
||||
return <div onClick={() => onOpenChange?.(true)}>{children}</div>;
|
||||
}
|
||||
|
||||
export function AlertDialogContent({ children }: AlertDialogContentProps) {
|
||||
const { open, onOpenChange } = React.useContext(AlertDialogContext);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div className="fixed inset-0 bg-black/50" onClick={() => onOpenChange?.(false)} />
|
||||
<div className="relative z-50 w-full max-w-lg rounded-lg bg-white p-6 shadow-lg">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AlertDialogHeader({ children }: AlertDialogHeaderProps) {
|
||||
return <div className="mb-4">{children}</div>;
|
||||
}
|
||||
|
||||
export function AlertDialogFooter({ children }: AlertDialogFooterProps) {
|
||||
return <div className="mt-4 flex justify-end gap-2">{children}</div>;
|
||||
}
|
||||
|
||||
export function AlertDialogTitle({ children }: AlertDialogTitleProps) {
|
||||
return <h2 className="text-lg font-semibold">{children}</h2>;
|
||||
}
|
||||
|
||||
export function AlertDialogDescription({ children }: AlertDialogDescriptionProps) {
|
||||
return <p className="text-sm text-gray-600">{children}</p>;
|
||||
}
|
||||
|
||||
export function AlertDialogAction({ children, ...props }: AlertDialogActionProps) {
|
||||
return (
|
||||
<button
|
||||
className="rounded-md bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function AlertDialogCancel({ children, ...props }: AlertDialogCancelProps) {
|
||||
const { onOpenChange } = React.useContext(AlertDialogContext);
|
||||
|
||||
return (
|
||||
<button
|
||||
className="rounded-md border border-gray-300 px-4 py-2 text-sm hover:bg-gray-100"
|
||||
onClick={() => onOpenChange?.(false)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
22
apps/web/src/components/ui/badge.tsx
Normal file
22
apps/web/src/components/ui/badge.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Badge as BaseBadge } from "@mosaic/ui";
|
||||
import type { BadgeProps as BaseBadgeProps, BadgeVariant as BaseBadgeVariant } from "@mosaic/ui";
|
||||
|
||||
// Extend BadgeVariant to include shadcn/ui variants
|
||||
export type BadgeVariant = BaseBadgeVariant | "secondary" | "outline" | "default" | "destructive";
|
||||
|
||||
export interface BadgeProps extends Omit<BaseBadgeProps, "variant"> {
|
||||
variant?: BadgeVariant;
|
||||
}
|
||||
|
||||
// Map extended variants to base variants
|
||||
const variantMap: Record<string, BaseBadgeVariant> = {
|
||||
"secondary": "status-neutral",
|
||||
"outline": "status-info",
|
||||
"default": "status-neutral",
|
||||
"destructive": "status-error",
|
||||
};
|
||||
|
||||
export function Badge({ variant = "default", ...props }: BadgeProps) {
|
||||
const mappedVariant = (variantMap[variant] || variant) as BaseBadgeVariant;
|
||||
return <BaseBadge variant={mappedVariant} {...props} />;
|
||||
}
|
||||
26
apps/web/src/components/ui/button.tsx
Normal file
26
apps/web/src/components/ui/button.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Button as BaseButton } from "@mosaic/ui";
|
||||
import type { ButtonProps as BaseButtonProps } from "@mosaic/ui";
|
||||
import type { ReactNode, ButtonHTMLAttributes } from "react";
|
||||
|
||||
// Extend Button to support additional variants
|
||||
type ExtendedVariant = "primary" | "secondary" | "danger" | "ghost" | "outline" | "destructive" | "link";
|
||||
|
||||
export interface ButtonProps extends Omit<BaseButtonProps, "variant"> {
|
||||
variant?: ExtendedVariant;
|
||||
size?: "sm" | "md" | "lg" | "icon";
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
// Map extended variants to base variants
|
||||
const variantMap: Record<string, "primary" | "secondary" | "danger" | "ghost"> = {
|
||||
"outline": "ghost",
|
||||
"destructive": "danger",
|
||||
"link": "ghost",
|
||||
};
|
||||
|
||||
export function Button({ variant = "primary", size = "md", ...props }: ButtonProps) {
|
||||
const mappedVariant = variantMap[variant] || variant;
|
||||
const mappedSize = size === "icon" ? "sm" : size;
|
||||
|
||||
return <BaseButton variant={mappedVariant as "primary" | "secondary" | "danger" | "ghost"} size={mappedSize as "sm" | "md" | "lg"} {...props} />;
|
||||
}
|
||||
22
apps/web/src/components/ui/card.tsx
Normal file
22
apps/web/src/components/ui/card.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
export { Card, CardHeader, CardContent, CardFooter } from "@mosaic/ui";
|
||||
export type { CardProps, CardHeaderProps, CardContentProps, CardFooterProps } from "@mosaic/ui";
|
||||
|
||||
// Additional Card sub-components for shadcn/ui compatibility
|
||||
import * as React from "react";
|
||||
|
||||
export interface CardTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {}
|
||||
export interface CardDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {}
|
||||
|
||||
export const CardTitle = React.forwardRef<HTMLHeadingElement, CardTitleProps>(
|
||||
({ className = "", ...props }, ref) => (
|
||||
<h3 ref={ref} className={`text-2xl font-semibold leading-none tracking-tight ${className}`} {...props} />
|
||||
)
|
||||
);
|
||||
CardTitle.displayName = "CardTitle";
|
||||
|
||||
export const CardDescription = React.forwardRef<HTMLParagraphElement, CardDescriptionProps>(
|
||||
({ className = "", ...props }, ref) => (
|
||||
<p ref={ref} className={`text-sm text-gray-600 ${className}`} {...props} />
|
||||
)
|
||||
);
|
||||
CardDescription.displayName = "CardDescription";
|
||||
2
apps/web/src/components/ui/input.tsx
Normal file
2
apps/web/src/components/ui/input.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export { Input } from "@mosaic/ui";
|
||||
export type { InputProps } from "@mosaic/ui";
|
||||
17
apps/web/src/components/ui/label.tsx
Normal file
17
apps/web/src/components/ui/label.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import * as React from "react";
|
||||
|
||||
export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {}
|
||||
|
||||
export const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
|
||||
({ className = "", ...props }, ref) => {
|
||||
return (
|
||||
<label
|
||||
ref={ref}
|
||||
className={`text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 ${className}`}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Label.displayName = "Label";
|
||||
102
apps/web/src/components/ui/select.tsx
Normal file
102
apps/web/src/components/ui/select.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import * as React from "react";
|
||||
|
||||
export interface SelectProps {
|
||||
value?: string;
|
||||
onValueChange?: (value: string) => void;
|
||||
defaultValue?: string;
|
||||
disabled?: boolean;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface SelectTriggerProps {
|
||||
id?: string;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface SelectContentProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface SelectItemProps {
|
||||
value: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface SelectValueProps {
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
const SelectContext = React.createContext<{
|
||||
value?: string;
|
||||
onValueChange?: (value: string) => void;
|
||||
isOpen: boolean;
|
||||
setIsOpen: (open: boolean) => void;
|
||||
}>({ isOpen: false, setIsOpen: () => {} });
|
||||
|
||||
export function Select({ value, onValueChange, defaultValue, disabled, children }: SelectProps) {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
||||
|
||||
const currentValue = value !== undefined ? value : internalValue;
|
||||
|
||||
const handleValueChange = (newValue: string) => {
|
||||
if (value === undefined) {
|
||||
setInternalValue(newValue);
|
||||
}
|
||||
onValueChange?.(newValue);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<SelectContext.Provider value={{ value: currentValue, onValueChange: handleValueChange, isOpen, setIsOpen }}>
|
||||
<div className="relative">{children}</div>
|
||||
</SelectContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function SelectTrigger({ id, className = "", children }: SelectTriggerProps) {
|
||||
const { isOpen, setIsOpen } = React.useContext(SelectContext);
|
||||
|
||||
return (
|
||||
<button
|
||||
id={id}
|
||||
type="button"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className={`flex h-10 w-full items-center justify-between rounded-md border border-gray-300 bg-white px-3 py-2 text-sm ${className}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function SelectValue({ placeholder }: SelectValueProps) {
|
||||
const { value } = React.useContext(SelectContext);
|
||||
|
||||
return <span>{value || placeholder}</span>;
|
||||
}
|
||||
|
||||
export function SelectContent({ children }: SelectContentProps) {
|
||||
const { isOpen } = React.useContext(SelectContext);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border border-gray-300 bg-white shadow-lg">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SelectItem({ value, children }: SelectItemProps) {
|
||||
const { onValueChange } = React.useContext(SelectContext);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => onValueChange?.(value)}
|
||||
className="cursor-pointer px-3 py-2 text-sm hover:bg-gray-100"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
28
apps/web/src/components/ui/switch.tsx
Normal file
28
apps/web/src/components/ui/switch.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import * as React from "react";
|
||||
|
||||
export interface SwitchProps {
|
||||
id?: string;
|
||||
checked?: boolean;
|
||||
onCheckedChange?: (checked: boolean) => void;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
|
||||
({ id, checked, onCheckedChange, disabled, className = "" }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
ref={ref}
|
||||
id={id}
|
||||
checked={checked}
|
||||
onChange={(e) => onCheckedChange?.(e.target.checked)}
|
||||
disabled={disabled}
|
||||
className={`w-11 h-6 rounded-full ${className}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Switch.displayName = "Switch";
|
||||
2
apps/web/src/components/ui/textarea.tsx
Normal file
2
apps/web/src/components/ui/textarea.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export { Textarea } from "@mosaic/ui";
|
||||
export type { TextareaProps } from "@mosaic/ui";
|
||||
@@ -7,7 +7,7 @@
|
||||
* - Automatic token refresh
|
||||
*/
|
||||
import { createAuthClient } from "better-auth/react";
|
||||
import { credentialsClient } from "better-auth-credentials-plugin/client";
|
||||
// Note: Credentials plugin import removed - better-auth has built-in credentials support
|
||||
|
||||
/**
|
||||
* Auth client instance configured for Jarvis.
|
||||
@@ -18,11 +18,8 @@ export const authClient = createAuthClient({
|
||||
? window.location.origin
|
||||
: process.env.BETTER_AUTH_URL || "http://localhost:3042",
|
||||
|
||||
// Plugins
|
||||
plugins: [
|
||||
// Credentials client for username/password auth
|
||||
credentialsClient(),
|
||||
],
|
||||
// Plugins can be added here when needed
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
|
||||
Reference in New Issue
Block a user