- {!isShared && !session ? (
- <>
-
-
-
-
-
-
- Please{' '}
-
- log in
- {' '}
- or{' '}
-
- sign up
- {' '}
- to save and revisit your chat history!
-
-
-
-
- >
- ) : null}
-
{messages.map((message, index) => (
{message.display}
diff --git a/components/chat.tsx b/components/chat.tsx
index c43d42a11..1e08bd2ae 100644
--- a/components/chat.tsx
+++ b/components/chat.tsx
@@ -15,11 +15,10 @@ import { toast } from 'sonner'
export interface ChatProps extends React.ComponentProps<'div'> {
initialMessages?: Message[]
id?: string
- session?: Session
missingKeys: string[]
}
-export function Chat({ id, className, session, missingKeys }: ChatProps) {
+export function Chat({ id, className, missingKeys }: ChatProps) {
const router = useRouter()
const path = usePathname()
const [input, setInput] = useState('')
@@ -28,14 +27,6 @@ export function Chat({ id, className, session, missingKeys }: ChatProps) {
const [_, setNewChatId] = useLocalStorage('newChatId', id)
- useEffect(() => {
- if (session?.user) {
- if (!path.includes('chat') && messages.length === 1) {
- window.history.replaceState({}, '', `/chat/${id}`)
- }
- }
- }, [id, path, session?.user, messages])
-
useEffect(() => {
const messagesLength = aiState.messages?.length
if (messagesLength === 2) {
@@ -66,7 +57,7 @@ export function Chat({ id, className, session, missingKeys }: ChatProps) {
ref={messagesRef}
>
{messages.length ? (
-
+
) : (
)}
diff --git a/components/header.tsx b/components/header.tsx
index 3b41a04a4..1af50fd03 100644
--- a/components/header.tsx
+++ b/components/header.tsx
@@ -2,78 +2,20 @@ import * as React from 'react'
import Link from 'next/link'
import { cn } from '@/lib/utils'
-import { auth } from '@/auth'
-import { Button, buttonVariants } from '@/components/ui/button'
+import { buttonVariants } from '@/components/ui/button'
import {
IconGitHub,
IconNextChat,
IconSeparator,
IconVercel
} from '@/components/ui/icons'
-import { UserMenu } from '@/components/user-menu'
-import { SidebarMobile } from './sidebar-mobile'
-import { SidebarToggle } from './sidebar-toggle'
-import { ChatHistory } from './chat-history'
-import { Session } from '@/lib/types'
-
-async function UserOrLogin() {
- const session = (await auth()) as Session
- return (
- <>
- {session?.user ? (
- <>
-
-
-
-
- >
- ) : (
-
-
-
-
- )}
-
-
- {session?.user ? (
-
- ) : (
-
- )}
-
- >
- )
-}
+import { UserButton } from '@clerk/nextjs'
export function Header() {
return (
)
diff --git a/components/sidebar-actions.tsx b/components/sidebar-actions.tsx
deleted file mode 100644
index 4f90f2d5d..000000000
--- a/components/sidebar-actions.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-'use client'
-
-import { useRouter } from 'next/navigation'
-import * as React from 'react'
-import { toast } from 'sonner'
-
-import { ServerActionResult, type Chat } from '@/lib/types'
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle
-} from '@/components/ui/alert-dialog'
-import { Button } from '@/components/ui/button'
-import { IconShare, IconSpinner, IconTrash } from '@/components/ui/icons'
-import { ChatShareDialog } from '@/components/chat-share-dialog'
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger
-} from '@/components/ui/tooltip'
-
-interface SidebarActionsProps {
- chat: Chat
- removeChat: (args: { id: string; path: string }) => ServerActionResult
- shareChat: (id: string) => ServerActionResult
-}
-
-export function SidebarActions({
- chat,
- removeChat,
- shareChat
-}: SidebarActionsProps) {
- const router = useRouter()
- const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false)
- const [shareDialogOpen, setShareDialogOpen] = React.useState(false)
- const [isRemovePending, startRemoveTransition] = React.useTransition()
-
- return (
- <>
-
-
-
-
-
- Share chat
-
-
-
-
-
- Delete chat
-
-
- setShareDialogOpen(false)}
- />
-
-
-
- Are you absolutely sure?
-
- This will permanently delete your chat message and remove your
- data from our servers.
-
-
-
-
- Cancel
-
- {
- event.preventDefault()
- // @ts-ignore
- startRemoveTransition(async () => {
- const result = await removeChat({
- id: chat.id,
- path: chat.path
- })
-
- if (result && 'error' in result) {
- toast.error(result.error)
- return
- }
-
- setDeleteDialogOpen(false)
- router.refresh()
- router.push('/')
- toast.success('Chat deleted')
- })
- }}
- >
- {isRemovePending && }
- Delete
-
-
-
-
- >
- )
-}
diff --git a/components/sidebar-desktop.tsx b/components/sidebar-desktop.tsx
deleted file mode 100644
index 7bc0e19c1..000000000
--- a/components/sidebar-desktop.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Sidebar } from '@/components/sidebar'
-
-import { auth } from '@/auth'
-import { ChatHistory } from '@/components/chat-history'
-
-export async function SidebarDesktop() {
- const session = await auth()
-
- if (!session?.user?.id) {
- return null
- }
-
- return (
-
- {/* @ts-ignore */}
-
-
- )
-}
diff --git a/components/sidebar-footer.tsx b/components/sidebar-footer.tsx
deleted file mode 100644
index a2e18ea66..000000000
--- a/components/sidebar-footer.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { cn } from '@/lib/utils'
-
-export function SidebarFooter({
- children,
- className,
- ...props
-}: React.ComponentProps<'div'>) {
- return (
-
- {children}
-
- )
-}
diff --git a/components/sidebar-item.tsx b/components/sidebar-item.tsx
deleted file mode 100644
index fc7020bcc..000000000
--- a/components/sidebar-item.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-'use client'
-
-import * as React from 'react'
-
-import Link from 'next/link'
-import { usePathname } from 'next/navigation'
-
-import { motion } from 'framer-motion'
-
-import { buttonVariants } from '@/components/ui/button'
-import { IconMessage, IconUsers } from '@/components/ui/icons'
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger
-} from '@/components/ui/tooltip'
-import { useLocalStorage } from '@/lib/hooks/use-local-storage'
-import { type Chat } from '@/lib/types'
-import { cn } from '@/lib/utils'
-
-interface SidebarItemProps {
- index: number
- chat: Chat
- children: React.ReactNode
-}
-
-export function SidebarItem({ index, chat, children }: SidebarItemProps) {
- const pathname = usePathname()
-
- const isActive = pathname === chat.path
- const [newChatId, setNewChatId] = useLocalStorage('newChatId', null)
- const shouldAnimate = index === 0 && isActive && newChatId
-
- if (!chat?.id) return null
-
- return (
-
-
- {chat.sharePath ? (
-
-
-
-
- This is a shared chat.
-
- ) : (
-
- )}
-
-
-
-
- {shouldAnimate ? (
- chat.title.split('').map((character, index) => (
- {
- if (index === chat.title.length - 1) {
- setNewChatId(null)
- }
- }}
- >
- {character}
-
- ))
- ) : (
- {chat.title}
- )}
-
-
-
- {isActive && {children}
}
-
- )
-}
diff --git a/components/sidebar-items.tsx b/components/sidebar-items.tsx
deleted file mode 100644
index 11cc7fc47..000000000
--- a/components/sidebar-items.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-'use client'
-
-import { Chat } from '@/lib/types'
-import { AnimatePresence, motion } from 'framer-motion'
-
-import { removeChat, shareChat } from '@/app/actions'
-
-import { SidebarActions } from '@/components/sidebar-actions'
-import { SidebarItem } from '@/components/sidebar-item'
-
-interface SidebarItemsProps {
- chats?: Chat[]
-}
-
-export function SidebarItems({ chats }: SidebarItemsProps) {
- if (!chats?.length) return null
-
- return (
-
- {chats.map(
- (chat, index) =>
- chat && (
-
-
-
-
-
- )
- )}
-
- )
-}
diff --git a/components/sidebar-list.tsx b/components/sidebar-list.tsx
deleted file mode 100644
index 45c2f4ab8..000000000
--- a/components/sidebar-list.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { clearChats, getChats } from '@/app/actions'
-import { ClearHistory } from '@/components/clear-history'
-import { SidebarItems } from '@/components/sidebar-items'
-import { ThemeToggle } from '@/components/theme-toggle'
-import { redirect } from 'next/navigation'
-import { cache } from 'react'
-
-interface SidebarListProps {
- userId?: string
- children?: React.ReactNode
-}
-
-const loadChats = cache(async (userId?: string) => {
- return await getChats(userId)
-})
-
-export async function SidebarList({ userId }: SidebarListProps) {
- const chats = await loadChats(userId)
-
- if (!chats || 'error' in chats) {
- redirect('/')
- } else {
- return (
-
-
- {chats?.length ? (
-
-
-
- ) : (
-
- )}
-
-
-
- 0} />
-
-
- )
- }
-}
diff --git a/components/ui/form.tsx b/components/ui/form.tsx
new file mode 100644
index 000000000..b6daa654a
--- /dev/null
+++ b/components/ui/form.tsx
@@ -0,0 +1,178 @@
+"use client"
+
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { Slot } from "@radix-ui/react-slot"
+import {
+ Controller,
+ ControllerProps,
+ FieldPath,
+ FieldValues,
+ FormProvider,
+ useFormContext,
+} from "react-hook-form"
+
+import { cn } from "@/lib/utils"
+import { Label } from "@/components/ui/label"
+
+const Form = FormProvider
+
+type FormFieldContextValue<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+> = {
+ name: TName
+}
+
+const FormFieldContext = React.createContext(
+ {} as FormFieldContextValue
+)
+
+const FormField = <
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+>({
+ ...props
+}: ControllerProps) => {
+ return (
+
+
+
+ )
+}
+
+const useFormField = () => {
+ const fieldContext = React.useContext(FormFieldContext)
+ const itemContext = React.useContext(FormItemContext)
+ const { getFieldState, formState } = useFormContext()
+
+ const fieldState = getFieldState(fieldContext.name, formState)
+
+ if (!fieldContext) {
+ throw new Error("useFormField should be used within ")
+ }
+
+ const { id } = itemContext
+
+ return {
+ id,
+ name: fieldContext.name,
+ formItemId: `${id}-form-item`,
+ formDescriptionId: `${id}-form-item-description`,
+ formMessageId: `${id}-form-item-message`,
+ ...fieldState,
+ }
+}
+
+type FormItemContextValue = {
+ id: string
+}
+
+const FormItemContext = React.createContext(
+ {} as FormItemContextValue
+)
+
+const FormItem = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const id = React.useId()
+
+ return (
+
+
+
+ )
+})
+FormItem.displayName = "FormItem"
+
+const FormLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ const { error, formItemId } = useFormField()
+
+ return (
+
+ )
+})
+FormLabel.displayName = "FormLabel"
+
+const FormControl = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ ...props }, ref) => {
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
+
+ return (
+
+ )
+})
+FormControl.displayName = "FormControl"
+
+const FormDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { formDescriptionId } = useFormField()
+
+ return (
+
+ )
+})
+FormDescription.displayName = "FormDescription"
+
+const FormMessage = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, children, ...props }, ref) => {
+ const { error, formMessageId } = useFormField()
+ const body = error ? String(error?.message) : children
+
+ if (!body) {
+ return null
+ }
+
+ return (
+
+ {body}
+
+ )
+})
+FormMessage.displayName = "FormMessage"
+
+export {
+ useFormField,
+ Form,
+ FormItem,
+ FormLabel,
+ FormControl,
+ FormDescription,
+ FormMessage,
+ FormField,
+}
diff --git a/lib/chat/actions.tsx b/lib/chat/actions.tsx
index ab27c59bd..04c0d0f4e 100644
--- a/lib/chat/actions.tsx
+++ b/lib/chat/actions.tsx
@@ -17,6 +17,7 @@ import {
Stock,
Purchase
} from '@/components/stocks'
+import { auth } from '@clerk/nextjs/server'
import { z } from 'zod'
import { EventsSkeleton } from '@/components/stocks/events-skeleton'
@@ -30,10 +31,8 @@ import {
sleep,
nanoid
} from '@/lib/utils'
-import { saveChat } from '@/app/actions'
import { SpinnerMessage, UserMessage } from '@/components/stocks/message'
import { Chat, Message } from '@/lib/types'
-import { auth } from '@/auth'
const openai = createOpenAI({
baseURL: 'https://proxy.tune.app',
@@ -114,6 +113,9 @@ async function submitUserMessage(content: string) {
'use server'
const aiState = getMutableAIState()
+ const session = await auth()
+ const metadata = session.sessionClaims?.metadata
+ console.log(JSON.stringify(session))
aiState.update({
...aiState.get(),
@@ -133,7 +135,7 @@ async function submitUserMessage(content: string) {
const result = await streamUI({
model: openai('meta/llama-3.1-8b-instruct'),
initial: ,
- system: `Only reply with the letter "a""`,
+ system: `I have health information regarding a user, please help them diagnose the issue: ${JSON.stringify(metadata)}`,
messages: [
...aiState.get().messages.map((message: any) => ({
role: message.role,
@@ -190,52 +192,56 @@ export const AI = createAI({
confirmPurchase
},
initialUIState: [],
- initialAIState: { chatId: nanoid(), messages: [] },
- onGetUIState: async () => {
- 'use server'
-
- const session = await auth()
-
- if (session && session.user) {
- const aiState = getAIState() as Chat
-
- if (aiState) {
- const uiState = getUIStateFromAIState(aiState)
- return uiState
- }
- } else {
- return
- }
- },
- onSetAIState: async ({ state }) => {
- 'use server'
-
- const session = await auth()
-
- if (session && session.user) {
- const { chatId, messages } = state
-
- const createdAt = new Date()
- const userId = session.user.id as string
- const path = `/chat/${chatId}`
-
- const firstMessageContent = messages[0].content as string
- const title = firstMessageContent.substring(0, 100)
-
- const chat: Chat = {
- id: chatId,
- title,
- userId,
- createdAt,
- messages,
- path
- }
-
- await saveChat(chat)
- } else {
- return
- }
- }
+ initialAIState: { chatId: nanoid(), messages: [] }
+ //
+ // SET AND SAVE CHATS HERE
+ //
+ //
+ // onGetUIState: async () => {
+ // 'use server'
+
+ // const session = await auth()
+
+ // if (session && session.user) {
+ // const aiState = getAIState() as Chat
+
+ // if (aiState) {
+ // const uiState = getUIStateFromAIState(aiState)
+ // return uiState
+ // }
+ // } else {
+ // return
+ // }
+ // },
+ // onSetAIState: async ({ state }) => {
+ // 'use server'
+
+ // const session = await auth()
+
+ // if (session && session.user) {
+ // const { chatId, messages } = state
+
+ // const createdAt = new Date()
+ // const userId = session.user.id as string
+ // const path = `/chat/${chatId}`
+
+ // const firstMessageContent = messages[0].content as string
+ // const title = firstMessageContent.substring(0, 100)
+
+ // const chat: Chat = {
+ // id: chatId,
+ // title,
+ // userId,
+ // createdAt,
+ // messages,
+ // path
+ // }
+
+ // await saveChat(chat)
+ // } else {
+ // return
+ // }
+ // }
})
export const getUIStateFromAIState = (aiState: Chat) => {
diff --git a/middleware.ts b/middleware.ts
index d20c6c765..718650723 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -1,11 +1,7 @@
-import NextAuth from 'next-auth'
-import { authConfig } from './auth.config'
import { clerkMiddleware } from '@clerk/nextjs/server'
// fix this
-export default clerkMiddleware(() => {
- NextAuth(authConfig).auth
-})
+export default clerkMiddleware()
export const config = {
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)']
diff --git a/package.json b/package.json
index 5de065497..430d7507b 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,9 @@
},
"dependencies": {
"@ai-sdk/openai": "^0.0.53",
+ "@clerk/clerk-sdk-node": "^5.0.38",
"@clerk/nextjs": "^5.5.2",
+ "@hookform/resolvers": "^3.9.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
@@ -35,6 +37,7 @@
"openai": "^4.56.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
+ "react-hook-form": "^7.53.0",
"react-intersection-observer": "^9.10.3",
"react-markdown": "^8.0.7",
"react-syntax-highlighter": "^15.5.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 109dd1b37..580a225ab 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -11,9 +11,15 @@ importers:
'@ai-sdk/openai':
specifier: ^0.0.53
version: 0.0.53(zod@3.23.8)
+ '@clerk/clerk-sdk-node':
+ specifier: ^5.0.38
+ version: 5.0.38(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@clerk/nextjs':
specifier: ^5.5.2
version: 5.5.2(next@14.2.6(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@hookform/resolvers':
+ specifier: ^3.9.0
+ version: 3.9.0(react-hook-form@7.53.0(react@18.3.1))
'@radix-ui/react-alert-dialog':
specifier: ^1.1.1
version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -86,6 +92,9 @@ importers:
react-dom:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
+ react-hook-form:
+ specifier: ^7.53.0
+ version: 7.53.0(react@18.3.1)
react-intersection-observer:
specifier: ^9.10.3
version: 9.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -265,6 +274,10 @@ packages:
react: '>=18 || >=19.0.0-beta'
react-dom: '>=18 || >=19.0.0-beta'
+ '@clerk/clerk-sdk-node@5.0.38':
+ resolution: {integrity: sha512-mJp2HobC0k8K3j2YTfgmLzUm2elYwhkaLRnIe2cAr4ZLyTZryUB1bqijfbTL2DhQwdyCcvAXGQC4KxeXOnhYjA==}
+ engines: {node: '>=18.17.0'}
+
'@clerk/nextjs@5.5.2':
resolution: {integrity: sha512-xBxwKzjvaJOHY8iCD5AwBU39owLUJw2rC6ndlC+R7mlPTXBrGCjLUNPh8vkokM2z3qACM4mDOGGchj2L8jJ7GQ==}
engines: {node: '>=18.17.0'}
@@ -304,6 +317,11 @@ packages:
'@floating-ui/utils@0.2.7':
resolution: {integrity: sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==}
+ '@hookform/resolvers@3.9.0':
+ resolution: {integrity: sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==}
+ peerDependencies:
+ react-hook-form: ^7.0.0
+
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
@@ -1808,6 +1826,12 @@ packages:
peerDependencies:
react: ^18.3.1
+ react-hook-form@7.53.0:
+ resolution: {integrity: sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18 || ^19
+
react-intersection-observer@9.13.0:
resolution: {integrity: sha512-y0UvBfjDiXqC8h0EWccyaj4dVBWMxgEx0t5RGNzQsvkfvZwugnKwxpu70StY4ivzYuMajavwUDjH4LJyIki9Lw==}
peerDependencies:
@@ -2349,6 +2373,16 @@ snapshots:
react-dom: 18.3.1(react@18.3.1)
tslib: 2.4.1
+ '@clerk/clerk-sdk-node@5.0.38(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@clerk/backend': 1.11.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@clerk/shared': 2.7.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@clerk/types': 4.20.1
+ tslib: 2.4.1
+ transitivePeerDependencies:
+ - react
+ - react-dom
+
'@clerk/nextjs@5.5.2(next@14.2.6(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@clerk/backend': 1.11.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -2394,6 +2428,10 @@ snapshots:
'@floating-ui/utils@0.2.7': {}
+ '@hookform/resolvers@3.9.0(react-hook-form@7.53.0(react@18.3.1))':
+ dependencies:
+ react-hook-form: 7.53.0(react@18.3.1)
+
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
@@ -3972,6 +4010,10 @@ snapshots:
react: 18.3.1
scheduler: 0.23.2
+ react-hook-form@7.53.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
react-intersection-observer@9.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
react: 18.3.1
From be054cdb0cf8b911bbce1a311fc52c5e7b237293 Mon Sep 17 00:00:00 2001
From: Yiyun Yao <106117469+nyonyoko@users.noreply.github.com>
Date: Sat, 14 Sep 2024 20:04:58 -0400
Subject: [PATCH 04/11] Form for clerk
---
app/dashboard/actions/update-heartreate.ts | 3 +-
app/dashboard/components/form.tsx | 46 ++++++++++++++++------
app/dashboard/page.tsx | 22 +----------
3 files changed, 38 insertions(+), 33 deletions(-)
diff --git a/app/dashboard/actions/update-heartreate.ts b/app/dashboard/actions/update-heartreate.ts
index c7057f1c6..59476f84b 100644
--- a/app/dashboard/actions/update-heartreate.ts
+++ b/app/dashboard/actions/update-heartreate.ts
@@ -13,7 +13,8 @@ export async function updateHeartRate({
if (!user_id) {
throw new Error('YOU ARE NOT LOGGED IN')
}
+ // Update the postgres database???
await clerkClient.users.updateUser(user_id, {
- publicMetadata: { heartRate: newHeartRate }
+ publicMetadata: { heartRate: newHeartRate, weight: '1234' }
})
}
diff --git a/app/dashboard/components/form.tsx b/app/dashboard/components/form.tsx
index f5a97ce70..6eefb1a08 100644
--- a/app/dashboard/components/form.tsx
+++ b/app/dashboard/components/form.tsx
@@ -16,23 +16,31 @@ import {
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { updateHeartRate } from '../actions/update-heartreate'
+import { useState } from 'react'
const formSchema = z.object({
heartrate: z.string().min(2, {
message: 'heartrate must be at least 2 characters.'
+ }),
+ weight: z.string().min(2, {
+ message: 'weight must be at least 2 characters.'
})
})
export function ProfileForm() {
+ const [submitting, setSubmitting] = useState(false)
const form = useForm>({
resolver: zodResolver(formSchema),
defaultValues: {
- heartrate: 123
+ heartrate: '123',
+ weight: '123'
}
})
// 2. Define a submit handler.
async function onSubmit(values: z.infer) {
+ setSubmitting(true)
await updateHeartRate({ newHeartRate: values.heartrate })
+ setSubmitting(false)
console.log(values)
}
@@ -44,19 +52,33 @@ export function ProfileForm() {
control={form.control}
name="heartrate"
render={({ field }) => (
-
- heartrate
-
-
-
-
- This is your public display name.
-
-
-
+ <>
+
+ heartrate
+
+
+
+
+ This is your public display name.
+
+
+
+
+ weight
+
+
+
+
+ This is your public display name.
+
+
+
+ >
)}
/>
-
+
)
diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx
index e9ca128ca..44591ca5f 100644
--- a/app/dashboard/page.tsx
+++ b/app/dashboard/page.tsx
@@ -3,16 +3,14 @@ import {
Dialog,
DialogContent,
DialogDescription,
- DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger
} from '@/components/ui/dialog'
-import { Input } from '@/components/ui/input'
-import { Label } from '@/components/ui/label'
+
import { ProfileForm } from './components/form'
-export function DialogDemo() {
+export default function Page() {
return (
)
}
-
-export default function Page() {
- return (
-
-
-
- )
-}
-
-function SomeComponent() {
- return (
-
-
asdad
-
- )
-}
From 6096dba9ec2a84916f4ed3e10da889c0570b0c58 Mon Sep 17 00:00:00 2001
From: Yiyun Yao <106117469+nyonyoko@users.noreply.github.com>
Date: Sat, 14 Sep 2024 22:31:33 -0400
Subject: [PATCH 05/11] Adding patient/Doctor
---
app/(chat)/chat/[id]/page.tsx | 19 -------------------
app/{(chat) => chat}/layout.tsx | 0
app/{(chat) => chat}/page.tsx | 0
app/doctor/layout.tsx | 21 +++++++++++++++++++++
app/doctor/page.tsx | 7 +++++++
app/page.tsx | 13 +++++++++++++
app/patient/layout.tsx | 21 +++++++++++++++++++++
app/patient/page.tsx | 7 +++++++
components/header.tsx | 4 +++-
9 files changed, 72 insertions(+), 20 deletions(-)
delete mode 100644 app/(chat)/chat/[id]/page.tsx
rename app/{(chat) => chat}/layout.tsx (100%)
rename app/{(chat) => chat}/page.tsx (100%)
create mode 100644 app/doctor/layout.tsx
create mode 100644 app/doctor/page.tsx
create mode 100644 app/page.tsx
create mode 100644 app/patient/layout.tsx
create mode 100644 app/patient/page.tsx
diff --git a/app/(chat)/chat/[id]/page.tsx b/app/(chat)/chat/[id]/page.tsx
deleted file mode 100644
index 0611ad6dd..000000000
--- a/app/(chat)/chat/[id]/page.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { getMissingKeys } from '@/app/actions'
-import { Chat } from '@/components/chat'
-import { AI } from '@/lib/chat/actions'
-
-export interface ChatPageProps {
- params: {
- id: string
- }
-}
-
-export default async function ChatPage() {
- const missingKeys = await getMissingKeys()
-
- return (
-
-
-
- )
-}
diff --git a/app/(chat)/layout.tsx b/app/chat/layout.tsx
similarity index 100%
rename from app/(chat)/layout.tsx
rename to app/chat/layout.tsx
diff --git a/app/(chat)/page.tsx b/app/chat/page.tsx
similarity index 100%
rename from app/(chat)/page.tsx
rename to app/chat/page.tsx
diff --git a/app/doctor/layout.tsx b/app/doctor/layout.tsx
new file mode 100644
index 000000000..a7912de5a
--- /dev/null
+++ b/app/doctor/layout.tsx
@@ -0,0 +1,21 @@
+import { redirect } from 'next/navigation'
+import { auth } from '@clerk/nextjs/server'
+
+export default async function DoctorLayout({
+ children
+}: {
+ children: React.ReactNode
+}) {
+ const session = await auth()
+
+ const slug = session.orgSlug
+
+ if (!slug || slug !== 'doctor') {
+ redirect('/')
+ }
+ return (
+
+ {children}
+
+ )
+}
diff --git a/app/doctor/page.tsx b/app/doctor/page.tsx
new file mode 100644
index 000000000..c490ff8a6
--- /dev/null
+++ b/app/doctor/page.tsx
@@ -0,0 +1,7 @@
+export default async function () {
+ return (
+
+
DOCTOR
+
+ )
+}
diff --git a/app/page.tsx b/app/page.tsx
new file mode 100644
index 000000000..7f5c051de
--- /dev/null
+++ b/app/page.tsx
@@ -0,0 +1,13 @@
+import { OrganizationList } from '@clerk/nextjs'
+import { auth } from '@clerk/nextjs/server'
+import { redirect } from 'next/navigation'
+
+export default async function Page() {
+ const session = await auth()
+
+ const org = session.orgSlug
+
+ if (!org) return
+
+ redirect(`/${org}`)
+}
diff --git a/app/patient/layout.tsx b/app/patient/layout.tsx
new file mode 100644
index 000000000..8051974bb
--- /dev/null
+++ b/app/patient/layout.tsx
@@ -0,0 +1,21 @@
+import { redirect } from 'next/navigation'
+import { auth } from '@clerk/nextjs/server'
+
+export default async function DoctorLayout({
+ children
+}: {
+ children: React.ReactNode
+}) {
+ const session = await auth()
+
+ const slug = session.orgSlug
+
+ if (!slug || slug !== 'patient') {
+ redirect('/')
+ }
+ return (
+
+ {children}
+
+ )
+}
diff --git a/app/patient/page.tsx b/app/patient/page.tsx
new file mode 100644
index 000000000..18f44b6fd
--- /dev/null
+++ b/app/patient/page.tsx
@@ -0,0 +1,7 @@
+export default async function () {
+ return (
+
+
PATIENT
+
+ )
+}
diff --git a/components/header.tsx b/components/header.tsx
index 1af50fd03..6b2df88b4 100644
--- a/components/header.tsx
+++ b/components/header.tsx
@@ -9,13 +9,15 @@ import {
IconSeparator,
IconVercel
} from '@/components/ui/icons'
-import { UserButton } from '@clerk/nextjs'
+import { OrganizationSwitcher, UserButton } from '@clerk/nextjs'
export function Header() {
return (
)
From 7834659139c05fc041ff2b014d30f5cbce0908d3 Mon Sep 17 00:00:00 2001
From: Yiyun Yao <106117469+nyonyoko@users.noreply.github.com>
Date: Sat, 14 Sep 2024 23:00:35 -0400
Subject: [PATCH 06/11] adding dashboards
---
app/api/terra/route.ts | 6 ++
app/dashboard/page.tsx | 30 ------
.../actions/update-heartreate.ts | 0
.../components/form.tsx | 0
app/patient/page.tsx | 31 +++++-
lib/chat/actions.tsx | 95 +------------------
6 files changed, 36 insertions(+), 126 deletions(-)
create mode 100644 app/api/terra/route.ts
delete mode 100644 app/dashboard/page.tsx
rename app/{dashboard => patient}/actions/update-heartreate.ts (100%)
rename app/{dashboard => patient}/components/form.tsx (100%)
diff --git a/app/api/terra/route.ts b/app/api/terra/route.ts
new file mode 100644
index 000000000..7b109ac60
--- /dev/null
+++ b/app/api/terra/route.ts
@@ -0,0 +1,6 @@
+import { clerkClient } from '@clerk/clerk-sdk-node'
+
+export async function GET(request: Request) {
+ const clerk = clerkClient({ apiKey: process.env.CLERK_API_KEY })
+ return new Response('Hello, Terra!')
+}
diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx
deleted file mode 100644
index 44591ca5f..000000000
--- a/app/dashboard/page.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Button } from '@/components/ui/button'
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
- DialogTrigger
-} from '@/components/ui/dialog'
-
-import { ProfileForm } from './components/form'
-
-export default function Page() {
- return (
-
- )
-}
diff --git a/app/dashboard/actions/update-heartreate.ts b/app/patient/actions/update-heartreate.ts
similarity index 100%
rename from app/dashboard/actions/update-heartreate.ts
rename to app/patient/actions/update-heartreate.ts
diff --git a/app/dashboard/components/form.tsx b/app/patient/components/form.tsx
similarity index 100%
rename from app/dashboard/components/form.tsx
rename to app/patient/components/form.tsx
diff --git a/app/patient/page.tsx b/app/patient/page.tsx
index 18f44b6fd..44591ca5f 100644
--- a/app/patient/page.tsx
+++ b/app/patient/page.tsx
@@ -1,7 +1,30 @@
-export default async function () {
+import { Button } from '@/components/ui/button'
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger
+} from '@/components/ui/dialog'
+
+import { ProfileForm } from './components/form'
+
+export default function Page() {
return (
-
-
PATIENT
-
+
)
}
diff --git a/lib/chat/actions.tsx b/lib/chat/actions.tsx
index 04c0d0f4e..afb88a75e 100644
--- a/lib/chat/actions.tsx
+++ b/lib/chat/actions.tsx
@@ -2,35 +2,17 @@ import 'server-only'
import {
createAI,
- createStreamableUI,
getMutableAIState,
- getAIState,
streamUI,
createStreamableValue
} from 'ai/rsc'
import { createOpenAI } from '@ai-sdk/openai'
-import {
- spinner,
- BotCard,
- BotMessage,
- SystemMessage,
- Stock,
- Purchase
-} from '@/components/stocks'
+import { BotCard, BotMessage, Stock, Purchase } from '@/components/stocks'
import { auth } from '@clerk/nextjs/server'
-import { z } from 'zod'
-import { EventsSkeleton } from '@/components/stocks/events-skeleton'
import { Events } from '@/components/stocks/events'
-import { StocksSkeleton } from '@/components/stocks/stocks-skeleton'
import { Stocks } from '@/components/stocks/stocks'
-import { StockSkeleton } from '@/components/stocks/stock-skeleton'
-import {
- formatNumber,
- runAsyncFnWithoutBlocking,
- sleep,
- nanoid
-} from '@/lib/utils'
+import { nanoid } from '@/lib/utils'
import { SpinnerMessage, UserMessage } from '@/components/stocks/message'
import { Chat, Message } from '@/lib/types'
@@ -39,76 +21,6 @@ const openai = createOpenAI({
apiKey: process.env.TUNE_STUDIO_API_KEY
})
-async function confirmPurchase(symbol: string, price: number, amount: number) {
- 'use server'
-
- const aiState = getMutableAIState()
-
- const purchasing = createStreamableUI(
-
- {spinner}
-
- Purchasing {amount} ${symbol}...
-
-
- )
-
- const systemMessage = createStreamableUI(null)
-
- runAsyncFnWithoutBlocking(async () => {
- await sleep(1000)
-
- purchasing.update(
-
- {spinner}
-
- Purchasing {amount} ${symbol}... working on it...
-
-
- )
-
- await sleep(1000)
-
- purchasing.done(
-
-
- You have successfully purchased {amount} ${symbol}. Total cost:{' '}
- {formatNumber(amount * price)}
-
-
- )
-
- systemMessage.done(
-
- You have purchased {amount} shares of {symbol} at ${price}. Total cost ={' '}
- {formatNumber(amount * price)}.
-
- )
-
- aiState.done({
- ...aiState.get(),
- messages: [
- ...aiState.get().messages,
- {
- id: nanoid(),
- role: 'system',
- content: `[User has purchased ${amount} shares of ${symbol} at ${price}. Total cost = ${
- amount * price
- }]`
- }
- ]
- })
- })
-
- return {
- purchasingUI: purchasing.value,
- newMessage: {
- id: nanoid(),
- display: systemMessage.value
- }
- }
-}
-
async function submitUserMessage(content: string) {
'use server'
@@ -188,8 +100,7 @@ export type UIState = {
export const AI = createAI({
actions: {
- submitUserMessage,
- confirmPurchase
+ submitUserMessage
},
initialUIState: [],
initialAIState: { chatId: nanoid(), messages: [] }
From b03be13637781212d76b250f55a65f2f42bf7962 Mon Sep 17 00:00:00 2001
From: Yiyun Yao <106117469+nyonyoko@users.noreply.github.com>
Date: Sat, 14 Sep 2024 23:13:51 -0400
Subject: [PATCH 07/11] fixing next builds
---
.env.example | 9 ---
app/api/terra/route.ts | 9 ++-
app/new/page.tsx | 5 --
app/patient/components/form.tsx | 54 +++++++++---------
components/chat-panel.tsx | 33 -----------
components/login-button.tsx | 42 --------------
components/login-form.tsx | 97 ---------------------------------
components/signup-form.tsx | 95 --------------------------------
components/user-menu.tsx | 53 ------------------
9 files changed, 35 insertions(+), 362 deletions(-)
delete mode 100644 app/new/page.tsx
delete mode 100644 components/login-button.tsx
delete mode 100644 components/login-form.tsx
delete mode 100644 components/signup-form.tsx
delete mode 100644 components/user-menu.tsx
diff --git a/.env.example b/.env.example
index 4de4ec6df..89153aa2c 100644
--- a/.env.example
+++ b/.env.example
@@ -5,12 +5,3 @@ TUNE_STUDIO_API_KEY=XXXXXXXX
# Clerk
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=XXXXXXXX
CLERK_SECRET=XXXXXXXX
-
-# Generate a random secret: https://generate-secret.vercel.app/32 or `openssl rand -base64 32`
-AUTH_SECRET=XXXXXXXX
-
-# Instructions to create kv database here: https://vercel.com/docs/storage/vercel-kv/quickstart and
-KV_URL=XXXXXXXX
-KV_REST_API_URL=XXXXXXXX
-KV_REST_API_TOKEN=XXXXXXXX
-KV_REST_API_READ_ONLY_TOKEN=XXXXXXXX
\ No newline at end of file
diff --git a/app/api/terra/route.ts b/app/api/terra/route.ts
index 7b109ac60..767802c11 100644
--- a/app/api/terra/route.ts
+++ b/app/api/terra/route.ts
@@ -1,6 +1,9 @@
import { clerkClient } from '@clerk/clerk-sdk-node'
+import { NextResponse } from 'next/server'
-export async function GET(request: Request) {
- const clerk = clerkClient({ apiKey: process.env.CLERK_API_KEY })
- return new Response('Hello, Terra!')
+export const dynamic = 'force-dynamic'
+
+export async function GET() {
+ // Your API logic here
+ return NextResponse.json({ message: 'Hello from Terra API' })
}
diff --git a/app/new/page.tsx b/app/new/page.tsx
deleted file mode 100644
index d23589481..000000000
--- a/app/new/page.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { redirect } from 'next/navigation'
-
-export default async function NewPage() {
- redirect('/')
-}
diff --git a/app/patient/components/form.tsx b/app/patient/components/form.tsx
index 6eefb1a08..11d9c97ec 100644
--- a/app/patient/components/form.tsx
+++ b/app/patient/components/form.tsx
@@ -20,12 +20,13 @@ import { useState } from 'react'
const formSchema = z.object({
heartrate: z.string().min(2, {
- message: 'heartrate must be at least 2 characters.'
+ message: 'Heart rate must be at least 2 characters.'
}),
weight: z.string().min(2, {
- message: 'weight must be at least 2 characters.'
+ message: 'Weight must be at least 2 characters.'
})
})
+
export function ProfileForm() {
const [submitting, setSubmitting] = useState(false)
const form = useForm>({
@@ -36,7 +37,6 @@ export function ProfileForm() {
}
})
- // 2. Define a submit handler.
async function onSubmit(values: z.infer) {
setSubmitting(true)
await updateHeartRate({ newHeartRate: values.heartrate })
@@ -52,28 +52,32 @@ export function ProfileForm() {
control={form.control}
name="heartrate"
render={({ field }) => (
- <>
-
- heartrate
-
-
-
-
- This is your public display name.
-
-
-
-
- weight
-
-
-
-
- This is your public display name.
-
-
-
- >
+
+ Heart Rate
+
+
+
+ This is your heart rate value.
+
+
+ )}
+ />
+ (
+
+ Weight
+
+
+
+ This is your weight value.
+
+
)}
/>
- {messages?.length >= 2 ? (
-
-
- {id && title ? (
- <>
-
- setShareDialogOpen(false)}
- shareChat={shareChat}
- chat={{
- id,
- title,
- messages: aiState.messages
- }}
- />
- >
- ) : null}
-
-
- ) : null}
-
diff --git a/components/login-button.tsx b/components/login-button.tsx
deleted file mode 100644
index ae8f84274..000000000
--- a/components/login-button.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-'use client'
-
-import * as React from 'react'
-import { signIn } from 'next-auth/react'
-
-import { cn } from '@/lib/utils'
-import { Button, type ButtonProps } from '@/components/ui/button'
-import { IconGitHub, IconSpinner } from '@/components/ui/icons'
-
-interface LoginButtonProps extends ButtonProps {
- showGithubIcon?: boolean
- text?: string
-}
-
-export function LoginButton({
- text = 'Login with GitHub',
- showGithubIcon = true,
- className,
- ...props
-}: LoginButtonProps) {
- const [isLoading, setIsLoading] = React.useState(false)
- return (
-
- )
-}
diff --git a/components/login-form.tsx b/components/login-form.tsx
deleted file mode 100644
index 0a6910d63..000000000
--- a/components/login-form.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-'use client'
-
-import { useFormState, useFormStatus } from 'react-dom'
-import { authenticate } from '@/app/login/actions'
-import Link from 'next/link'
-import { useEffect } from 'react'
-import { toast } from 'sonner'
-import { IconSpinner } from './ui/icons'
-import { getMessageFromCode } from '@/lib/utils'
-import { useRouter } from 'next/navigation'
-
-export default function LoginForm() {
- const router = useRouter()
- const [result, dispatch] = useFormState(authenticate, undefined)
-
- useEffect(() => {
- if (result) {
- if (result.type === 'error') {
- toast.error(getMessageFromCode(result.resultCode))
- } else {
- toast.success(getMessageFromCode(result.resultCode))
- router.refresh()
- }
- }
- }, [result, router])
-
- return (
-
- )
-}
-
-function LoginButton() {
- const { pending } = useFormStatus()
-
- return (
-
- )
-}
diff --git a/components/signup-form.tsx b/components/signup-form.tsx
deleted file mode 100644
index f5b78a966..000000000
--- a/components/signup-form.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-'use client'
-
-import { useFormState, useFormStatus } from 'react-dom'
-import { signup } from '@/app/signup/actions'
-import Link from 'next/link'
-import { useEffect } from 'react'
-import { toast } from 'sonner'
-import { IconSpinner } from './ui/icons'
-import { getMessageFromCode } from '@/lib/utils'
-import { useRouter } from 'next/navigation'
-
-export default function SignupForm() {
- const router = useRouter()
- const [result, dispatch] = useFormState(signup, undefined)
-
- useEffect(() => {
- if (result) {
- if (result.type === 'error') {
- toast.error(getMessageFromCode(result.resultCode))
- } else {
- toast.success(getMessageFromCode(result.resultCode))
- router.refresh()
- }
- }
- }, [result, router])
-
- return (
-
- )
-}
-
-function LoginButton() {
- const { pending } = useFormStatus()
-
- return (
-
- )
-}
diff --git a/components/user-menu.tsx b/components/user-menu.tsx
deleted file mode 100644
index 3ad28f034..000000000
--- a/components/user-menu.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { type Session } from '@/lib/types'
-
-import { Button } from '@/components/ui/button'
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger
-} from '@/components/ui/dropdown-menu'
-import { signOut } from '@/auth'
-
-export interface UserMenuProps {
- user: Session['user']
-}
-
-function getUserInitials(name: string) {
- const [firstName, lastName] = name.split(' ')
- return lastName ? `${firstName[0]}${lastName[0]}` : firstName.slice(0, 2)
-}
-
-export function UserMenu({ user }: UserMenuProps) {
- return (
-
-
-
-
-
-
-
- {user.email}
-
-
-
-
-
-
- )
-}
From e53b1e2e16da5db87e99b78032050f5dab3cf077 Mon Sep 17 00:00:00 2001
From: Yiyun Yao <106117469+nyonyoko@users.noreply.github.com>
Date: Sat, 14 Sep 2024 23:25:19 -0400
Subject: [PATCH 08/11] udpdate organization
---
components/header.tsx | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/components/header.tsx b/components/header.tsx
index 6b2df88b4..7df188b17 100644
--- a/components/header.tsx
+++ b/components/header.tsx
@@ -17,7 +17,10 @@ export function Header() {
-
+
)
From 99a30a371e9daa12a19d174efbf34d378a921a2a Mon Sep 17 00:00:00 2001
From: Yiyun Yao <106117469+nyonyoko@users.noreply.github.com>
Date: Sat, 14 Sep 2024 23:29:13 -0400
Subject: [PATCH 09/11] fix
---
app/page.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/page.tsx b/app/page.tsx
index 7f5c051de..5ed83957c 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -7,7 +7,7 @@ export default async function Page() {
const org = session.orgSlug
- if (!org) return
+ if (!org) return
redirect(`/${org}`)
}
From 49af760cd5f11cc345fdf4e892d60538dd21cfeb Mon Sep 17 00:00:00 2001
From: mahdi afshari
Date: Sun, 15 Sep 2024 03:36:22 -0400
Subject: [PATCH 10/11] added terra widget to the patient page
---
app/api/terra/generateWidgetSession/route.ts | 16 ++++
app/api/terra/route.ts | 9 --
app/patient/components/TerraWidget.tsx | 74 +++++++++++++++
app/patient/components/form.tsx | 96 ++------------------
app/patient/page.tsx | 47 +++++-----
5 files changed, 122 insertions(+), 120 deletions(-)
create mode 100644 app/api/terra/generateWidgetSession/route.ts
delete mode 100644 app/api/terra/route.ts
create mode 100644 app/patient/components/TerraWidget.tsx
diff --git a/app/api/terra/generateWidgetSession/route.ts b/app/api/terra/generateWidgetSession/route.ts
new file mode 100644
index 000000000..d86a29d0f
--- /dev/null
+++ b/app/api/terra/generateWidgetSession/route.ts
@@ -0,0 +1,16 @@
+import { NextRequest, NextResponse } from "next/server";
+import Terra from "terra-api";
+
+const terra = new Terra(process.env.TERRA_DEV_ID ?? "", process.env.TERRA_API_KEY ?? "", process.env.TERRA_WEBHOOK_SECRET ?? "");
+
+export async function GET(request: NextRequest) {
+ const resp = await terra.generateWidgetSession({
+ referenceID: "HelloHarvard",
+ language: "en",
+ authSuccessRedirectUrl: "http://localhost:3000",
+ authFailureRedirectUrl: "http://localhost:3000"
+ })
+ return NextResponse.json({ url: resp.url }, { status: 200});
+}
+
+
diff --git a/app/api/terra/route.ts b/app/api/terra/route.ts
deleted file mode 100644
index 767802c11..000000000
--- a/app/api/terra/route.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { clerkClient } from '@clerk/clerk-sdk-node'
-import { NextResponse } from 'next/server'
-
-export const dynamic = 'force-dynamic'
-
-export async function GET() {
- // Your API logic here
- return NextResponse.json({ message: 'Hello from Terra API' })
-}
diff --git a/app/patient/components/TerraWidget.tsx b/app/patient/components/TerraWidget.tsx
new file mode 100644
index 000000000..78c33afdd
--- /dev/null
+++ b/app/patient/components/TerraWidget.tsx
@@ -0,0 +1,74 @@
+'use client';
+
+import React, { useEffect, useState } from 'react';
+import { Button } from '@/components/ui/button';
+
+const api_key = process.env.NEXT_PUBLIC_TERRA_API_KEY;
+const reference_id = process.env.NEXT_PUBLIC_TERRA_DEV_ID;
+
+/// The getWidgetAsync function is an asynchronous function that fetches the widget session URL from the Terra API
+/// and sets the url state using the setUrl function.
+/// It takes an object with an onSuccess callback function as a parameter to set state back to the main component.
+export const getWidgetAsync = async (props: { onSuccess: any }) => {
+ try {
+ const response = await fetch(
+ 'https://api.tryterra.co/v2/auth/generateWidgetSession',
+ {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'dev-id': 'testingTerra',
+ 'content-type': 'application/json',
+ 'x-api-key': api_key as string,
+ },
+ body: JSON.stringify({
+ reference_id: reference_id,
+ providers:
+ 'GARMIN,WITHINGS,FITBIT,GOOGLE,OURA,WAHOO,PELOTON,ZWIFT,TRAININGPEAKS,FREESTYLELIBRE,DEXCOM,COROS,HUAWEI,OMRON,RENPHO,POLAR,SUUNTO,EIGHT,APPLE,CONCEPT2,WHOOP,IFIT,TEMPO,CRONOMETER,FATSECRET,NUTRACHECK,UNDERARMOUR',
+ language: 'en',
+ auth_success_redirect_url: 'terraficapp://request',
+ auth_failure_redirect_url: 'terraficapp://login',
+ }),
+ },
+ );
+ const json = await response.json();
+ props.onSuccess(json.url);
+ } catch (error) {
+ console.error(error);
+ }
+};
+
+export const Widget = () => {
+ const [url, setUrl] = useState('');
+
+ // Close the browser when authentication is completed. Authentication completion can be detected by listening to the incoming link.
+ // The success or fail url is logged into the console.
+ const _handleURL = (event: MessageEvent) => {
+ console.log(event.data);
+ };
+ // Open the browser for authentication using the Widget components.
+ const _handlePressButtonAsync = async () => {
+ getWidgetAsync({ onSuccess: setUrl });
+ // Use window.open instead of WebBrowser.openBrowserAsync
+ window.open(url, '_blank');
+ };
+
+ // set up an url listener and invoke getWidgetAsync when the component is mounted
+ useEffect(() => {
+ // Linking.addEventListener is not available in Next.js
+ // We can use window.addEventListener for similar functionality
+ window.addEventListener('message', _handleURL);
+ getWidgetAsync({ onSuccess: setUrl });
+
+ // Cleanup function
+ return () => {
+ window.removeEventListener('message', _handleURL);
+ };
+ }, []);
+
+ return (
+
+
+
+ );
+};
diff --git a/app/patient/components/form.tsx b/app/patient/components/form.tsx
index 11d9c97ec..138e8f266 100644
--- a/app/patient/components/form.tsx
+++ b/app/patient/components/form.tsx
@@ -1,89 +1,13 @@
-'use client'
-
-import { zodResolver } from '@hookform/resolvers/zod'
-import { useForm } from 'react-hook-form'
-import { z } from 'zod'
-
-import { Button } from '@/components/ui/button'
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage
-} from '@/components/ui/form'
-import { Input } from '@/components/ui/input'
-import { updateHeartRate } from '../actions/update-heartreate'
-import { useState } from 'react'
-
-const formSchema = z.object({
- heartrate: z.string().min(2, {
- message: 'Heart rate must be at least 2 characters.'
- }),
- weight: z.string().min(2, {
- message: 'Weight must be at least 2 characters.'
- })
-})
-
-export function ProfileForm() {
- const [submitting, setSubmitting] = useState(false)
- const form = useForm>({
- resolver: zodResolver(formSchema),
- defaultValues: {
- heartrate: '123',
- weight: '123'
- }
- })
-
- async function onSubmit(values: z.infer) {
- setSubmitting(true)
- await updateHeartRate({ newHeartRate: values.heartrate })
- setSubmitting(false)
-
- console.log(values)
- }
+import { Widget } from './TerraWidget'
+export function PatientForm() {
return (
-
-
+
+
Connect Your Health Device
+
+ Click the button below to connect your health device and start syncing your data.
+
+
+
)
-}
+}
\ No newline at end of file
diff --git a/app/patient/page.tsx b/app/patient/page.tsx
index 44591ca5f..e01bb74fb 100644
--- a/app/patient/page.tsx
+++ b/app/patient/page.tsx
@@ -1,30 +1,27 @@
-import { Button } from '@/components/ui/button'
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
- DialogTrigger
-} from '@/components/ui/dialog'
+'use client'
-import { ProfileForm } from './components/form'
+import { useEffect } from 'react'
+import { useSearchParams } from 'next/navigation'
+import { PatientForm } from './components/form'
+
+export default function PatientPage() {
+ const searchParams = useSearchParams()
+
+ useEffect(() => {
+ const authStatus = searchParams.get('auth')
+ if (authStatus === 'success') {
+ console.log('Device connected successfully')
+ // Handle successful connection (e.g., show a success message, update user state)
+ } else if (authStatus === 'failure') {
+ console.log('Device connection failed')
+ // Handle failed connection (e.g., show an error message)
+ }
+ }, [searchParams])
-export default function Page() {
return (
-
+
+
Patient Health Device Connection
+
+
)
}
From 7eb9973b6dcccab98a6b0ae1d3440553343113ff Mon Sep 17 00:00:00 2001
From: mahdi afshari
Date: Sun, 15 Sep 2024 04:26:59 -0400
Subject: [PATCH 11/11] api update
---
app/api/terra-widget/route.ts | 47 ++++++++++
app/api/terra/generateWidgetSession/route.ts | 19 ++--
app/patient/components/TerraWidget.tsx | 99 +++++++++-----------
3 files changed, 102 insertions(+), 63 deletions(-)
create mode 100644 app/api/terra-widget/route.ts
diff --git a/app/api/terra-widget/route.ts b/app/api/terra-widget/route.ts
new file mode 100644
index 000000000..fce162af5
--- /dev/null
+++ b/app/api/terra-widget/route.ts
@@ -0,0 +1,47 @@
+import { NextRequest, NextResponse } from "next/server";
+
+const api_key = process.env.TERRA_API_KEY;
+const reference_id = process.env.TERRA_DEV_ID;
+
+export async function POST(request: NextRequest) {
+ try {
+ console.log('API Key:', api_key);
+ console.log('Reference ID:', reference_id);
+
+ if (!api_key || !reference_id) {
+ console.error('Missing API key or reference ID');
+ return NextResponse.json({ error: 'Missing API key or reference ID' }, { status: 400 });
+ }
+
+ const response = await fetch(
+ 'https://api.tryterra.co/v2/auth/generateWidgetSession',
+ {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'dev-id': 'testingTerra',
+ 'content-type': 'application/json',
+ 'x-api-key': api_key,
+ },
+ body: JSON.stringify({
+ reference_id: reference_id,
+ providers:
+ 'GARMIN,WITHINGS,FITBIT,GOOGLE,OURA,WAHOO,PELOTON,ZWIFT,TRAININGPEAKS,FREESTYLELIBRE,DEXCOM,COROS,HUAWEI,OMRON,RENPHO,POLAR,SUUNTO,EIGHT,APPLE,CONCEPT2,WHOOP,IFIT,TEMPO,CRONOMETER,FATSECRET,NUTRACHECK,UNDERARMOUR',
+ language: 'en',
+ auth_success_redirect_url: 'terraficapp://request',
+ auth_failure_redirect_url: 'terraficapp://login',
+ }),
+ },
+ );
+
+ console.log('Terra API response status:', response.status);
+
+ const data = await response.json();
+ console.log('Terra API response data:', data);
+
+ return NextResponse.json(data);
+ } catch (error) {
+ console.error('Error in Terra API route:', error);
+ return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
+ }
+}
\ No newline at end of file
diff --git a/app/api/terra/generateWidgetSession/route.ts b/app/api/terra/generateWidgetSession/route.ts
index d86a29d0f..b2b40511b 100644
--- a/app/api/terra/generateWidgetSession/route.ts
+++ b/app/api/terra/generateWidgetSession/route.ts
@@ -4,13 +4,18 @@ import Terra from "terra-api";
const terra = new Terra(process.env.TERRA_DEV_ID ?? "", process.env.TERRA_API_KEY ?? "", process.env.TERRA_WEBHOOK_SECRET ?? "");
export async function GET(request: NextRequest) {
- const resp = await terra.generateWidgetSession({
- referenceID: "HelloHarvard",
- language: "en",
- authSuccessRedirectUrl: "http://localhost:3000",
- authFailureRedirectUrl: "http://localhost:3000"
- })
- return NextResponse.json({ url: resp.url }, { status: 200});
+ try {
+ const resp = await terra.generateWidgetSession({
+ referenceID: "HelloMIT",
+ language: "en",
+ authSuccessRedirectUrl: "http://localhost:3000",
+ authFailureRedirectUrl: "http://localhost:3000"
+ });
+ return NextResponse.json({ url: resp.url }, { status: 200 });
+ } catch (error) {
+ console.error('Error generating widget session:', error);
+ return NextResponse.json({ error: 'Failed to generate widget session' }, { status: 500 });
+ }
}
diff --git a/app/patient/components/TerraWidget.tsx b/app/patient/components/TerraWidget.tsx
index 78c33afdd..836c4b71c 100644
--- a/app/patient/components/TerraWidget.tsx
+++ b/app/patient/components/TerraWidget.tsx
@@ -1,74 +1,61 @@
'use client';
-import React, { useEffect, useState } from 'react';
+import React, { useState } from 'react';
import { Button } from '@/components/ui/button';
-const api_key = process.env.NEXT_PUBLIC_TERRA_API_KEY;
-const reference_id = process.env.NEXT_PUBLIC_TERRA_DEV_ID;
-
-/// The getWidgetAsync function is an asynchronous function that fetches the widget session URL from the Terra API
-/// and sets the url state using the setUrl function.
-/// It takes an object with an onSuccess callback function as a parameter to set state back to the main component.
-export const getWidgetAsync = async (props: { onSuccess: any }) => {
+export const getWidgetAsync = async (props: { onSuccess: (url: string) => void }) => {
try {
- const response = await fetch(
- 'https://api.tryterra.co/v2/auth/generateWidgetSession',
- {
- method: 'POST',
- headers: {
- Accept: 'application/json',
- 'dev-id': 'testingTerra',
- 'content-type': 'application/json',
- 'x-api-key': api_key as string,
- },
- body: JSON.stringify({
- reference_id: reference_id,
- providers:
- 'GARMIN,WITHINGS,FITBIT,GOOGLE,OURA,WAHOO,PELOTON,ZWIFT,TRAININGPEAKS,FREESTYLELIBRE,DEXCOM,COROS,HUAWEI,OMRON,RENPHO,POLAR,SUUNTO,EIGHT,APPLE,CONCEPT2,WHOOP,IFIT,TEMPO,CRONOMETER,FATSECRET,NUTRACHECK,UNDERARMOUR',
- language: 'en',
- auth_success_redirect_url: 'terraficapp://request',
- auth_failure_redirect_url: 'terraficapp://login',
- }),
- },
- );
+ console.log('Fetching widget URL...');
+ const response = await fetch('/api/terra/generateWidgetSession', { method: 'GET' });
+ console.log('Response status:', response.status);
const json = await response.json();
- props.onSuccess(json.url);
+ console.log('API Response:', json);
+ if (json.url) {
+ props.onSuccess(json.url);
+ } else {
+ console.error('No URL in response:', json);
+ }
} catch (error) {
- console.error(error);
+ console.error('Error in getWidgetAsync:', error);
}
};
export const Widget = () => {
- const [url, setUrl] = useState('');
-
- // Close the browser when authentication is completed. Authentication completion can be detected by listening to the incoming link.
- // The success or fail url is logged into the console.
- const _handleURL = (event: MessageEvent) => {
- console.log(event.data);
- };
- // Open the browser for authentication using the Widget components.
- const _handlePressButtonAsync = async () => {
- getWidgetAsync({ onSuccess: setUrl });
- // Use window.open instead of WebBrowser.openBrowserAsync
- window.open(url, '_blank');
+ const [error, setError] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+
+ const handlePressButton = async () => {
+ try {
+ setError(null);
+ setIsLoading(true);
+ console.log('Button clicked');
+ await getWidgetAsync({
+ onSuccess: (newUrl: string) => {
+ console.log('Received URL:', newUrl);
+ if (newUrl) {
+ window.open(newUrl, '_blank');
+ } else {
+ setError('Received empty URL from Terra API');
+ }
+ }
+ });
+ } catch (error) {
+ console.error('Error opening widget:', error);
+ setError('Failed to open widget');
+ } finally {
+ setIsLoading(false);
+ }
};
- // set up an url listener and invoke getWidgetAsync when the component is mounted
- useEffect(() => {
- // Linking.addEventListener is not available in Next.js
- // We can use window.addEventListener for similar functionality
- window.addEventListener('message', _handleURL);
- getWidgetAsync({ onSuccess: setUrl });
-
- // Cleanup function
- return () => {
- window.removeEventListener('message', _handleURL);
- };
- }, []);
-
return (
-
+
+ {error &&
{error}
}
);
};