From fc8110bcad94ece76bbdebd86b1b99d08978814a Mon Sep 17 00:00:00 2001 From: Nixyi Date: Mon, 16 Dec 2024 22:47:16 -0300 Subject: [PATCH] Early Release This project is working as expected, might have a few bugs here and there but nothing anormal. --- database.types.ts | Bin 16934 -> 0 bytes package-lock.json | 281 +++++++++++++ package.json | 94 ++--- src/app/[id]/page.tsx | 396 +++++++++++++++++++ src/app/api/auth/get_user/route.ts | 24 +- src/app/api/user/create/thread/route.ts | 5 +- src/app/api/user/get/thread/route.ts | 53 +++ src/app/api/user/search/user/route.ts | 6 +- src/app/api/user/send/message/route.ts | 31 ++ src/app/api/user/send/request/route.ts | 1 - src/app/layout.tsx | 1 - src/app/page.tsx | 5 +- src/app/settings/page.tsx | 287 ++++++++++++++ src/components/main/realtime/threads.tsx | 29 +- src/components/main/sidebar/rightsidebar.tsx | 32 +- src/components/main/sidebar/sidebar.tsx | 10 +- src/components/ui/accordion.tsx | 74 ++-- src/components/ui/alert-dialog.tsx | 190 ++++----- src/components/ui/alert.tsx | 59 +++ src/components/ui/dropdown-menu.tsx | 274 ++++++------- src/components/ui/switch.tsx | 29 ++ src/components/ui/tabs.tsx | 55 +++ src/contexts/user.tsx | 172 ++++---- src/hooks/shared-states.tsx | 6 +- src/lib/crypto/helpers/updateKey.ts | 2 +- src/lib/crypto/keys.ts | 245 +++++++++++- src/types/user.d.ts | 31 +- 27 files changed, 1923 insertions(+), 469 deletions(-) delete mode 100644 database.types.ts create mode 100644 src/app/[id]/page.tsx create mode 100644 src/app/api/user/get/thread/route.ts create mode 100644 src/app/settings/page.tsx create mode 100644 src/components/ui/alert.tsx create mode 100644 src/components/ui/switch.tsx create mode 100644 src/components/ui/tabs.tsx diff --git a/database.types.ts b/database.types.ts deleted file mode 100644 index 8d39734ae00b34b37a95e760869ce5708b255e35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16934 zcmeHO+iu%N5Z&hj{RhJjMGE8#iqt`ora)St4$`DAf*|-Jr$T*6B|C|c=GWVv!_|0a z_ChX6Syc-|kR_4a*_kslXNJ4<-+#^BNB7QMxuv^ydwAdC*Bsw|bSoUa#J6wVm+lLE z?zunlUg4UhTj0!FbNtfXxC?iTu}gFO7*ej>IgTG;jF!X^=XmL3pD@G3{f?_|-8a!} z)8!{RXs%%xQ59$GrlA9||&gp0AU&q-)4K zg=|_9BPdAA82MaaM9sAUSsBUC7w`emVgyl&MMYnk)A4E5%#=bc>#ol0`38}F+Tb$Z zX+2*7%kxIhMU#OmM9BiW`>2vQ*0`qj+DI$KBW3JLY*82H!bxN60^cqXPc!@`3o)ws zwc^+~myR9k!!Cv{Aycl5^uZaJj#;!ocW~)`!na%ZGwJ}joV>Rm z^$#mBo5fmCnn9NDq4w{37tRpB$gSt#wF}HA)n&gRbAR1G#WLjg0^<%LXO2GTYWk zXezJGR{CRG-n4y6+!1fe!>rqF7MHv`O?;kKWQ*-PL_7On-XXAO<2}zcVwJI*kCU4I z%g)iK3z2M@A#TJwus^b8XA#X@rmSc`C1sn`!B`s*z~68@)wM^$8k}t1!8h z8A{Bp_Juh2-?$dYHMozcc3d6v-Kt&p>+ZBEORulZ)GDB?;nhiD_Wgls$8|WVz1j}0 zpwvlP^{Se*K>TqDETnJObC>vgo7Fkz>$;jn3sZ0Vv77R@mp0DhsWrRo^Hkehl;rz` z`X1-O>w2%jGgtTJ+=Ghn4y(@_LRvhSHtN1ZxdG_xGB6 z{P(ze2_9vQ;61f0p{{hU%`+D9PaXq&*kB-tA>H&9}-h)fI{%gC>a=rTwquv&0 z5TECJT&}2$=VJQi8giC*mbTRZixls6-5#)}8ZT`zUogiTz}mkMdUS&9eFtgXr0%d3Li+WX@a_q?6uIqh9j z?Yx{NAAKGL9V@xr9**bBuATU?Qa4NEK4W)vn)o+Gf*rT@5VJA2wlOvjEr$1zd$u#t z97PmU+qtGnOHYtb^})@e z_#E1CSE|IhW4lrA16?3yt}w%*=`CNcpL@EG8NF@(FC#8IWA##H#4k;~nM1E5c!$p& z#ZMzm+;gn4@ae0grjvfMULm={&RKdjd%As-7yIsDUHhp;=@}^f6w{NtPC;uLu8`fy zL+Esfxk7ip*U0xzZSdJ9ooRe-%kNHl%^#sF4a}f%n81QC8&~DHa`P45Z6m_H46pw> znVZj;trXgfd96>3J9~t%`xo>P>tkE3cIoxqXmM?~zm6K?r;$yMGSQ?ltkII*(6|xZ zDf-F0kF+-A8@^&>C&rKCbZGNBJsj>*eRhT4-nR$1hFRAamDKxVd`G|i`@Y(ypC&59 zC8L<-Q$1fGD%qp)d4TOy(mo#6 z-epA|!dn0Js(7C~7iW~l+eX{2*Q0HsG`t$vT=f(qc9b(mfxG7Q$eC#@;6n*P~9&^K5L<&vP!1=FbqB8aCEDWe;q;iC7*|G2%GZ z`LIo%(R$c?e_&((A56Tv8_vc$+hR6;)4|5}y!DRw?SYL231LQl)}zj=^K5L<&vUNK znfiuWmDo6}>gD-Y&ezvdjX}Js)K(#7rnI(>?}SCRSdrezYR%%SYkG+7@@}_=<#iz$ zo8oLEr=D{~qM&=!Hmd18jFL#emeVIzM zJ6(DmnX#>7GtM_b+14w@V%sPieM4+0=WSV9>c62(SDr(>R)_lVeS~iA8=~WIR41-` jzQ7?dV|I_N!Ba([]); + const [inputMessage, setInputMessage] = useState(''); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [showKeyDialog, setShowKeyDialog] = useState(false); + const [showUserDialog, setShowUserDialog] = useState(false); + const [isEncrypted, setIsEncrypted] = useState(true); + + const [realtimeSubscribed, setRealtimeSubscribed] = useState() + + const [isLoaded, setIsLoaded] = useState(false); + + const [user, setUser] = useState(null); + const pathName = usePathname(); + const threadId = pathName.replace("/", ""); + + const { + user: currentUser, + getUser + } = useUser() + + const {threads} = useSharedState(); + + useEffect(() => { + const channel = supabase + .channel(`messages:${threadId}`) + .on( + 'postgres_changes', + { + event: '*', + schema: 'public', + table: 'messages', + }, + async (payload) => { + if (payload.eventType === "INSERT") { + const messageData = payload.new as SiPher.RealtimeMessageData; + const isSender = messageData.sender_uuid === currentUser.uuid; + + const decryptedMsg = await CryptoManager.decryptMessage(messageData.sender_content) + console.log(`Hello there`) + setMessages((prevState) => { + return [ + ...prevState, + { + id: messageData.id, + content: decryptedMsg, + sender_uuid: messageData.sender_uuid, + created_at: messageData.created_at, + isSender + } + ] + }) + } + } + ) + .subscribe((status) => { + setRealtimeSubscribed(status) + console.log('Realtime subscription status:', status) + }) + + return () => { + supabase.removeChannel(channel) + } + }, [threadId]) + + useEffect(() => { + const getUserDataAndChat = async () => { + const {thread: getThread} = await (await fetch(`/api/user/get/thread?threadId=${threadId}`)).json() as { + thread: SiPher.Thread + }; + + const otherUser = getThread.participant_suuids.filter((ids) => ids !== currentUser.suuid); + const user = await getUser(`Being called from chat page (${threadId}`, otherUser[0], "suuid", true) + + if (!(user.user[0].suuid && user.user[0].username)) { + toast({ + title: "Error", + description: "Could not verify the existence of this user", + variant: "destructive", + duration: 5000 + }); + } + + setUser(user.user[0]) + + const decryptedMsg = await CryptoManager.decryptThreadMessages(getThread["messages"], currentUser.uuid) + setMessages(decryptedMsg) + } + + if (threads.length > 0) { + setIsLoaded(true) + getUserDataAndChat() + } + + return () => { + setUser(null) + setMessages([]) + setIsLoaded(false) + } + }, [setUser, setMessages, setIsLoaded, threads]) + + if (!isLoaded || !user || realtimeSubscribed !== "SUBSCRIBED") { + return ( + <> + a + + ) + } + + // Mock functions - replace with actual implementations + const checkUserValidity = async () => { + // Implementation for checking user validity + setShowUserDialog(true); + }; + + const checkCurrentKey = async () => { + // Implementation for checking current key + setShowKeyDialog(true); + }; + + const deleteUser = async () => { + // Implementation for deleting user + setShowDeleteDialog(true); + }; + + const sendMessage = async (content: string) => { + if (!content.trim()) return; + setInputMessage(''); + + await CryptoManager.prepareAndSendMessage( + content, + currentUser.public_key, + user.public_key, + threadId + ) + + }; + + return ( +
+ {/* Chat Header */} +
+
+ + + { + user.username.charAt(0).toLocaleUpperCase() + } + + +
+

+ { + user.username.charAt(0).toLocaleUpperCase() + user.username.slice(1) + } +

+
+
+ +
+ + + + + + + {isEncrypted ? 'Encrypted Chat' : 'Encryption Issue'} + + + + + + + + + + Chat Options + + + + + Check User + + + + + Check Current Key + + + + + + + Message History + + + + + Archive Chat + + + + + Export Chat + + + + + + + Delete User + + + +
+
+ + {/* Chat Messages */} + +
+ + {messages.map((message) => ( + +
+

{message.content}

+
+ + {new Date(message.created_at).toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit' + })} + +
+
+
+ ))} +
+
+
+ + {/* Input Area */} +
+
+ setInputMessage(e.target.value)} + placeholder="Type a message..." + onKeyDown={(e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + sendMessage(inputMessage); + } + }} + /> + +
+
+ + {/* Dialogs */} + + + + Delete User + + Are you sure you want to delete this user? This will remove them from your contacts + and delete all messages. This action cannot be undone. + + + + Cancel + Delete + + + + + + + + Encryption Status + +
+ + Local private key is valid and active +
+
+ + Remote public key is verified +
+
+ + End-to-end encryption is active +
+
+
+ + Close + +
+
+ + + + + User Verification + +
+ + User is verified and active +
+
+ + Last active: 2 minutes ago +
+
+ + Secure connection established +
+
+
+ + Close + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/api/auth/get_user/route.ts b/src/app/api/auth/get_user/route.ts index 7633791..e4e5d7a 100644 --- a/src/app/api/auth/get_user/route.ts +++ b/src/app/api/auth/get_user/route.ts @@ -4,17 +4,32 @@ import getUserByUUID from "@/lib/api/helpers/getUserByUUID"; // Helper function to get user data by UUID - export async function GET(request: Request) { try { const supabase = await createClient(); const {searchParams} = new URL(request.url); const uuid = searchParams.get('uuid'); + const suuid = searchParams.get('suuid'); + const getDetails = searchParams.get("detailed") if (uuid) { // Get specific user by UUID const userData = await getUserByUUID(supabase, uuid); return NextResponse.json({user: userData}); + } else if (suuid) { + const {data, error} = await supabase.rpc('search_users', { + search_term: suuid + }); + + if (error) { + return NextResponse.json({error: error}, {status: 500}); + } + + if (getDetails) { + return NextResponse.json({user: data}) + } + + return NextResponse.json({exists: !!(data[0].suuid && data[0].username)}, {status: 200}); } else { // Get current authenticated user const {data: {user}, error: authError} = await supabase.auth.getUser(); @@ -28,6 +43,13 @@ export async function GET(request: Request) { return NextResponse.json({user: userData}); } } catch (error) { + if (typeof error === "object") { + return NextResponse.json( + {error: `Failed to fetch user: ${JSON.stringify(error)}`}, + {status: 500} + ); + } + return NextResponse.json( {error: `Failed to fetch user: ${error}`}, {status: 500} diff --git a/src/app/api/user/create/thread/route.ts b/src/app/api/user/create/thread/route.ts index 14f5acd..af5d214 100644 --- a/src/app/api/user/create/thread/route.ts +++ b/src/app/api/user/create/thread/route.ts @@ -1,7 +1,6 @@ import {NextResponse} from "next/server"; import {createClient} from "@/lib/supabase/server"; import getUserByUUID from "@/lib/api/helpers/getUserByUUID"; -import updateUserRequests from "@/lib/api/helpers/updateUserRequests"; export async function POST(req: Request) { const {participant} = await req.json(); @@ -28,7 +27,7 @@ export async function POST(req: Request) { /** First we need to check if the requested participant is in the user's request array */ const dbUser = await getUserByUUID(supabase, user.id) - + if (!dbUser) { return NextResponse.json( {error: "User not found"}, @@ -49,7 +48,7 @@ export async function POST(req: Request) { const {error} = await supabase.rpc('create_private_thread', { participant_suuid: participant }); - + if (error) { return NextResponse.json({error}, {status: 500}); } diff --git a/src/app/api/user/get/thread/route.ts b/src/app/api/user/get/thread/route.ts new file mode 100644 index 0000000..4f7daa0 --- /dev/null +++ b/src/app/api/user/get/thread/route.ts @@ -0,0 +1,53 @@ +import {createClient} from "@/lib/supabase/server"; +import {NextResponse} from "next/server"; + +export async function GET(request: Request) { + try { + const {searchParams} = new URL(request.url); + const threadId = searchParams.get('threadId'); + + if (!threadId) { + return NextResponse.json({ + error: "No thread id provided" + }, {status: 400}) + } + + const supabase = await createClient(); + + const {data: {user}, error: userError} = await supabase.auth.getUser() + + if (userError) { + NextResponse.json( + {error: userError}, + {status: userError?.status} + ) + } else if (!user) { + NextResponse.json( + {error: "User not found"}, + {status: 401} + ) + } + + const {data, error} = await supabase.rpc( + "get_thread", + { + thread_uuid: threadId, + user_id: user!.id + } + ) + + if (error) { + return NextResponse.json({error}, {status: 400}) + } + + return NextResponse.json({thread: data[0]}, {status: 200}); + + } catch (e: any) { + console.log(e) + if (typeof e === "object") { + return NextResponse.json({error: JSON.stringify(e)}, {status: 500}) + } + + return NextResponse.json({error: e}, {status: 500}) + } +} \ No newline at end of file diff --git a/src/app/api/user/search/user/route.ts b/src/app/api/user/search/user/route.ts index 4c9e1b6..23f5e7e 100644 --- a/src/app/api/user/search/user/route.ts +++ b/src/app/api/user/search/user/route.ts @@ -6,7 +6,7 @@ export async function GET(request: Request) { const supabase = await createClient(); const {searchParams} = new URL(request.url); const uuid = searchParams.get('uuid'); - console.log('Searching for UUID:', uuid); + const getDetails = searchParams.get("detailed") if (!uuid) { return NextResponse.json({error: "Missing UUID from request"}, {status: 400}) @@ -36,6 +36,10 @@ export async function GET(request: Request) { return NextResponse.json({error: error}, {status: 500}); } + if (getDetails) { + return NextResponse.json({user: data}) + } + return NextResponse.json({exists: !!(data[0].suuid && data[0].username)}, {status: 200}); } catch (error) { diff --git a/src/app/api/user/send/message/route.ts b/src/app/api/user/send/message/route.ts index e69de29..2d16b2e 100644 --- a/src/app/api/user/send/message/route.ts +++ b/src/app/api/user/send/message/route.ts @@ -0,0 +1,31 @@ +import {createClient} from "@/lib/supabase/server"; +import {NextResponse} from "next/server"; + +export async function POST(request: Request) { + try { + const {threadId, senderContent, recipientContent} = await request.json(); + const supabase = await createClient(); + + const {data, error} = await supabase.rpc('send_message', { + thread_uuid: threadId, + sender_content: senderContent, + recipient_content: recipientContent + }); + + if (error) throw error; + + return NextResponse.json({messageId: data}); + } catch (error: any) { + if (typeof error === "object") { + return NextResponse.json( + {error}, + {status: 500} + ); + } + + return NextResponse.json( + {error: 'Failed to send message', details: error.message}, + {status: 500} + ); + } +} \ No newline at end of file diff --git a/src/app/api/user/send/request/route.ts b/src/app/api/user/send/request/route.ts index 525997e..f291698 100644 --- a/src/app/api/user/send/request/route.ts +++ b/src/app/api/user/send/request/route.ts @@ -1,6 +1,5 @@ import {createClient} from "@/lib/supabase/server"; import {NextResponse} from "next/server"; -import {SupabaseClient} from "@supabase/supabase-js"; import getUserByUUID from "@/lib/api/helpers/getUserByUUID"; import updateUserRequests from "@/lib/api/helpers/updateUserRequests"; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 82072f7..fbeac88 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -9,7 +9,6 @@ import {SharedStateProvider} from "@/hooks/shared-states"; import ThemeProvider from "@/components/ui/theme-provider"; import {headers} from "next/headers"; import {Toaster} from "@/components/ui/toaster"; -import {RealtimeRequests} from "@/components/main/realtime/request"; const publicSans = Public_Sans({ subsets: ['latin'], diff --git a/src/app/page.tsx b/src/app/page.tsx index 2457b1a..54f9e6e 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -26,6 +26,7 @@ export default function SiPher() { /** CryptoManager Alert */ const [privateKeyPresent, setPrivateKeyPresent] = useState(true); + const [backupPanel, setBackupPanel] = useState(false); /** Consent Form states */ const [showConsentForm, setShowConsentForm] = useState(false); @@ -164,8 +165,8 @@ export default function SiPher() { Private Key Missing - This app could not retrieve your private key, which means it's either lost, never stored or corrupted. Want to try again or insert it from a backup? - You can also regenerate it if you do not have it backed up, but this would mean that you'll loose access to all old messages. + This app could not retrieve your private key, which means it's either lost, never stored or corrupted. Want to try again or insert it from a backup? + You can also regenerate it if you do not have it backed up, but this would mean that you'll loose access to all old messages. diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx new file mode 100644 index 0000000..9379e13 --- /dev/null +++ b/src/app/settings/page.tsx @@ -0,0 +1,287 @@ +"use client" +import {motion} from "framer-motion"; +import {useTheme} from "next-themes"; +import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card"; +import {Button} from "@/components/ui/button"; +import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs"; +import {Input} from "@/components/ui/input"; +import {Label} from "@/components/ui/label"; +import {Switch} from "@/components/ui/switch"; +import {Separator} from "@/components/ui/separator"; +import {useUser} from "@/contexts/user"; +import {useState} from "react"; +import {AlertTriangle, Copy, Download, Eye, EyeOff, Key, Lock, Save, User} from "lucide-react"; +import {CryptoManager} from "@/lib/crypto/keys"; +import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert"; + +export default function SettingsPage() { + const {theme, setTheme} = useTheme(); + const {user} = useUser(); + const [loading, setLoading] = useState(false); + const [privateKeyVisible, setPrivateKeyVisible] = useState(false); + const [privateKeyData, setPrivateKeyData] = useState<{ text: string; file: File } | null>(null); + const [backupError, setBackupError] = useState(""); + + const containerVariants = { + hidden: {opacity: 0, y: 20}, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.6, + staggerChildren: 0.1 + } + } + }; + + const itemVariants = { + hidden: {opacity: 0, y: 20}, + visible: {opacity: 1, y: 0} + }; + + return ( + +
+
+

