From efab162ff983d6875f76c8f9c62abedef1023cf3 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Sat, 25 Nov 2023 00:45:43 +0800 Subject: [PATCH] Chore/server actions (#19200) * Migrate database-new to use server actions. * Revert the package-lock.json changes, leftover from a rebased commit. * Add server actions config back * Cleanup * Cleanup * Undo --------- Co-authored-by: Ivan Vasilov Co-authored-by: Terry Sutton --- apps/database-new/app/[threadId]/Chat.tsx | 1 + apps/database-new/app/actions.ts | 53 ++++++++ apps/database-new/app/layout.tsx | 14 +-- .../app/profile/ConfirmDeleteThreadModal.tsx | 40 +++--- .../app/profile/EditThreadModal.tsx | 67 ++++++---- apps/database-new/app/profile/Thread.tsx | 88 +++++++------- apps/database-new/app/profile/Threads.tsx | 62 ++++------ apps/database-new/app/profile/page.tsx | 91 ++++++++------ apps/database-new/components/Footer.tsx | 4 +- .../components/Header/AvatarDropdown.tsx | 114 +++++++----------- .../database-new/components/Header/Header.tsx | 7 +- .../components/Header/HeaderActions.tsx | 4 +- .../components/Header/NoUserDropdown.tsx | 59 ++++----- .../components/Header/SaveSchemaDropdown.tsx | 14 +-- .../components/Header/UserDropdown.tsx | 48 ++++++++ apps/database-new/lib/actions.ts | 23 ---- apps/database-new/supabase/config.toml | 2 +- packages/ui/index.tsx | 2 + packages/ui/src/components/Button/Button.tsx | 2 + packages/ui/src/components/Modal/Modal.tsx | 2 + 20 files changed, 382 insertions(+), 315 deletions(-) create mode 100644 apps/database-new/app/actions.ts create mode 100644 apps/database-new/components/Header/UserDropdown.tsx delete mode 100644 apps/database-new/lib/actions.ts diff --git a/apps/database-new/app/[threadId]/Chat.tsx b/apps/database-new/app/[threadId]/Chat.tsx index 0f802dae565..8560f77ec1b 100644 --- a/apps/database-new/app/[threadId]/Chat.tsx +++ b/apps/database-new/app/[threadId]/Chat.tsx @@ -61,6 +61,7 @@ export const Chat = () => {
- + -
-
-
- {children} -
-
-
+
+
+ {children} +
+
diff --git a/apps/database-new/app/profile/ConfirmDeleteThreadModal.tsx b/apps/database-new/app/profile/ConfirmDeleteThreadModal.tsx index 1a804fd9246..9b759e03c17 100644 --- a/apps/database-new/app/profile/ConfirmDeleteThreadModal.tsx +++ b/apps/database-new/app/profile/ConfirmDeleteThreadModal.tsx @@ -1,35 +1,39 @@ -import { Modal } from 'ui' +'use client' + +import { Button, Modal } from 'ui' +import { deleteThread } from './../../app/actions' import { ThreadType } from './Threads' -import { deleteThread } from '@/lib/actions' const ConfirmDeleteThreadModal = ({ thread, onClose, + visible, }: { - thread?: ThreadType + thread: ThreadType onClose: () => void + visible: boolean }) => { - const deleteCurrentThread = () => { - const threadID = thread?.thread_id - onClose() - deleteThread(threadID!) - } - return ( { - await deleteCurrentThread() - }} + hideFooter header="Confirm to delete thread?" + className="pb-2" > - -

Once the thread is deleted, it cannot be recovered.

-
+
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 a61d9f9ad4a..4eeea121611 100644 --- a/apps/database-new/app/profile/EditThreadModal.tsx +++ b/apps/database-new/app/profile/EditThreadModal.tsx @@ -1,35 +1,58 @@ -import { Input, Modal } from 'ui' +'use client' + +import { Button, Input, Modal } from 'ui' +import { updateThreadName } from './../../app/actions' import { ThreadType } from './Threads' -import { useEffect, useState } from 'react' +import { useState } from 'react' -const EditThreadModal = ({ thread, onClose }: { thread?: ThreadType; onClose: () => void }) => { - const [value, setValue] = useState('') - - useEffect(() => { - if (thread !== undefined) setValue(thread.thread_title) - }, [thread]) - - const updateThread = () => { - // Logic here - onClose() - } +const EditThreadModal = ({ + thread, + onClose, + visible, +}: { + thread: ThreadType + onClose: () => void + visible: boolean +}) => { + const [value, setValue] = useState(thread.thread_title) return ( - - setValue(e.target.value)} - /> - +
{ + 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)} + /> + + + + + + +
) } diff --git a/apps/database-new/app/profile/Thread.tsx b/apps/database-new/app/profile/Thread.tsx index 66ceceea1e1..738371666f1 100644 --- a/apps/database-new/app/profile/Thread.tsx +++ b/apps/database-new/app/profile/Thread.tsx @@ -1,4 +1,5 @@ 'use client' + import { timeAgo } from '@/lib/utils' import Link from 'next/link' import { @@ -11,55 +12,60 @@ import { IconMoreVertical, IconTrash2, } from 'ui' +import ConfirmDeleteThreadModal from './ConfirmDeleteThreadModal' +import EditThreadModal from './EditThreadModal' import { ThreadType } from './Threads' +import { useState } from 'react' -const Thread = ({ - thread, - handleThreadActions, - onSelectEdit, - onSelectDelete, -}: { - thread: ThreadType - handleThreadActions: (formData: FormData) => void - onSelectEdit: () => void - onSelectDelete: () => void -}) => { +const Thread = ({ thread }: { thread: ThreadType }) => { const formattedTimeAgo = timeAgo(thread.created_at) - //[Joshen] Just FYI Terry sorry i had to peel out your form component here which handled the delete - // Ideal UX for delete is to have a confirmation modal, and edit to be in a modal too so need client - //
- // - //
+ const [editOpen, setEditOpen] = useState(false) + const [deleteOpen, setDeleteOpen] = useState(false) return ( -
-
- - {thread.thread_title} - -

Last updated {formattedTimeAgo}

+ <> +
+
+ + {thread.thread_title} + + Last updated {formattedTimeAgo} +
+ + + +
- - -
+ setDeleteOpen(false)} + /> + setEditOpen(false)} + /> + ) } diff --git a/apps/database-new/app/profile/Threads.tsx b/apps/database-new/app/profile/Threads.tsx index 9d1591a674c..14e4886c462 100644 --- a/apps/database-new/app/profile/Threads.tsx +++ b/apps/database-new/app/profile/Threads.tsx @@ -1,56 +1,36 @@ -'use client' import { Database } from '@/types/supabase' import EmptyState from './EmptyState' import Thread from './Thread' -import { useState, useEffect } from 'react' +// import { useState, useEffect } from 'react' import ConfirmDeleteThreadModal from './ConfirmDeleteThreadModal' import EditThreadModal from './EditThreadModal' +import { cookies } from 'next/headers' +import { createClient } from '@/lib/supabase/server' export type ThreadType = Database['public']['Tables']['threads']['Row'] -interface ThreadsProps { - threads: ThreadType[] - handleThreadActions: (formData: FormData) => void -} +async function Threads() { + const cookieStore = cookies() + const supabase = createClient(cookieStore) -const Threads = ({ threads, handleThreadActions }: ThreadsProps) => { - // To circumvent hydration errors, although not sure why its happening - const [mounted, setMounted] = useState(false) - const [selectedThreadToEdit, setSelectedThreadToEdit] = useState() - const [selectedThreadToDelete, setSelectedThreadToDelete] = useState() + const { + data: { user }, + } = await supabase.auth.getUser() - useEffect(() => { - setMounted(true) - }, []) + if (!user) return

Error fetching user details

+ + const { data } = await supabase.from('threads').select().eq('user_id', user.id) + + const threads = data ?? [] return ( - mounted && ( - <> -
- {threads.length > 0 ? ( - threads.map((thread) => ( - setSelectedThreadToEdit(thread)} - onSelectDelete={() => setSelectedThreadToDelete(thread)} - /> - )) - ) : ( - - )} -
- setSelectedThreadToDelete(undefined)} - /> - setSelectedThreadToEdit(undefined)} - /> - - ) +
+ {threads.length > 0 ? ( + threads.map((thread) => ) + ) : ( + + )} +
) } diff --git a/apps/database-new/app/profile/page.tsx b/apps/database-new/app/profile/page.tsx index 665d461838a..8b98328bd6a 100644 --- a/apps/database-new/app/profile/page.tsx +++ b/apps/database-new/app/profile/page.tsx @@ -1,9 +1,9 @@ -import { deleteThread } from '@/lib/actions' import { createClient } from '@/lib/supabase/server' import { default as dayjs, default as relativeTime } from 'dayjs' import { cookies } from 'next/headers' import { redirect } from 'next/navigation' import Threads from './Threads' +import { Suspense } from 'react' dayjs.extend(relativeTime) @@ -13,6 +13,43 @@ dayjs.extend(relativeTime) // export const dynamic = 'force-dynamic' const Profile = async () => { + // async function handleThreadActions(formData: FormData) { + // 'use server' + + // const action = formData.get('action') as string + // const threadID = formData.get('threadID') as string + + // if (!threadID) return + + // if (action === 'delete') { + // deleteThread(threadID) + // } + // } + + return ( +
+
+
+ Loading user...

}> + +
+
+
+ +
+

