diff --git a/src/app/[locale]/(auth)/login/mfa/page.client.tsx b/src/app/[locale]/(auth)/login/mfa/page.client.tsx index 6618af31..8b3477a7 100644 --- a/src/app/[locale]/(auth)/login/mfa/page.client.tsx +++ b/src/app/[locale]/(auth)/login/mfa/page.client.tsx @@ -13,11 +13,27 @@ import { } from '@/utils/actions/account/account' import { useEffect, useState } from 'react' import { useRouter } from '@/navigation' +import { mfaChallengeNeeded } from '@/utils/server-api/account/user' export default function MfaPageClient() { const { toast } = useToast() const router = useRouter() const [challengeId, setChallengeId] = useState('') + const [needsMfa, setNeedsMfa] = useState(false) + + useEffect(() => { + const checkMfa = async () => { + const mfaNeeded = await mfaChallengeNeeded() + if (mfaNeeded.type === 'general_unauthorized_scope') { + window.location.href = '/login' + } else if (mfaNeeded.$id) { + window.location.href = '/account' + } else { + setNeedsMfa(true) + } + } + checkMfa().then() + }) const createMfaCode = async () => { try { @@ -36,7 +52,7 @@ export default function MfaPageClient() { try { await updateMfaChallenge(challengeId, otp) - router.push('/account') + router.replace('/account') } catch (error) { if (error.type === 'user_invalid_token') { toast({ @@ -45,6 +61,7 @@ export default function MfaPageClient() { "Incorrect code. Please try again. If you're having trouble, please contact support.", variant: 'destructive', }) + return } else { toast({ title: 'Error', @@ -53,37 +70,73 @@ export default function MfaPageClient() { variant: 'destructive', }) Sentry.captureException(error) + return } } } - return ( - { - handleMfaVerify(result).catch((error) => { - console.log(error) - toast({ - title: 'Error', - description: - "You encountered an error. But don't worry, we're on it.", - variant: 'destructive', - }) - Sentry.captureException(error) - }) - }} - > - - - - - - - - - - - - - ) + if (!needsMfa) { + return ( +
+
+
+
+
+

+ Loading... +

+
+
+
+
+
+ ) + } else { + return ( +
+ {/* Add justify-center and items-center here */} +
+
+
+
+

+ Please fill in your 2FA code +

+
+
+ { + handleMfaVerify(result).catch((error) => { + console.log(error) + toast({ + title: 'Error', + description: + "You encountered an error. But don't worry, we're on it.", + variant: 'destructive', + }) + Sentry.captureException(error) + return + }) + }} + > + + + + + + + + + + + + +
+
+
+
+
+ ) + } } diff --git a/src/app/[locale]/(auth)/login/mfa/page.tsx b/src/app/[locale]/(auth)/login/mfa/page.tsx index 21861ab0..833b7e5d 100644 --- a/src/app/[locale]/(auth)/login/mfa/page.tsx +++ b/src/app/[locale]/(auth)/login/mfa/page.tsx @@ -1,7 +1,6 @@ import { Button } from '@/components/ui/button' -import { Link, redirect } from '@/navigation' +import { Link } from '@/navigation' import MfaPageClient from '@/app/[locale]/(auth)/login/mfa/page.client' -import { mfaChallengeNeeded } from '@/utils/server-api/account/user' export const metadata = { title: 'Login', @@ -12,41 +11,12 @@ export const metadata = { export const runtime = 'edge' export default async function Page() { - const mfaNeeded = await mfaChallengeNeeded() - if (mfaNeeded.type === 'general_unauthorized_scope') { - return redirect('/login') - } - if (mfaNeeded.$id) { - return redirect('/account') - } - return ( <> -
-
-
-
-
-
- {/* Add justify-center and items-center here */} -
-
-
-
-

- Please fill in your 2FA code -

-
-
- -
-
-
-
-
+ ) } diff --git a/src/app/[locale]/(auth)/login/page.client.tsx b/src/app/[locale]/(auth)/login/page.client.tsx index 4f77d788..d59bf322 100644 --- a/src/app/[locale]/(auth)/login/page.client.tsx +++ b/src/app/[locale]/(auth)/login/page.client.tsx @@ -1,5 +1,5 @@ 'use client' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { SiGithub, SiDiscord, @@ -57,27 +57,31 @@ export default function Login() { description: 'Invalid E-Mail or password provided.', variant: 'destructive', }) + return } else if (response.code === 401) { toast({ title: 'Error', description: 'E-Mail or Password incorrect.', variant: 'destructive', }) + return } else if (response.code === 409) { toast({ title: 'Error', description: 'E-Mail already in use.', variant: 'destructive', }) + return } else if (response.code === 429) { toast({ title: 'Error', description: 'Too many requests, please try again later.', variant: 'destructive', }) + return } - router.push('/account') + window.location.href = '/account' } else { const signIn = await fetch('/api/user/signin', { method: 'POST', @@ -108,17 +112,12 @@ export default function Login() { client.setSession(dataResponse.secret) - router.push('/login/mfa') + router.replace('/login/mfa') } } return ( <> -
-
-
-
-
diff --git a/src/app/[locale]/(auth)/login/page.tsx b/src/app/[locale]/(auth)/login/page.tsx index 1dcf89d2..44978fae 100644 --- a/src/app/[locale]/(auth)/login/page.tsx +++ b/src/app/[locale]/(auth)/login/page.tsx @@ -15,7 +15,9 @@ export default async function LoginPage() { const accountData = await account.get().catch((e) => { if (e.type === 'user_more_factors_required') redirect('/login/mfa') }) - if (accountData) redirect('/account') + if (accountData) { + redirect('/account') + } return ( <> diff --git a/src/app/[locale]/(user)/notifications/page.tsx b/src/app/[locale]/(user)/notifications/page.tsx new file mode 100644 index 00000000..cd27925f --- /dev/null +++ b/src/app/[locale]/(user)/notifications/page.tsx @@ -0,0 +1,63 @@ +import PageLayout from '@/components/pageLayout' +import { ChevronRight, MegaphoneIcon } from 'lucide-react' +import { getUserNotifications } from '@/utils/server-api/notifications/getUserNotifications' +import { getUserDataSingle } from '@/utils/server-api/user/getUserData' +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { getAvatarImageUrlPreview } from '@/components/getStorageItem' + +export default async function Page() { + const notifications = await getUserNotifications() + return ( + +
    + {notifications && + notifications.documents.map(async (notification) => { + if (notification.type === 'newFollower') { + const user = await getUserDataSingle(notification.userId) + + return ( +
  • +
    + + + + {user.displayName.charAt(0).toUpperCase()} + + +
    +

    + + New Follower! +

    +

    + {user.displayName} followed you! 🎉 +

    +
    +
    +
    +
    +
  • + ) + } + })} +
+
+ ) +} diff --git a/src/app/[locale]/(user)/user/[profileUrl]/page.tsx b/src/app/[locale]/(user)/user/[profileUrl]/page.tsx index d2ed4d72..40949480 100644 --- a/src/app/[locale]/(user)/user/[profileUrl]/page.tsx +++ b/src/app/[locale]/(user)/user/[profileUrl]/page.tsx @@ -181,7 +181,9 @@ export default async function UserProfile({ params: { profileUrl } }) { className={'col-span-3 border-none md:col-span-1 lg:col-span-2'} > -
+
{userData.displayName} diff --git a/src/components/header/data.tsx b/src/components/header/data.tsx index 59fe0207..c95e1a11 100644 --- a/src/components/header/data.tsx +++ b/src/components/header/data.tsx @@ -1,3 +1,4 @@ +'use client' import { CalendarIcon, CircleUserIcon, diff --git a/src/components/header/header-server.tsx b/src/components/header/header-server.tsx index 56d0d38b..e8481987 100644 --- a/src/components/header/header-server.tsx +++ b/src/components/header/header-server.tsx @@ -3,6 +3,8 @@ import SidebarResizable from '@/components/header/header-resizable' import MobileNav from '@/components/header/mobile-nav' import { createSessionServerClient } from '@/app/appwrite-session' +export const dynamic = 'force-dynamic' + export default async function HeaderServer({ children, locale, diff --git a/src/utils/server-api/account/user.ts b/src/utils/server-api/account/user.ts index 28d2930c..ec6da6a5 100644 --- a/src/utils/server-api/account/user.ts +++ b/src/utils/server-api/account/user.ts @@ -1,3 +1,4 @@ +'use server' import { createSessionServerClient } from '@/app/appwrite-session' import { Models } from 'node-appwrite' import { Account } from '@/utils/types/models' diff --git a/src/utils/server-api/notifications/getUserNotifications.ts b/src/utils/server-api/notifications/getUserNotifications.ts new file mode 100644 index 00000000..03f0b809 --- /dev/null +++ b/src/utils/server-api/notifications/getUserNotifications.ts @@ -0,0 +1,7 @@ +import { createSessionServerClient } from '@/app/appwrite-session' +import { Notifications } from '@/utils/types/models' + +export async function getUserNotifications(): Promise { + const { databases } = await createSessionServerClient() + return await databases.listDocuments('hp_db', 'user-notifications') +} diff --git a/src/utils/server-api/user/getUserData.ts b/src/utils/server-api/user/getUserData.ts index be0f1798..3cf688ad 100644 --- a/src/utils/server-api/user/getUserData.ts +++ b/src/utils/server-api/user/getUserData.ts @@ -1,6 +1,8 @@ import { createSessionServerClient } from '@/app/appwrite-session' import { UserData } from '@/utils/types/models' import { Query } from 'node-appwrite' +import { getUser } from '@/utils/server-api/account/user' +import { unstable_noStore } from 'next/cache' /** * This function is used to get the user data. @@ -10,6 +12,7 @@ import { Query } from 'node-appwrite' export async function getUserDataFromProfileUrl( profileUrl: string ): Promise { + unstable_noStore() const { databases } = await createSessionServerClient() return await databases .listDocuments('hp_db', 'userdata', [Query.equal('profileUrl', profileUrl)]) @@ -18,16 +21,32 @@ export async function getUserDataFromProfileUrl( }) } +/** + * This function is used to get the user data from the user id. + * @example + * const userData = await getUserDataSingle('userId') + */ +export async function getUserDataSingle( + userId: string +): Promise { + unstable_noStore() + const { databases } = await createSessionServerClient() + return await databases + .getDocument('hp_db', 'userdata', `${userId}`) + .catch((error) => { + return error + }) +} + /** * This function is used to get the user data. * @example * const userData = await getUserData() */ export async function getUserData(): Promise { - const { account, databases } = await createSessionServerClient() - const accountData = await account.get().catch((error) => { - return error - }) + unstable_noStore() + const { databases } = await createSessionServerClient() + const accountData = await getUser() return await databases .getDocument('hp_db', 'userdata', `${accountData.$id}`) .catch((error) => { @@ -41,6 +60,7 @@ export async function getUserData(): Promise { * const userData = await getUserDataList() */ export async function getUserDataList(): Promise { + unstable_noStore() const { databases } = await createSessionServerClient() return await databases.listDocuments('hp_db', 'userdata').catch((error) => { return error diff --git a/src/utils/types/models.ts b/src/utils/types/models.ts index 8a9f39bc..37ea764c 100644 --- a/src/utils/types/models.ts +++ b/src/utils/types/models.ts @@ -358,3 +358,29 @@ export namespace Community { communityId: string } } + +export namespace Notifications { + export interface NotificationsType { + total: number + documents: NotificationsDocumentsType[] + } + + export interface NotificationsDocumentsType extends Models.Document { + /** + * The title of the notification. + */ + message: string + /** + * The user ID of the user that received the notification. + */ + userId: string + /** + * The description of the notification. + */ + read: boolean + /** + * The date the notification was created. + */ + type: 'newFollower' + } +}