diff --git a/app/chat/[id]/page.tsx b/app/chat/[id]/page.tsx index 409e48c..4e7c7a7 100644 --- a/app/chat/[id]/page.tsx +++ b/app/chat/[id]/page.tsx @@ -3,25 +3,17 @@ import { generateId } from "ai"; import { useActions, useUIState } from "ai/rsc"; import Link from "next/link"; -import { use, useEffect } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { useChat } from "@/components/chat/context"; -import { ArrowUpIcon, ChevronRightIcon, CircleIcon, Market0Icon } from "@/components/icons"; +import { Examples } from "@/components/chat/examples"; +import { ArrowUpIcon, CircleIcon, Market0Icon } from "@/components/icons"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; import { Form, FormControl, FormField, FormItem } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { useScrollAnchor } from "@/hooks/chat/use-scroll-anchor"; -import { examples, menuItems } from "@/lib/examples"; +import { examples } from "@/lib/examples"; import { cn } from "@/lib/utils"; import { ClientMessage } from "@/llm/types"; import { useUser } from "@auth0/nextjs-auth0/client"; @@ -77,7 +69,9 @@ export default function Chat({ params }: { params: { id: string } }) {
{conversation.map((message: ClientMessage) => message.role === "user" ? ( @@ -105,15 +99,15 @@ export default function Chat({ params }: { params: { id: string } }) {
{conversation.length === 0 && (
-
+
-

+

Welcome to{" "} Market0

-

+

Market0 is a demo app that showcases secure auth patterns for GenAI apps

@@ -122,7 +116,7 @@ export default function Chat({ params }: { params: { id: string } }) { Get started with these examples
-
+
{examples.map((example) => (
)} -
-
+
+
- {!form.formState.disabled && ( - - - - - - - {menuItems.map((menuItem, idx) => ( - -
- {menuItem.icon} - {menuItem.message} -
- -
- ))} -
-
-
- )} + {!form.formState.disabled && }
{conversation.length > 0 && ( -
+
Market0 is a demo app that showcases secure auth patterns for GenAI apps
)} diff --git a/components/chat/examples.tsx b/components/chat/examples.tsx new file mode 100644 index 0000000..2aa6c45 --- /dev/null +++ b/components/chat/examples.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { ArrowUpIcon, ChevronRightIcon, CloseIcon } from "@/components/icons"; +import { Button } from "@/components/ui/button"; +import { Drawer, DrawerClose, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from "@/components/ui/drawer"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { menuItems } from "@/lib/examples"; +import { cn } from "@/lib/utils"; + +function ExamplesDesktop({ onExampleClick }: { onExampleClick: (input: string) => () => Promise }) { + return ( +
+ + + + + + + {menuItems.map((menuItem, idx) => ( + +
+ {menuItem.icon} + {menuItem.message} +
+ +
+ ))} +
+
+
+
+ ); +} + +function ExamplesMobile({ onExampleClick }: { onExampleClick: (input: string) => () => Promise }) { + return ( +
+ + + + + + + +
+
Examples
+ + + + +
+
+
+
    + {menuItems.map((menuItem, idx) => ( +
  • + + + +
  • + ))} +
+
+
+
+ ); +} + +export function Examples({ onExampleClick }: { onExampleClick: (input: string) => () => Promise }) { + return ( + <> + + + + ); +} diff --git a/components/chat/header.tsx b/components/chat/header.tsx index 33806d2..ae217cb 100644 --- a/components/chat/header.tsx +++ b/components/chat/header.tsx @@ -1,17 +1,16 @@ import Link from "next/link"; -import { ArrowRightIcon, DiscordIcon, GHIcon, IconAuth0 } from "@/components/icons"; +import { ArrowRightIcon, IconAuth0 } from "@/components/icons"; import { getSession } from "@auth0/nextjs-auth0"; -import UserButton from "../auth0/user-button"; -import { DropdownMenu, DropdownMenuGroup, DropdownMenuItem, DropdownMenuShortcut } from "../ui/dropdown-menu"; +import { Menu } from "./mobile-menu"; export async function Header({ children }: { children?: React.ReactNode }) { const session = await getSession(); const user = session?.user!; return ( -
+
@@ -22,46 +21,13 @@ export async function Header({ children }: { children?: React.ReactNode }) { href="#" target="_blank" rel="noopener noreferrer" - className="hover:text-black transition-all duration-300 text-sm font-light text-slate-500 flex items-center gap-1" + className="hover:text-black transition-all duration-300 text-sm font-light text-slate-500 items-center gap-1 hidden sm:flex" > Learn about Auth for GenAI
-
-
- {children} - - - - - - - - - - - - - Profile - - ⌘P - - - - -
-
+ {children}
); } diff --git a/components/chat/mobile-menu.tsx b/components/chat/mobile-menu.tsx new file mode 100644 index 0000000..2956c95 --- /dev/null +++ b/components/chat/mobile-menu.tsx @@ -0,0 +1,168 @@ +import Link from "next/link"; + +import { + ArrowRightIcon, + CloseIcon, + DiscordIcon, + DiscordMenuIcon, + ExternalLink, + GHIcon, + GitHubMenuIcon, + IconAuth0, + LearnMenuIcon, + MenuIcon, + ShareMenuIcon, +} from "@/components/icons"; +import { Claims } from "@auth0/nextjs-auth0"; + +import UserButton, { getAvatarFallback } from "../auth0/user-button"; +import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar"; +import { Button } from "../ui/button"; +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "../ui/drawer"; +import { DropdownMenu, DropdownMenuGroup, DropdownMenuItem, DropdownMenuShortcut } from "../ui/dropdown-menu"; + +function MenuMobile({ user }: { user: Claims }) { + return ( +
+ + + + + + + +
+ + + + + + + +
+
+
+
    +
  • + +
    + + Learn about Auth for GenAI +
    + + +
  • +
  • +
    + + Share chat +
    + +
  • +
  • + +
    + + GitHub +
    + + +
  • +
  • + +
    + + Discord +
    + + +
  • +
+ + +
{user.email}
+
+
+
+
+ ); +} + +function MenuDesktop({ user, children }: { user: Claims; children?: React.ReactNode }) { + return ( +
+
+ {children} + + + + + + + + + + + + + + Profile + + ⌘P + + + + +
+
+ ); +} + +export function Menu({ user, children }: { user: Claims; children?: React.ReactNode }) { + return ( + <> + + {children} + + ); +} diff --git a/components/icons.tsx b/components/icons.tsx index 81adcba..16d3c50 100644 --- a/components/icons.tsx +++ b/components/icons.tsx @@ -634,7 +634,142 @@ function CircleCheckBigIcon() { ); } +function MenuIcon() { + return ( + + + + ); +} + +function CloseIcon() { + return ( + + + + ); +} + +function LearnMenuIcon() { + return ( + + + + + + ); +} + +function ShareMenuIcon() { + return ( + + + + + + + + + ); +} + +function GitHubMenuIcon() { + return ( + + + + + ); +} + +function DiscordMenuIcon() { + return ( + + + + + + + + + + ); +} + export { + LearnMenuIcon, + ShareMenuIcon, + GitHubMenuIcon, + DiscordMenuIcon, + CloseIcon, + MenuIcon, TaskIcon, CancelRedIcon, EarningsIcon, diff --git a/components/ui/drawer.tsx b/components/ui/drawer.tsx new file mode 100644 index 0000000..cd37099 --- /dev/null +++ b/components/ui/drawer.tsx @@ -0,0 +1,89 @@ +"use client"; + +import * as React from "react"; +import { Drawer as DrawerPrimitive } from "vaul"; + +import { cn } from "@/lib/utils"; + +const Drawer = ({ shouldScaleBackground = true, ...props }: React.ComponentProps) => ( + +); +Drawer.displayName = "Drawer"; + +const DrawerTrigger = DrawerPrimitive.Trigger; + +const DrawerPortal = DrawerPrimitive.Portal; + +const DrawerClose = DrawerPrimitive.Close; + +const DrawerOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName; + +const DrawerContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {/*
*/} + {children} + + +)); +DrawerContent.displayName = "DrawerContent"; + +const DrawerHeader = ({ className, ...props }: React.HTMLAttributes) => ( +
+); +DrawerHeader.displayName = "DrawerHeader"; + +const DrawerFooter = ({ className, ...props }: React.HTMLAttributes) => ( +
+); +DrawerFooter.displayName = "DrawerFooter"; + +const DrawerTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DrawerTitle.displayName = DrawerPrimitive.Title.displayName; + +const DrawerDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DrawerDescription.displayName = DrawerPrimitive.Description.displayName; + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +}; diff --git a/package-lock.json b/package-lock.json index e0d7562..a3b3921 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@neondatabase/serverless": "^0.9.4", "@openfga/sdk": "^0.6.2", "@radix-ui/react-avatar": "^1.1.0", - "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", @@ -51,6 +51,7 @@ "react-markdown": "^9.0.1", "tailwind-merge": "^2.5.2", "usehooks-ts": "^3.1.0", + "vaul": "^1.0.0", "yahoo-finance2": "^2.12.2", "zod": "^3.23.8" }, @@ -2804,25 +2805,64 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz", - "integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==", - "license": "MIT", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz", + "integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==", "dependencies": { "@radix-ui/primitive": "1.1.0", "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-dismissable-layer": "1.1.0", - "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.0", "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-portal": "1.1.1", - "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-slot": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.7" + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", + "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", @@ -2839,6 +2879,90 @@ } } }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", + "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", + "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", @@ -12792,6 +12916,18 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "node_modules/vaul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.0.0.tgz", + "integrity": "sha512-TegfMkwy86RSvSiIVREG6OqgRL7agqRsKYyWYacyVUAdpcIi34QoCOED476Mbf8J5d06e1hygSdvJhehlxEBhQ==", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", diff --git a/package.json b/package.json index d7605fe..5d51ea4 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@neondatabase/serverless": "^0.9.4", "@openfga/sdk": "^0.6.2", "@radix-ui/react-avatar": "^1.1.0", - "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", @@ -61,6 +61,7 @@ "react-markdown": "^9.0.1", "tailwind-merge": "^2.5.2", "usehooks-ts": "^3.1.0", + "vaul": "^1.0.0", "yahoo-finance2": "^2.12.2", "zod": "^3.23.8" },