Past conversations

+ +
+ + Loading...
}> + + +
+
+ ) +} + +async function ProfileDetails() { const cookieStore = cookies() const supabase = createClient(cookieStore) @@ -22,45 +59,21 @@ const Profile = async () => { if (!user) redirect('/') - const { data } = await supabase.from('threads').select().eq('user_id', user.id) - const threads = data ?? [] - - async function handleThreadActions(formData: FormData) { - 'use server' - - const action = formData.get('action') as string - const threadID = formData.get('threadID') as string - - if (!threadID) return - - if (action === 'delete') { - deleteThread(threadID) - } - } - - return ( -
-
-
-
-
-

{user.user_metadata.full_name}

-

@{user.user_metadata.user_name}

-
-
+ return !user ? ( + // make this better! +

No user found

+ ) : ( + <> +
+
+

{user.user_metadata.full_name}

+

@{user.user_metadata.user_name}

- -
-

Past conversations

- -
- - -
-
+ ) } + export default Profile diff --git a/apps/database-new/components/Footer.tsx b/apps/database-new/components/Footer.tsx index 55bdaded4b7..843fe15d1ef 100644 --- a/apps/database-new/components/Footer.tsx +++ b/apps/database-new/components/Footer.tsx @@ -9,7 +9,7 @@ export const links = [ ] const Footer = () => ( -
+
    {links.map((link, index) => (
  • @@ -18,7 +18,7 @@ const Footer = () => ( ))}
-
+
) export default Footer diff --git a/apps/database-new/components/Header/AvatarDropdown.tsx b/apps/database-new/components/Header/AvatarDropdown.tsx index dca48d4712b..ac412174a02 100644 --- a/apps/database-new/components/Header/AvatarDropdown.tsx +++ b/apps/database-new/components/Header/AvatarDropdown.tsx @@ -1,79 +1,55 @@ 'use client' -import { HelpCircle, LogIn, LogOut } from 'lucide-react' + import { User } from '@supabase/supabase-js' -import Link from 'next/link' +import { HelpCircle, LogIn, LogOut } from 'lucide-react' import Image from 'next/image' -import { - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - IconUser, - SidePanel, -} from 'ui' +import Link from 'next/link' +import { Button, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, IconUser } from 'ui' +import { logout } from './../../app/actions' interface AvatarDropdownProps { - currentUser: User | null - signout: () => void + user: User } -export default function AvatarDropdown({ currentUser, signout }: AvatarDropdownProps) { +export default function AvatarDropdown({ user }: AvatarDropdownProps) { return ( - - - {currentUser ? ( - -
- )} - - + <> +
+

{user.user_metadata.full_name}

+

{user.user_metadata.email}

+
+ + + + {}}> + +

Profile

+
+ + + {}}> + +

FAQs

+
+ +
+ + + + + supabase +

Supabase

+
+
+
+
+ + + +
+ ) } diff --git a/apps/database-new/components/Header/Header.tsx b/apps/database-new/components/Header/Header.tsx index 2a7ebf92683..6d10e899364 100644 --- a/apps/database-new/components/Header/Header.tsx +++ b/apps/database-new/components/Header/Header.tsx @@ -3,6 +3,7 @@ import { createClient } from '@/lib/supabase/server' import { cookies } from 'next/headers' import CurrentThreadName from './CurrentThreadName' import Link from 'next/link' +import UserDropdown from './UserDropdown' const Header = async () => { const cookieStore = cookies() @@ -28,8 +29,10 @@ const Header = async () => { - - +
+ + +
) } diff --git a/apps/database-new/components/Header/HeaderActions.tsx b/apps/database-new/components/Header/HeaderActions.tsx index d08a064f420..7d600e018af 100644 --- a/apps/database-new/components/Header/HeaderActions.tsx +++ b/apps/database-new/components/Header/HeaderActions.tsx @@ -47,11 +47,11 @@ const HeaderActions = ({ user }: HeaderActionsProps) => { New conversation - {currentUser ? ( + {/* {currentUser ? ( ) : ( - )} + )} */} ) } diff --git a/apps/database-new/components/Header/NoUserDropdown.tsx b/apps/database-new/components/Header/NoUserDropdown.tsx index 086eb0810bd..b7e5c3b6729 100644 --- a/apps/database-new/components/Header/NoUserDropdown.tsx +++ b/apps/database-new/components/Header/NoUserDropdown.tsx @@ -1,48 +1,31 @@ import { LogIn } from 'lucide-react' import Image from 'next/image' import Link from 'next/link' -import { - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - IconUser, - SidePanel, -} from 'ui' +import { DropdownMenuItem, DropdownMenuSeparator } from 'ui' const NoUserDropdown = () => { return ( - - - - + copyToClipboard()}> - +

Copy SQL

downloadSQL()}> - +

Download SQL

loadSQLInSupabase()}> diff --git a/apps/database-new/components/Header/UserDropdown.tsx b/apps/database-new/components/Header/UserDropdown.tsx new file mode 100644 index 00000000000..0f8b571a525 --- /dev/null +++ b/apps/database-new/components/Header/UserDropdown.tsx @@ -0,0 +1,48 @@ +import { createClient } from '@/lib/supabase/server' +import { User2 } from 'lucide-react' +import { cookies } from 'next/headers' +import { Suspense } from 'react' +import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from 'ui' +import AvatarDropdown from './AvatarDropdown' +import NoUserDropdown from './NoUserDropdown' + +const EmptyUserButton = () => ( +