Settings

+

+ Manage your account settings and preferences +

+
+
+ + + + + + Profile + + + + Privacy + + + + + + + + Profile Information + + Update your profile information and settings + + + +
+ + +
+
+ +
+ + +
+
+
+
+
+ + + + + Privacy Settings + + Manage your privacy and security preferences + + + +
+
+ +

+ End-to-end encryption is always enabled +

+
+ +
+ +
+
+
+ +

+ View and download your private key for backup +

+
+
+ + +
+
+ + {backupError && ( + + + Error + {backupError} + + )} + + {privateKeyData && ( + + +
+ Private Key +
+ + +
+
+
+ +
+
+                            {privateKeyData.text}
+                          
+
+
+
+ )} +
+ +
+
+ +

+ Receive message requests from other users +

+
+ +
+
+
+ + + + Private Key Management + + Your private key is stored securely in your browser. + Make sure to back it up to avoid losing access to your messages. + + +
+
+
+ + + + +
+ ); +} \ No newline at end of file diff --git a/src/components/main/realtime/threads.tsx b/src/components/main/realtime/threads.tsx index 8665dcf..5834b5f 100644 --- a/src/components/main/realtime/threads.tsx +++ b/src/components/main/realtime/threads.tsx @@ -1,15 +1,15 @@ // hooks/useRealtime.ts -import {useEffect} from 'react' +import {Dispatch, SetStateAction, useEffect} from 'react' import {createBrowserClient} from '@/lib/supabase/browser' import {useUser} from '@/contexts/user' import {useToast} from '@/hooks/use-toast' interface UseRealtimeProps { - setThreads: React.Dispatch>; - threads: SiPher.Messages[] + setThreads: Dispatch>; + threads: SiPher.Thread[] } -export function useRealtime({setThreads}: UseRealtimeProps) { +export function useRealtime({setThreads, threads}: UseRealtimeProps) { const supabase = createBrowserClient(); const {user, updateUser} = useUser(); const {toast} = useToast(); @@ -38,6 +38,7 @@ export function useRealtime({setThreads}: UseRealtimeProps) { table: 'users', filter: `uuid=eq.${user.uuid}`, }, async (payload) => { + console.log(payload) if (payload.eventType === "UPDATE") { // This will also handle updates for the threads, but only for the user that accepted the request. // Why? Because the function that creates the thread will also update the current user request field and remove @@ -48,6 +49,13 @@ export function useRealtime({setThreads}: UseRealtimeProps) { requests: payload.new.requests }) } + } else if (payload.eventType === "DELETE") { + console.log(`Payload from delete: \n${payload}`) + updateUser({ + ...user, + //@ts-expect-error + requests: payload.new + }) } }).subscribe() @@ -56,11 +64,18 @@ export function useRealtime({setThreads}: UseRealtimeProps) { .on("postgres_changes", { event: "*", schema: 'public', - // Using on this one because it's easier table: "thread_participants", - filter: `user_uuid=${user.uuid}`, + filter: `user_uuid=eq.${user.uuid}` }, async (payload) => { - console.log(payload) + if (payload.new !== payload.old) { + await fetchAndUpdateThreads(); + } }).subscribe() + + return () => { + threadUpdate.unsubscribe() + userUpdate.unsubscribe() + } + }, [user?.uuid]); } \ No newline at end of file diff --git a/src/components/main/sidebar/rightsidebar.tsx b/src/components/main/sidebar/rightsidebar.tsx index dee01d3..e4da858 100644 --- a/src/components/main/sidebar/rightsidebar.tsx +++ b/src/components/main/sidebar/rightsidebar.tsx @@ -10,6 +10,8 @@ import {GearIcon} from "@radix-ui/react-icons"; import Link from "next/link"; import {useRealtime} from "@/components/main/realtime/threads"; import {useUser} from "@/contexts/user"; +import {usePathname} from "next/navigation"; +import {useSharedState} from "@/hooks/shared-states"; interface RightSidebarContentProps { isDarkMode: boolean; @@ -20,30 +22,22 @@ export default function RightSidebarContent( isDarkMode, }: RightSidebarContentProps) { - const [selectedThreads, setSelectedThreads] = useState(""); - const [threadMenu, setThreadMenu] = useState([]); - const [pendingRequest, setPendingRequest] = useState(0); - - - const [threads, setThreads] = useState([]); - useRealtime( - {setThreads} - ); const [copied, setCopied] = useState(false); + const {threads, setThreads} = useSharedState(); + useRealtime({setThreads, threads}); + const {user} = useUser(); const {username, suuid, requests = []} = user; + const pathname = usePathname(); - - // No need for separate requests state since it's in user object const pendingRequests = requests?.length ?? 0; - // Move fetch to separate function const fetchThreads = useCallback(async () => { try { const req = await fetch("/api/user/get/threads") if (req.ok) { - const {threads} = await req.json() as { threads: SiPher.Messages[] | [] } + const {threads} = await req.json() as { threads: SiPher.Thread[] | [] } setThreads(threads) } else { setThreads([]) @@ -65,7 +59,6 @@ export default function RightSidebarContent( body: JSON.stringify({participant: request}), }); if (response.ok) { - // Optionally refresh threads after successful creation fetchThreads(); } } catch (error) { @@ -73,7 +66,6 @@ export default function RightSidebarContent( } } - return ( <>
@@ -149,17 +141,19 @@ export default function RightSidebarContent( {threads && threads.length > 0 ? ( threads.map((thread, index) => { + // Gets the user's username instead of the SUUID to use as a recognizable user. const otherUser = thread.participants.filter((user) => user !== username)[0]; - console.log(thread) return (
  • -
    +
    +
  • ) @@ -175,7 +169,7 @@ export default function RightSidebarContent(