diff --git a/src/webapp/app/(authenticated)/my-cards/error.tsx b/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/cards/error.tsx similarity index 100% rename from src/webapp/app/(authenticated)/my-cards/error.tsx rename to src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/cards/error.tsx diff --git a/src/webapp/app/(authenticated)/collections/[collectionId]/layout.tsx b/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/cards/layout.tsx similarity index 86% rename from src/webapp/app/(authenticated)/collections/[collectionId]/layout.tsx rename to src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/cards/layout.tsx index bb204ea7..89af4c97 100644 --- a/src/webapp/app/(authenticated)/collections/[collectionId]/layout.tsx +++ b/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/cards/layout.tsx @@ -3,8 +3,8 @@ import type { Metadata } from 'next'; import { Fragment } from 'react'; export const metadata: Metadata = { - title: 'Collection', - description: 'Collection', + title: 'My Cards', + description: 'All my cards', }; interface Props { diff --git a/src/webapp/app/(authenticated)/my-cards/loading.tsx b/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/cards/loading.tsx similarity index 100% rename from src/webapp/app/(authenticated)/my-cards/loading.tsx rename to src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/cards/loading.tsx diff --git a/src/webapp/app/(authenticated)/my-cards/page.tsx b/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/cards/page.tsx similarity index 100% rename from src/webapp/app/(authenticated)/my-cards/page.tsx rename to src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/cards/page.tsx diff --git a/src/webapp/app/(authenticated)/collections/error.tsx b/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/collections/error.tsx similarity index 100% rename from src/webapp/app/(authenticated)/collections/error.tsx rename to src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/collections/error.tsx diff --git a/src/webapp/app/(authenticated)/collections/layout.tsx b/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/collections/layout.tsx similarity index 78% rename from src/webapp/app/(authenticated)/collections/layout.tsx rename to src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/collections/layout.tsx index 98351f2e..467e7667 100644 --- a/src/webapp/app/(authenticated)/collections/layout.tsx +++ b/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/collections/layout.tsx @@ -12,10 +12,5 @@ interface Props { } export default function Layout(props: Props) { - return ( - -
- {props.children} - - ); + return {props.children}; } diff --git a/src/webapp/app/(authenticated)/collections/loading.tsx b/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/collections/loading.tsx similarity index 100% rename from src/webapp/app/(authenticated)/collections/loading.tsx rename to src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/collections/loading.tsx diff --git a/src/webapp/app/(authenticated)/collections/page.tsx b/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/collections/page.tsx similarity index 100% rename from src/webapp/app/(authenticated)/collections/page.tsx rename to src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/collections/page.tsx diff --git a/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/layout.tsx b/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/layout.tsx new file mode 100644 index 00000000..d082cc04 --- /dev/null +++ b/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/layout.tsx @@ -0,0 +1,58 @@ +import { ApiClient } from '@/api-client/ApiClient'; +import Header from '@/components/navigation/header/Header'; +import ProfileHeader from '@/features/profile/components/profileHeader/ProfileHeader'; +import { getAccessTokenInServerComponent } from '@/services/auth'; +import { Button, Container, Stack, Text, Title } from '@mantine/core'; +import type { Metadata } from 'next'; +import Link from 'next/link'; +import { Fragment } from 'react'; + +interface Props { + params: Promise<{ handle: string }>; + children: React.ReactNode; +} + +export async function generateMetadata({ params }: Props): Promise { + const { handle } = await params; + + return { + title: handle, + description: 'Profile', + }; +} + +export default async function Layout(props: Props) { + const { handle } = await props.params; + const accessToken = await getAccessTokenInServerComponent(); + const apiClient = new ApiClient( + process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3000', + () => accessToken, + ); + const data = await apiClient.getMyProfile(); + + // TODO: use profile endpoints to fetch profile information + // for now we'll use getMyProfile + if (!accessToken || data.handle !== handle) { + return ( + +
+ + + Public profiles are coming soon! + + + + + ); + } + + return ( + +
+ + {props.children} + + ); +} diff --git a/src/webapp/app/(authenticated)/profile/[handle]/loading.tsx b/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/loading.tsx similarity index 100% rename from src/webapp/app/(authenticated)/profile/[handle]/loading.tsx rename to src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/loading.tsx diff --git a/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/page.tsx b/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/page.tsx new file mode 100644 index 00000000..7d1e0b71 --- /dev/null +++ b/src/webapp/app/(authenticated)/profile/[handle]/(withHeader)/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return <>; +} diff --git a/src/webapp/app/(authenticated)/collections/[collectionId]/error.tsx b/src/webapp/app/(authenticated)/profile/[handle]/(withoutHeader)/collections/[collectionId]/error.tsx similarity index 100% rename from src/webapp/app/(authenticated)/collections/[collectionId]/error.tsx rename to src/webapp/app/(authenticated)/profile/[handle]/(withoutHeader)/collections/[collectionId]/error.tsx diff --git a/src/webapp/app/(authenticated)/my-cards/layout.tsx b/src/webapp/app/(authenticated)/profile/[handle]/(withoutHeader)/collections/[collectionId]/layout.tsx similarity index 51% rename from src/webapp/app/(authenticated)/my-cards/layout.tsx rename to src/webapp/app/(authenticated)/profile/[handle]/(withoutHeader)/collections/[collectionId]/layout.tsx index aea0cf5a..1e8e7579 100644 --- a/src/webapp/app/(authenticated)/my-cards/layout.tsx +++ b/src/webapp/app/(authenticated)/profile/[handle]/(withoutHeader)/collections/[collectionId]/layout.tsx @@ -1,10 +1,9 @@ -import Header from '@/components/navigation/header/Header'; import type { Metadata } from 'next'; import { Fragment } from 'react'; export const metadata: Metadata = { - title: 'My Cards', - description: 'All my cards', + title: 'Collection', + description: 'Collection', }; interface Props { @@ -12,10 +11,5 @@ interface Props { } export default function Layout(props: Props) { - return ( - -
- {props.children} - - ); + return {props.children}; } diff --git a/src/webapp/app/(authenticated)/collections/[collectionId]/loading.tsx b/src/webapp/app/(authenticated)/profile/[handle]/(withoutHeader)/collections/[collectionId]/loading.tsx similarity index 100% rename from src/webapp/app/(authenticated)/collections/[collectionId]/loading.tsx rename to src/webapp/app/(authenticated)/profile/[handle]/(withoutHeader)/collections/[collectionId]/loading.tsx diff --git a/src/webapp/app/(authenticated)/collections/[collectionId]/page.tsx b/src/webapp/app/(authenticated)/profile/[handle]/(withoutHeader)/collections/[collectionId]/page.tsx similarity index 100% rename from src/webapp/app/(authenticated)/collections/[collectionId]/page.tsx rename to src/webapp/app/(authenticated)/profile/[handle]/(withoutHeader)/collections/[collectionId]/page.tsx diff --git a/src/webapp/app/(authenticated)/profile/[handle]/layout.tsx b/src/webapp/app/(authenticated)/profile/[handle]/layout.tsx deleted file mode 100644 index baff099f..00000000 --- a/src/webapp/app/(authenticated)/profile/[handle]/layout.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import Header from '@/components/navigation/header/Header'; -import type { Metadata } from 'next'; -import { Fragment } from 'react'; - -interface Props { - params: Promise<{ handle: string }>; - children: React.ReactNode; -} - -export async function generateMetadata({ params }: Props): Promise { - const { handle } = await params; - - return { - title: handle, - description: 'Profile', - }; -} - -export default async function Layout(props: Props) { - return ( - -
- {props.children} - - ); -} diff --git a/src/webapp/app/(authenticated)/profile/[handle]/page.tsx b/src/webapp/app/(authenticated)/profile/[handle]/page.tsx deleted file mode 100644 index 4ab89d1f..00000000 --- a/src/webapp/app/(authenticated)/profile/[handle]/page.tsx +++ /dev/null @@ -1,24 +0,0 @@ -'use client'; - -import { useParams } from 'next/navigation'; -import { Stack, Title, Text, Card, Center } from '@mantine/core'; - -export default function ProfilePage() { - const params = useParams(); - const handle = params.handle as string; - - return ( -
- - - Profile Page - Profile for @{handle} - Coming Soon! - - This page will show user profile information and their public - activity. - - - -
- ); -} diff --git a/src/webapp/app/(authenticated)/profile/page.tsx b/src/webapp/app/(authenticated)/profile/page.tsx index 19d39e56..fc84468e 100644 --- a/src/webapp/app/(authenticated)/profile/page.tsx +++ b/src/webapp/app/(authenticated)/profile/page.tsx @@ -1,14 +1,6 @@ -'use client'; - -import Header from '@/components/navigation/header/Header'; -import ProfileContainer from '@/features/profile/containers/profileContainer/ProfileContainer'; -import { Fragment } from 'react'; +import { redirect } from 'next/navigation'; export default function Page() { - return ( - -
- - - ); + // page is left empty, redirect to home if [handle] isn't provided + redirect('/home'); } diff --git a/src/webapp/components/navigation/navbar/Navbar.tsx b/src/webapp/components/navigation/navbar/Navbar.tsx index 9cae1a56..5856817d 100644 --- a/src/webapp/components/navigation/navbar/Navbar.tsx +++ b/src/webapp/components/navigation/navbar/Navbar.tsx @@ -23,9 +23,11 @@ import CollectionsNavListSkeleton from '@/features/collections/components/collec import NavbarToggle from '../NavbarToggle'; import { FiPlus } from 'react-icons/fi'; import AddCardDrawer from '@/features/cards/components/addCardDrawer/AddCardDrawer'; +import useMyProfile from '@/features/profile/lib/queries/useMyProfile'; export default function Navbar() { const [openAddDrawer, setOpenAddDrawer] = useState(false); + const { data: profile } = useMyProfile(); return ( @@ -45,8 +47,8 @@ export default function Navbar() { } /> } /> - My Cards + Cards {allCards.length > 0 ? ( diff --git a/src/webapp/features/cards/containers/myCardsContainer/Skeleton.MyCardsContainer.tsx b/src/webapp/features/cards/containers/myCardsContainer/Skeleton.MyCardsContainer.tsx index fe3e114e..8833b93c 100644 --- a/src/webapp/features/cards/containers/myCardsContainer/Skeleton.MyCardsContainer.tsx +++ b/src/webapp/features/cards/containers/myCardsContainer/Skeleton.MyCardsContainer.tsx @@ -5,7 +5,7 @@ export default function MyCardsContainerSkeleton() { return ( - My Cards + Cards {Array.from({ length: 8 }).map((_, i) => ( diff --git a/src/webapp/features/collections/components/addToCollectionModal/CardToBeAddedPreview.tsx b/src/webapp/features/collections/components/addToCollectionModal/CardToBeAddedPreview.tsx index e6839159..e51c8566 100644 --- a/src/webapp/features/collections/components/addToCollectionModal/CardToBeAddedPreview.tsx +++ b/src/webapp/features/collections/components/addToCollectionModal/CardToBeAddedPreview.tsx @@ -74,7 +74,7 @@ export default function CardToBeAddedPreview(props: Props) { router.push(`/collections/${collection.id}`)} + onClick={() => + router.push( + `/profile/${collection.createdBy.handle}/collections/${collection.id}`, + ) + } radius={'lg'} p={'sm'} style={{ cursor: 'pointer' }} diff --git a/src/webapp/features/collections/components/collectionsNavList/CollectionsNavList.tsx b/src/webapp/features/collections/components/collectionsNavList/CollectionsNavList.tsx index f1271b70..4256adea 100644 --- a/src/webapp/features/collections/components/collectionsNavList/CollectionsNavList.tsx +++ b/src/webapp/features/collections/components/collectionsNavList/CollectionsNavList.tsx @@ -6,9 +6,11 @@ import useCollections from '../../lib/queries/useCollections'; import { useToggle } from '@mantine/hooks'; import CollectionsNavListError from './Error.CollectionsNavList'; import CreateCollectionShortcut from '../createCollectionShortcut/CreateCollectionShortcut'; +import useMyProfile from '@/features/profile/lib/queries/useMyProfile'; export default function CollectionsNavList() { const { data, error } = useCollections({ limit: 30 }); + const { data: profile } = useMyProfile(); const [opened, toggleMenu] = useToggle([true, false]); if (error) { @@ -31,7 +33,7 @@ export default function CollectionsNavList() { ))} diff --git a/src/webapp/features/collections/components/deleteCollectionModal/DeleteCollectionModal.tsx b/src/webapp/features/collections/components/deleteCollectionModal/DeleteCollectionModal.tsx index fd64237e..f9f4f87b 100644 --- a/src/webapp/features/collections/components/deleteCollectionModal/DeleteCollectionModal.tsx +++ b/src/webapp/features/collections/components/deleteCollectionModal/DeleteCollectionModal.tsx @@ -16,9 +16,9 @@ export default function DeleteCollectionModal(props: Props) { const handleDeleteCollection = () => { deleteCollection.mutate(props.collectionId, { - onSuccess: () => { + onSuccess: (fes) => { props.onClose(); - router.push('/collections'); + router.push('./'); }, onError: () => { notifications.show({ diff --git a/src/webapp/features/feeds/components/feedActivityStatus/FeedActivityStatus.tsx b/src/webapp/features/feeds/components/feedActivityStatus/FeedActivityStatus.tsx index c87ad705..c0286af6 100644 --- a/src/webapp/features/feeds/components/feedActivityStatus/FeedActivityStatus.tsx +++ b/src/webapp/features/feeds/components/feedActivityStatus/FeedActivityStatus.tsx @@ -32,14 +32,16 @@ export default function FeedActivityStatus(props: Props) { added to{' '} {displayedCollections.map((collection, index) => ( + {/* TODO: use collection creator's handle to direct to profile/creatorHandle/collections/id */} {collection.name} + {index < displayedCollections.length - 1 ? ' and ' : ''} ))} diff --git a/src/webapp/features/home/containers/homeContainer/HomeContainer.tsx b/src/webapp/features/home/containers/homeContainer/HomeContainer.tsx index c344800b..dc1dd58a 100644 --- a/src/webapp/features/home/containers/homeContainer/HomeContainer.tsx +++ b/src/webapp/features/home/containers/homeContainer/HomeContainer.tsx @@ -21,10 +21,12 @@ import { useState } from 'react'; import { BiCollection, BiPlus } from 'react-icons/bi'; import { FaRegNoteSticky } from 'react-icons/fa6'; import AddCardDrawer from '@/features/cards/components/addCardDrawer/AddCardDrawer'; +import useMyProfile from '@/features/profile/lib/queries/useMyProfile'; export default function HomeContainer() { const { data: collectionsData } = useCollections({ limit: 4 }); const { data: myCardsData } = useMyCards({ limit: 4 }); + const { data: profile } = useMyProfile(); const [showCollectionDrawer, setShowCollectionDrawer] = useState(false); const [showAddDrawer, setShowAddDrawer] = useState(false); @@ -46,7 +48,12 @@ export default function HomeContainer() { Collections - + View all @@ -80,9 +87,14 @@ export default function HomeContainer() { - My Cards + Cards - + View all diff --git a/src/webapp/features/home/containers/homeContainer/Skeleton.HomeContainer.tsx b/src/webapp/features/home/containers/homeContainer/Skeleton.HomeContainer.tsx index af488932..cb7f82e4 100644 --- a/src/webapp/features/home/containers/homeContainer/Skeleton.HomeContainer.tsx +++ b/src/webapp/features/home/containers/homeContainer/Skeleton.HomeContainer.tsx @@ -1,14 +1,13 @@ import { - Anchor, Container, Grid, GridCol, Group, SimpleGrid, + Skeleton, Stack, Title, } from '@mantine/core'; -import Link from 'next/link'; import { BiCollection } from 'react-icons/bi'; import { FaRegNoteSticky } from 'react-icons/fa6'; import UrlCardSkeleton from '@/features/cards/components/urlCard/Skeleton.UrlCard'; @@ -28,9 +27,7 @@ export default function HomeContainerSkeleton() { Collections - - View all - + @@ -45,11 +42,9 @@ export default function HomeContainerSkeleton() { - My Cards + Cards - - View all - + diff --git a/src/webapp/features/profile/components/profileHeader/ProfileHeader.tsx b/src/webapp/features/profile/components/profileHeader/ProfileHeader.tsx new file mode 100644 index 00000000..bacf3ae6 --- /dev/null +++ b/src/webapp/features/profile/components/profileHeader/ProfileHeader.tsx @@ -0,0 +1,63 @@ +import { ApiClient } from '@/api-client/ApiClient'; +import { getAccessTokenInServerComponent } from '@/services/auth'; +import { + Container, + Stack, + Group, + Avatar, + Text, + Title, + Button, +} from '@mantine/core'; +import { FaBluesky } from 'react-icons/fa6'; + +interface Props { + handle: string; +} + +export default async function ProfileHeader(props: Props) { + const accessToken = await getAccessTokenInServerComponent(); + + const apiClient = new ApiClient( + process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3000', + () => accessToken, + ); + const data = await apiClient.getMyProfile(); + + return ( + + + + + + + + + {data.name} + + + {data.handle} + + + {data.description && {data.description}} + + + + + + + ); +} diff --git a/src/webapp/features/profile/components/profileMenu/ProfileMenu.tsx b/src/webapp/features/profile/components/profileMenu/ProfileMenu.tsx index bb66fa26..467cbd1c 100644 --- a/src/webapp/features/profile/components/profileMenu/ProfileMenu.tsx +++ b/src/webapp/features/profile/components/profileMenu/ProfileMenu.tsx @@ -58,7 +58,7 @@ export default function ProfileMenu() { - +