feat: display user's posts on profile page

This commit is contained in:
Stevan Freeborn
2023-09-11 00:40:14 -05:00
parent 8b350c9809
commit 4b74f47e75
18 changed files with 397 additions and 67 deletions
+10 -5
View File
@@ -17,12 +17,13 @@ import { api } from '../../convex/_generated/api';
import PostContent from './PostContent';
export default function Editor() {
const user = useUser();
const { user, isSignedIn } = useUser();
const { theme } = useTheme();
const editorTheme = new Compartment();
const [currentDoc, setCurrentDoc] = useState(['']);
const createPost = useMutation(api.posts.createPost);
const router = useRouter();
const [creating, setCreating] = useState(false);
const doc = [''];
const extensions = [
@@ -55,13 +56,15 @@ export default function Editor() {
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
try {
if (user.isSignedIn !== true) {
if (isSignedIn !== true) {
router.push('/login');
return;
}
setCreating(true);
const result = await createPost({
clerkUserId: user.user.id,
clerkUserId: user.id,
content: currentDoc,
});
@@ -76,6 +79,8 @@ export default function Editor() {
}
} catch (error) {
console.log(error);
} finally {
setCreating(false);
}
}
@@ -121,8 +126,8 @@ export default function Editor() {
<div className='flex items-center justify-end p-4 pt-0'>
<button
type='submit'
disabled={!currentDoc.join() || !currentDoc.join().trim()}
className='py-1 px-4 bg-primary-accent text-white rounded-md disabled:opacity-50'
disabled={!currentDoc.join() || !currentDoc.join().trim() || creating}
className='py-1 px-4 bg-primary-accent text-white rounded-md disabled:opacity-50 flex items-center justify-center gap-2'
>
Post
</button>
+3 -14
View File
@@ -1,7 +1,8 @@
import { SignedIn, SignedOut, UserButton, currentUser } from '@clerk/nextjs';
import { SignedIn, SignedOut, currentUser } from '@clerk/nextjs';
import Link from 'next/link';
import { BiSolidMessageSquareAdd } from 'react-icons/bi';
import ThemeButton from './ThemeButton';
import UserButton from './UserButton';
export default async function Navbar() {
const user = await currentUser();
@@ -50,19 +51,7 @@ export default async function Navbar() {
</li>
<SignedIn>
<li>
<UserButton
appearance={{
elements: {
card: 'dark:bg-secondary-gray [&_*]:dark:text-white',
userButtonTrigger:
'border-solid border-2 border-primary-accent focus:shadow-none',
},
}}
userProfileMode='navigation'
userProfileUrl='/account'
signInUrl='/login'
afterSignOutUrl='/'
/>
<UserButton />
</li>
</SignedIn>
<SignedOut>
+55
View File
@@ -0,0 +1,55 @@
import { Text } from '@codemirror/state';
import { useQuery } from 'convex/react';
import Image from 'next/image';
import { api } from '../../convex/_generated/api';
import { Doc } from '../../convex/_generated/dataModel';
import PostContent from './PostContent';
import SpinningLoader from './SpinningLoader';
export default function Post({ post }: { post: Doc<'posts'> }) {
const user = useQuery(api.users.getUserById, { id: post.userId });
if (user === undefined) {
return (
<div>
<SpinningLoader className='animate-spin w-5 h-5' />
</div>
);
}
if (user === 'USER_NOT_FOUND') {
return <div>User for post not found</div>;
}
const username = user.clerkUser.username ?? user._id;
const createdPostDate = new Date(post._creationTime);
const postYear = createdPostDate.getFullYear();
const postMonth = createdPostDate.toLocaleString(undefined, {
month: 'short',
});
const dayOfMonth = createdPostDate.getDate();
return (
<div className='flex w-full h-full gap-4 p-8 bg-white dark:bg-secondary-gray border border-gray-600'>
<div>
<Image
alt='user profile image'
src={user.clerkUser.image_url}
width={40}
height={40}
className='rounded-full object-cover border-4 border-primary-accent'
/>
</div>
<div className='flex flex-col gap-2'>
<div className='flex items-center gap-2'>
<div>{username}</div>
<div className='text-xs'>{`${postMonth} ${dayOfMonth}, ${postYear}`}</div>
</div>
<div>
<PostContent content={Text.of(post.content).toString()} />
</div>
<div></div>
</div>
</div>
);
}
+5
View File
@@ -0,0 +1,5 @@
import { ImSpinner2 } from 'react-icons/im';
export default function SpinningLoader({ className }: { className: string }) {
return <ImSpinner2 className={className} />;
}
+3 -4
View File
@@ -1,16 +1,15 @@
'use client';
import { useMountedEffect } from '@/hooks';
import { useTheme } from 'next-themes';
import { useEffect, useState } from 'react';
import { BsMoonStarsFill, BsSunFill } from 'react-icons/bs';
import { ImSpinner2 } from 'react-icons/im';
export default function ThemeButton() {
const [mounted, setMounted] = useState(false);
const { mounted, Loader } = useMountedEffect();
const { theme, setTheme } = useTheme();
useEffect(() => setMounted(true), []);
const isDark = theme === 'dark';
return (
@@ -19,7 +18,7 @@ export default function ThemeButton() {
onClick={() => setTheme(isDark ? 'light' : 'dark')}
>
{mounted === false ? (
<ImSpinner2 className='w-5 h-5 animate-spin' />
<Loader className='animate-spin w-5 h-5' />
) : isDark ? (
<BsMoonStarsFill className='w-5 h-5' />
) : (
+27
View File
@@ -0,0 +1,27 @@
'use client';
import { useMountedEffect } from '@/hooks';
import { UserButton as ClerkUserButton } from '@clerk/nextjs';
export default function UserButton() {
const { mounted } = useMountedEffect();
if (mounted === false) {
return;
}
return (
<ClerkUserButton
appearance={{
elements: {
card: 'dark:bg-secondary-gray [&_*]:dark:text-white',
userButtonTrigger:
'border-solid border-2 border-primary-accent focus:shadow-none',
},
}}
userProfileMode='navigation'
userProfileUrl='/account'
signInUrl='/login'
afterSignOutUrl='/'
/>
);
}
+38
View File
@@ -0,0 +1,38 @@
'use client';
import { usePaginatedQuery } from 'convex/react';
import { api } from '../../convex/_generated/api';
import { Doc } from '../../convex/_generated/dataModel';
import Post from './Post';
export default function UserPosts({ user }: { user: Doc<'users'> }) {
const PAGE_SIZE = 10;
const pager = usePaginatedQuery(
api.posts.getUsersPostById,
{
userId: user._id,
},
{ initialNumItems: 10 }
);
return (
<div className='flex flex-col w-full h-full'>
{pager.results.map(post => (
<Post
key={post._id}
post={post}
/>
))}
{pager.isLoading ? (
<div>Loading...</div>
) : (
<button
onClick={() => pager.loadMore(10)}
disabled={pager.status !== 'CanLoadMore'}
>
Load More
</button>
)}
</div>
);
}
+38
View File
@@ -0,0 +1,38 @@
import Image from 'next/image';
import { AiFillCalendar } from 'react-icons/ai';
import { Doc } from '../../convex/_generated/dataModel';
export default function UserProfile({ user }: { user: Doc<'users'> }) {
const username = user.clerkUser.username ?? user._id;
const userCreatedDate = new Date(user._creationTime);
const monthJoined = userCreatedDate.toLocaleString(undefined, {
month: 'short',
});
const yearJoined = userCreatedDate.getFullYear();
return (
<div className='w-full'>
<div className='shadow-md bg-gradient-to-r from-violet-600 via-violet-600 to-indigo-600 rounded-t-md pt-20 pl-5 pr-40'>
<div className='flex items-center gap-4 -mb-[25px]'>
<div>
<Image
alt='user profile image'
src={user.clerkUser.image_url}
width={100}
height={100}
className='rounded-full object-cover border-4 border-primary-accent'
/>
</div>
<div>
<h1 className='font-bold'>{username}</h1>
<div className='flex gap-2 items-center text-sm'>
<AiFillCalendar className='w-4 h-4' />
{`Joined ${monthJoined} ${yearJoined}`}
</div>
</div>
</div>
</div>
<div className='h-[50px] bg-white dark:bg-primary-gray pl-5 pr-40 border border-gray-600'></div>
</div>
);
}