Skip to content

Commit

Permalink
preview attachment
Browse files Browse the repository at this point in the history
  • Loading branch information
mooncityorg authored Jan 24, 2025
1 parent 44c5c9a commit 6106068
Show file tree
Hide file tree
Showing 56 changed files with 8,647 additions and 0 deletions.
84 changes: 84 additions & 0 deletions app/(auth)/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use server';

import { z } from 'zod';

import { createUser, getUser } from '@/lib/db/queries';

import { signIn } from './auth';

const authFormSchema = z.object({
email: z.string().email(),
password: z.string().min(6),
});

export interface LoginActionState {
status: 'idle' | 'in_progress' | 'success' | 'failed' | 'invalid_data';
}

export const login = async (
_: LoginActionState,
formData: FormData,
): Promise<LoginActionState> => {
try {
const validatedData = authFormSchema.parse({
email: formData.get('email'),
password: formData.get('password'),
});

await signIn('credentials', {
email: validatedData.email,
password: validatedData.password,
redirect: false,
});

return { status: 'success' };
} catch (error) {
if (error instanceof z.ZodError) {
return { status: 'invalid_data' };
}

return { status: 'failed' };
}
};

export interface RegisterActionState {
status:
| 'idle'
| 'in_progress'
| 'success'
| 'failed'
| 'user_exists'
| 'invalid_data';
}

export const register = async (
_: RegisterActionState,
formData: FormData,
): Promise<RegisterActionState> => {
try {
const validatedData = authFormSchema.parse({
email: formData.get('email'),
password: formData.get('password'),
});

const [user] = await getUser(validatedData.email);

if (user) {
return { status: 'user_exists' } as RegisterActionState;
}
await createUser(validatedData.email, validatedData.password);
await signIn('credentials', {
email: validatedData.email,
password: validatedData.password,
redirect: false,
});

return { status: 'success' };
} catch (error) {
if (error instanceof z.ZodError) {
return { status: 'invalid_data' };
}

return { status: 'failed' };
}
};
67 changes: 67 additions & 0 deletions components/app-sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use client';

import type { User } from 'next-auth';
import { useRouter } from 'next/navigation';

import { PlusIcon } from '@/components/icons';
import { SidebarHistory } from '@/components/sidebar-history';
import { SidebarUserNav } from '@/components/sidebar-user-nav';
import { Button } from '@/components/ui/button';
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarMenu,
useSidebar,
} from '@/components/ui/sidebar';
import Link from 'next/link';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';

export function AppSidebar({ user }: { user: User | undefined }) {
const router = useRouter();
const { setOpenMobile } = useSidebar();

return (
<Sidebar className="group-data-[side=left]:border-r-0">
<SidebarHeader>
<SidebarMenu>
<div className="flex flex-row justify-between items-center">
<Link
href="/"
onClick={() => {
setOpenMobile(false);
}}
className="flex flex-row gap-3 items-center"
>
<span className="text-lg font-semibold px-2 hover:bg-muted rounded-md cursor-pointer">
Chatbot
</span>
</Link>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
type="button"
className="p-2 h-fit"
onClick={() => {
setOpenMobile(false);
router.push('/');
router.refresh();
}}
>
<PlusIcon />
</Button>
</TooltipTrigger>
<TooltipContent align="end">New Chat</TooltipContent>
</Tooltip>
</div>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarHistory user={user} />
</SidebarContent>
<SidebarFooter>{user && <SidebarUserNav user={user} />}</SidebarFooter>
</Sidebar>
);
}
60 changes: 60 additions & 0 deletions components/auth-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Form from 'next/form';

import { Input } from './ui/input';
import { Label } from './ui/label';

export function AuthForm({
action,
children,
defaultEmail = '',
}: {
action: NonNullable<
string | ((formData: FormData) => void | Promise<void>) | undefined
>;
children: React.ReactNode;
defaultEmail?: string;
}) {
return (
<Form action={action} className="flex flex-col gap-4 px-4 sm:px-16">
<div className="flex flex-col gap-2">
<Label
htmlFor="email"
className="text-zinc-600 font-normal dark:text-zinc-400"
>
Email Address
</Label>

<Input
id="email"
name="email"
className="bg-muted text-md md:text-sm"
type="email"
placeholder="[email protected]"
autoComplete="email"
required
autoFocus
defaultValue={defaultEmail}
/>
</div>

<div className="flex flex-col gap-2">
<Label
htmlFor="password"
className="text-zinc-600 font-normal dark:text-zinc-400"
>
Password
</Label>

<Input
id="password"
name="password"
className="bg-muted text-md md:text-sm"
type="password"
required
/>
</div>

{children}
</Form>
);
}
126 changes: 126 additions & 0 deletions components/block-actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { cn } from '@/lib/utils';
import { ClockRewind, CopyIcon, RedoIcon, UndoIcon } from './icons';
import { Button } from './ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
import { toast } from 'sonner';
import { ConsoleOutput, UIBlock } from './block';
import { Dispatch, memo, SetStateAction } from 'react';
import { RunCodeButton } from './run-code-button';
import { useMultimodalCopyToClipboard } from '@/hooks/use-multimodal-copy-to-clipboard';

