diff --git a/apps/database-new/app/actions.ts b/apps/database-new/app/actions.ts index 81f972010fc..b75da415f47 100644 --- a/apps/database-new/app/actions.ts +++ b/apps/database-new/app/actions.ts @@ -6,6 +6,7 @@ import { cookies } from 'next/headers' import { redirect } from 'next/navigation' import OpenAI from 'openai' +import { z } from 'zod' const openai = new OpenAI() @@ -24,30 +25,73 @@ export async function logout() { redirect('/') } -export async function deleteThread(threadID: string) { +export async function deleteThread(prevState: any, formData: FormData) { const cookieStore = cookies() const supabase = createClient(cookieStore) try { - await supabase.from('threads').delete().eq('thread_id', threadID) - } catch (error) { - if (error) console.error('Error deleting thread: ', error) + const schema = z.object({ + thread_id: z.string(), + }) + + const data = schema.parse({ + thread_id: formData.get('thread_id'), + }) + + await supabase.from('threads').delete().eq('thread_id', data.thread_id) + await openai.beta.threads.del(data.thread_id) + + revalidatePath('/profile') + + return { + success: true, + data, + } + } catch (error: any) { + console.error(error) + return { + success: false, + message: 'Failed to delete the thread', + data: undefined, + } } - - await openai.beta.threads.del(threadID) - - revalidatePath('/profile') } -export async function updateThreadName(threadId: number, threadName: string) { +export async function updateThreadName(prevState: any, formData: FormData) { const cookieStore = cookies() const supabase = createClient(cookieStore) try { - await supabase.from('threads').update({ thread_title: threadName }).eq('id', threadId) - } catch (error) { - if (error) console.error('Error updating thread: ', error) - } + const schema = z.object({ + thread_title: z.string(), + row_id: z.string(), + }) - revalidatePath('/profile') + const data = schema.parse({ + thread_title: formData.get('thread_title'), + row_id: formData.get('row_id'), + }) + + const { error } = await supabase + .from('threads') + .update({ thread_title: data.thread_title }) + .eq('id', data.row_id) + if (error) { + throw error + } + revalidatePath('/profile') + + return { + message: 'Thread name updated to ' + data.thread_title, + success: true, + data, + } + } catch (error: any) { + console.error(error) + return { + success: false, + message: 'Failed to update title to update title', + data: undefined, + } + } } diff --git a/apps/database-new/app/globals.css b/apps/database-new/app/globals.css index ac424210b1a..62af4851ba2 100644 --- a/apps/database-new/app/globals.css +++ b/apps/database-new/app/globals.css @@ -140,3 +140,34 @@ main { } /* .monaco-editor .overflow-guard */ + +.shimmering-loader { + animation: shimmer 2s infinite linear; + background: linear-gradient( + to right, + hsl(var(--border-default)) 4%, + hsl(var(--background-surface-100)) 25%, + hsl(var(--border-default)) 36% + ); + background-size: 1000px 100%; +} + +.dark .shimmering-loader { + animation: shimmer 2s infinite linear; + background: linear-gradient( + to right, + hsl(var(--background-surface-100)) 4%, + hsl(var(--border-default)) 25%, + hsl(var(--background-surface-100)) 36% + ); + background-size: 1000px 100%; +} + +@keyframes shimmer { + 0% { + background-position: -1000px 0; + } + 100% { + background-position: 1000px 0; + } +} diff --git a/apps/database-new/app/profile/ConfirmDeleteThreadModal.tsx b/apps/database-new/app/profile/ConfirmDeleteThreadModal.tsx index 05e7900d275..b3bc7c3833d 100644 --- a/apps/database-new/app/profile/ConfirmDeleteThreadModal.tsx +++ b/apps/database-new/app/profile/ConfirmDeleteThreadModal.tsx @@ -1,8 +1,10 @@ 'use client' -import { Button, Modal } from 'ui' +import { useEffect, createRef } from 'react' +import { useFormState, useFormStatus } from 'react-dom' +import { Button, Input_Shadcn_, Modal } from 'ui' +import { deleteThread } from '@/app/actions' import { ThreadType } from './Threads' -import { deleteThread } from '../actions' const ConfirmDeleteThreadModal = ({ thread, @@ -13,6 +15,46 @@ const ConfirmDeleteThreadModal = ({ onClose: () => void visible: boolean }) => { + const formRef = createRef() + + const initialState = { + message: '', + success: false, + data: { + thread_id: thread.thread_id, + }, + } + + const [state, formAction] = useFormState(deleteThread, initialState) + + useEffect(() => { + if (state?.success === true) { + onClose() + formRef.current?.reset() + state.success = false + } + }, [state, onClose, formRef]) + + useEffect(() => { + if (state?.success === true) { + onClose() + formRef.current?.reset() + state.success = false + } + }, [state, onClose, formRef]) + + function SubmitButton() { + const { pending } = useFormStatus() + + return ( + + ) + } + + console.log('thread', thread) + return ( -
deleteThread(thread.thread_id)}> +

Once the thread is deleted, it cannot be recovered.

+ - +
diff --git a/apps/database-new/app/profile/EditThreadModal.tsx b/apps/database-new/app/profile/EditThreadModal.tsx index 0fd3ed84ebd..88edb787191 100644 --- a/apps/database-new/app/profile/EditThreadModal.tsx +++ b/apps/database-new/app/profile/EditThreadModal.tsx @@ -1,10 +1,10 @@ 'use client' -import { Button, Input, Modal } from 'ui' - +import { updateThreadName } from '@/app/actions' +import { createRef, useEffect } from 'react' +import { useFormState, useFormStatus } from 'react-dom' +import { Button, Input_Shadcn_, Label_Shadcn_, Modal } from 'ui' import { ThreadType } from './Threads' -import { useState } from 'react' -import { updateThreadName } from '../actions' const EditThreadModal = ({ thread, @@ -15,7 +15,36 @@ const EditThreadModal = ({ onClose: () => void visible: boolean }) => { - const [value, setValue] = useState(thread.thread_title) + const formRef = createRef() + + const initialState = { + message: undefined, + success: undefined, + data: { + row_id: thread.id, + thread_title: thread.thread_title, + }, + } + + const [state, formAction] = useFormState(updateThreadName, initialState) + + useEffect(() => { + if (state?.success === true) { + onClose() + formRef.current?.reset() + state.success = undefined + } + }, [state, onClose, formRef]) + + function SubmitButton() { + const { pending } = useFormStatus() + + return ( + + ) + } return ( -
{ - const threadNameEntry: FormDataEntryValue | null = formData.get('threadName') - - // Check if threadNameEntry is not null and is of type string - if (threadNameEntry !== null && typeof threadNameEntry === 'string') { - const threadName: string = threadNameEntry - updateThreadName(thread.id, threadName) - } - }} - > + - setValue(e.target.value)} + Provide a name for your thread + + - + +

+ {state?.message} +

diff --git a/apps/database-new/app/profile/EmptyState.tsx b/apps/database-new/app/profile/EmptyState.tsx index c799079a9e7..489bc7a6362 100644 --- a/apps/database-new/app/profile/EmptyState.tsx +++ b/apps/database-new/app/profile/EmptyState.tsx @@ -1,4 +1,5 @@ 'use client' + import Link from 'next/link' import { Button } from 'ui' diff --git a/apps/database-new/app/profile/Thread.tsx b/apps/database-new/app/profile/Thread.tsx index 738371666f1..38cc88859db 100644 --- a/apps/database-new/app/profile/Thread.tsx +++ b/apps/database-new/app/profile/Thread.tsx @@ -2,8 +2,8 @@ import { timeAgo } from '@/lib/utils' import Link from 'next/link' +import { useState } from 'react' import { - Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -15,10 +15,9 @@ import { import ConfirmDeleteThreadModal from './ConfirmDeleteThreadModal' import EditThreadModal from './EditThreadModal' import { ThreadType } from './Threads' -import { useState } from 'react' const Thread = ({ thread }: { thread: ThreadType }) => { - const formattedTimeAgo = timeAgo(thread.created_at) + const formattedTimeAgo = timeAgo(thread.modified_at) const [editOpen, setEditOpen] = useState(false) const [deleteOpen, setDeleteOpen] = useState(false) @@ -27,32 +26,45 @@ const Thread = ({ thread }: { thread: ThreadType }) => { <>
-
- - {thread.thread_title} +
+ + {thread.thread_title} - Last updated {formattedTimeAgo} + + Last updated {formattedTimeAgo} + +
+
+ + + + + + { + console.log('trying to edit thread') + setEditOpen(true) + }} + > + +

Edit name

+
+ setDeleteOpen(true)}> + +

Delete thread

+
+
+
- - - -
- {threads.length > 0 ? ( - threads.map((thread) => ) + threads.sort().map((thread) => ) ) : ( )} diff --git a/apps/database-new/app/profile/page.tsx b/apps/database-new/app/profile/page.tsx index 701b37a6b16..cf788f21f27 100644 --- a/apps/database-new/app/profile/page.tsx +++ b/apps/database-new/app/profile/page.tsx @@ -4,6 +4,7 @@ import { cookies } from 'next/headers' import { redirect } from 'next/navigation' import Threads from './Threads' import { Suspense } from 'react' +import ShimmeringLoader from '@/components/Shimmer' import type { Metadata } from 'next' dayjs.extend(relativeTime) @@ -45,7 +46,18 @@ const Profile = async () => {
- Loading...
}> + + + + + + + +
+ } + > diff --git a/apps/database-new/components/Shimmer.tsx b/apps/database-new/components/Shimmer.tsx new file mode 100644 index 00000000000..2a3aa3ae437 --- /dev/null +++ b/apps/database-new/components/Shimmer.tsx @@ -0,0 +1,24 @@ +const ANIMATION_DELAY = 150 + +const ShimmeringLoader = ({ className = '', delayIndex = 0, animationDelay = 150 }) => { + return ( +
+ ) +} + +const GenericSkeletonLoader = () => ( +
+ + + +
+) + +export { GenericSkeletonLoader } +export default ShimmeringLoader diff --git a/apps/database-new/package.json b/apps/database-new/package.json index 8f11cb19218..9fccee5f608 100644 --- a/apps/database-new/package.json +++ b/apps/database-new/package.json @@ -20,14 +20,15 @@ "eslint-config-next": "^14.0.3", "lodash": "^4.17.21", "lucide-react": "^0.292.0", - "next": "^14.0.3", "openai": "^4.17.0", + "next": "^14.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-flow": "^1.0.3", "sql-formatter": "^13.1.0", "ui": "*", - "valtio": "^1.12.0" + "valtio": "^1.12.0", + "zod": "^3.22.4" }, "devDependencies": { "@types/node": "^18.11.9", diff --git a/apps/database-new/react.d.ts b/apps/database-new/react.d.ts new file mode 100644 index 00000000000..61ba1013dc4 --- /dev/null +++ b/apps/database-new/react.d.ts @@ -0,0 +1,14 @@ +import 'react-dom' + +declare module 'react-dom' { + function useFormState( + action: (state: State) => Promise, + initialState: State, + permalink?: string + ): [state: State, dispatch: () => void] + function useFormState( + action: (state: State, payload: Payload) => Promise, + initialState: State, + permalink?: string + ): [state: State, dispatch: (payload: Payload) => void] +} diff --git a/package-lock.json b/package-lock.json index 8084c3939d1..8e34e80ad89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,8 @@ "react-flow": "^1.0.3", "sql-formatter": "^13.1.0", "ui": "*", - "valtio": "^1.12.0" + "valtio": "^1.12.0", + "zod": "^3.22.4" }, "devDependencies": { "@types/node": "^18.11.9", @@ -242,8 +243,9 @@ } }, "apps/database-new/node_modules/@types/node": { - "version": "18.18.9", - "license": "MIT", + "version": "18.18.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.13.tgz", + "integrity": "sha512-vXYZGRrSCreZmq1rEjMRLXJhiy8MrIeVasx+PCVlP414N7CJLHnMf+juVvjdprHyH+XRy3zKZLHeNueOpJCn0g==", "dependencies": { "undici-types": "~5.26.4" } @@ -11152,8 +11154,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.2.37", - "license": "MIT", + "version": "18.2.39", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz", + "integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -11196,9 +11199,10 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.8", + "version": "18.2.17", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.17.tgz", + "integrity": "sha512-rvrT/M7Df5eykWFxn6MYt5Pem/Dbyc1N8Y0S9Mrkw2WFCRiqUgw9P7ul2NpwsXCSM1DVdENzdG9J5SreqfAIWg==", "devOptional": true, - "license": "MIT", "dependencies": { "@types/react": "*" } @@ -36007,8 +36011,9 @@ "license": "MIT" }, "node_modules/zod": { - "version": "3.22.3", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", "funding": { "url": "https://github.com/sponsors/colinhacks" }