interface BlockActionsProps {
block: UIBlock;
handleVersionChange: (type: 'next' | 'prev' | 'toggle' | 'latest') => void;
currentVersionIndex: number;
isCurrentVersion: boolean;
mode: 'read-only' | 'edit' | 'diff';
setConsoleOutputs: Dispatch<SetStateAction<Array<ConsoleOutput>>>;
}

function PureBlockActions({
block,
handleVersionChange,
currentVersionIndex,
isCurrentVersion,
mode,
setConsoleOutputs,
}: BlockActionsProps) {
const { copyTextToClipboard, copyImageToClipboard } =
useMultimodalCopyToClipboard();

return (
<div className="flex flex-row gap-1">
{block.kind === 'code' && (
<RunCodeButton block={block} setConsoleOutputs={setConsoleOutputs} />
)}

{block.kind === 'text' && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
className={cn(
'p-2 h-fit !pointer-events-auto dark:hover:bg-zinc-700',
{
'bg-muted': mode === 'diff',
},
)}
onClick={() => {
handleVersionChange('toggle');
}}
disabled={
block.status === 'streaming' || currentVersionIndex === 0
}
>
<ClockRewind size={18} />
</Button>
</TooltipTrigger>
<TooltipContent>View changes</TooltipContent>
</Tooltip>
)}

<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
className="p-2 h-fit dark:hover:bg-zinc-700 !pointer-events-auto"
onClick={() => {
handleVersionChange('prev');
}}
disabled={currentVersionIndex === 0 || block.status === 'streaming'}
>
<UndoIcon size={18} />
</Button>
</TooltipTrigger>
<TooltipContent>View Previous version</TooltipContent>
</Tooltip>

<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
className="p-2 h-fit dark:hover:bg-zinc-700 !pointer-events-auto"
onClick={() => {
handleVersionChange('next');
}}
disabled={isCurrentVersion || block.status === 'streaming'}
>
<RedoIcon size={18} />
</Button>
</TooltipTrigger>
<TooltipContent>View Next version</TooltipContent>
</Tooltip>

<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
className="p-2 h-fit dark:hover:bg-zinc-700"
onClick={() => {
if (block.kind === 'image') {
copyImageToClipboard(block.content);
} else {
copyTextToClipboard(block.content);
}

toast.success('Copied to clipboard!');
}}
disabled={block.status === 'streaming'}
>
<CopyIcon size={18} />
</Button>
</TooltipTrigger>
<TooltipContent>Copy to clipboard</TooltipContent>
</Tooltip>
</div>
);
}

export const BlockActions = memo(PureBlockActions, (prevProps, nextProps) => {
if (prevProps.block.status !== nextProps.block.status) return false;
if (prevProps.currentVersionIndex !== nextProps.currentVersionIndex)
return false;
if (prevProps.isCurrentVersion !== nextProps.isCurrentVersion) return false;

return true;
});
29 changes: 29 additions & 0 deletions components/block-close-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { memo } from 'react';
import { CrossIcon } from './icons';
import { Button } from './ui/button';
import { initialBlockData, useBlock } from '@/hooks/use-block';

function PureBlockCloseButton() {
const { setBlock } = useBlock();

return (
<Button
variant="outline"
className="h-fit p-2 dark:hover:bg-zinc-700"
onClick={() => {
setBlock((currentBlock) =>
currentBlock.status === 'streaming'
? {
...currentBlock,
isVisible: false,
}
: { ...initialBlockData, status: 'idle' },
);
}}
>
<CrossIcon size={18} />
</Button>
);
}

export const BlockCloseButton = memo(PureBlockCloseButton, () => true);
Loading

0 comments on commit 6106068

Please sign in to comment.