From 5b40a370437d94d3ba9c6f22653750380ffbf9ad Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sat, 16 Sep 2023 12:39:06 -0500 Subject: [PATCH] feat: provide custom title for each page. feat: add global error. feat: add favicon image. --- src/app/account/[[...account]]/page.tsx | 20 ++--- src/app/error.tsx | 1 + src/app/favicon.ico | Bin 0 -> 15086 bytes src/app/following/page.tsx | 5 ++ src/app/global-error.tsx | 35 ++++++++ src/app/layout.tsx | 3 +- src/app/login/[[...login]]/page.tsx | 23 ++--- src/app/page.tsx | 6 +- src/app/posts/[id]/edit/page.tsx | 67 ++------------- src/app/posts/[id]/page.tsx | 102 ++--------------------- src/app/posts/add/page.tsx | 55 ++---------- src/app/profile/[id]/page.tsx | 5 ++ src/app/search/page.tsx | 5 ++ src/app/signup/[[...signup]]/page.tsx | 23 ++--- src/app/types/index.ts | 4 +- src/components/AccountForm.tsx | 20 +++++ src/components/AddPostForm.tsx | 55 ++++++++++++ src/components/DisplayPost.tsx | 106 ++++++++++++++++++++++++ src/components/EditPostForm.tsx | 69 +++++++++++++++ src/components/Editor.tsx | 7 +- src/components/LoginForm.tsx | 23 +++++ src/components/SignUpForm.tsx | 23 +++++ 22 files changed, 400 insertions(+), 257 deletions(-) create mode 100644 src/app/favicon.ico create mode 100644 src/app/global-error.tsx create mode 100644 src/components/AccountForm.tsx create mode 100644 src/components/AddPostForm.tsx create mode 100644 src/components/DisplayPost.tsx create mode 100644 src/components/EditPostForm.tsx create mode 100644 src/components/LoginForm.tsx create mode 100644 src/components/SignUpForm.tsx diff --git a/src/app/account/[[...account]]/page.tsx b/src/app/account/[[...account]]/page.tsx index 8fe3d45..a129288 100644 --- a/src/app/account/[[...account]]/page.tsx +++ b/src/app/account/[[...account]]/page.tsx @@ -1,22 +1,14 @@ -'use client'; +import AccountForm from '@/components/AccountForm'; +import { Metadata } from 'next'; -import { UserProfile } from '@clerk/nextjs'; +export const metadata: Metadata = { + title: 'conveX | Account', +}; export default function AccountPage() { return (
- +
); } diff --git a/src/app/error.tsx b/src/app/error.tsx index c673ea9..518afa1 100644 --- a/src/app/error.tsx +++ b/src/app/error.tsx @@ -1,4 +1,5 @@ 'use client'; + export default function Error({ error, reset, diff --git a/src/app/favicon.ico b/src/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..473f0b3cf8643fe1f8e7258ae0415cbe4598d95c GIT binary patch literal 15086 zcmeI2yRREX6voFm$RR*(5=6l(ws<#)&_HPf5~4w(pbtVqAVC8(bk~7E1PBlk4^f36 z3J?_%e}D@_8la&A2^)qL1UlX*e``t6%J&*64nVp>-+qsr& zyJN>3vgba&#ksqkbG=^t`H*uzaPAON*Pq9ldZ8EmUJu>@--GYKtKg~&wZcE)<1+9J z_!j&KJ^^=xd&;)J-%;>6h&=pF`wS?Z1Gf9Y3t$3#-(U0ju53odui)o8kmPQ$W?cIa z1m}QF7I*Wl9PTX6bX61k7I+<)&omjF1OD>3Tb$3(J+R8G*cogOgKvQO%aY2W&{#sR zD0js=544mkbzplCdN)p2 z@cSsMZFR4eyX8|dhv|QTdxadbYVDujXx)vw$+#av#~S#)jWbGP zR&t2B*ERR=X$~d0YkU-)mo_?@lw11^|@Xy;nEL3;nZn8i>RNg=IVjU%3at}1P?&i+JCYOUx{_1p z`jRu}b|se>v=(iX@io&kJM&|HEsn(<#5|0bi+L`2$9Y&@mZ#-iv3^>d_Q1Vh`8zFs z%heoB1wtyo+N>3#@9S2y-G}`nK<~}Zf*s%Yx;DC6yFL!|4)PcX`t1t(ZSWNM6=*L_ zdsXj(Tn^~id!+3RN$%?WI?nzzbRPy|pxB074(M9!`kt4C=gpV&zt`|S*8<~F! z#`fo5jg74S3GNGg9nbMMkDaJh9*2D^Aq#f%KF>q(6s-*6wNtqFHycJKY)gC!JAu+8*|& ztwGMWpkyj>z4>B?qqix2sV@R@dKpF-ObJ+R!TsPxNN9BueuF1Qgq z2(Czm;+*dN2f(e#Yx>Oj9{2+M0rXwX<)%m5);XFlzXG?}HItmX5Bvu79R3r?#{r=A znLqc+$3tdkGDS~5^j+jjz@DKxr{AZh{>Gj2K2Pz`GxjfFoMgMq%VPVI>uKvreYgDv zNR3;UJ^oAck9n)keyvo$lZkVw^+PAG|IBYz|5X}4sv#Uok6*tJ_Oku&{;YF9pLOos XDd$EV=f*shC*1|q);V31j&Ji1w9Ax6 literal 0 HcmV?d00001 diff --git a/src/app/following/page.tsx b/src/app/following/page.tsx index 51deec5..d0d86a2 100644 --- a/src/app/following/page.tsx +++ b/src/app/following/page.tsx @@ -1,4 +1,9 @@ import FollowingPosts from '@/components/FollowingPosts'; +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'conveX | Following', +}; export default function FollowingPage() { return ( diff --git a/src/app/global-error.tsx b/src/app/global-error.tsx new file mode 100644 index 0000000..7c6798c --- /dev/null +++ b/src/app/global-error.tsx @@ -0,0 +1,35 @@ +'use client'; + +import { Inter } from 'next/font/google'; + +const inter = Inter({ subsets: ['latin'] }); + +export default function GlobalError({ + error, + reset, +}: { + error: Error; + reset: () => void; +}) { + return ( + + +
+
+

+ Oops it looks like we've run into an issue. +

+ +
+
+ + + ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 04d3866..2aecbfa 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -10,7 +10,8 @@ import './globals.css'; const inter = Inter({ subsets: ['latin'] }); export const metadata: Metadata = { - title: 'conveX', + description: + 'An X inspired social site that supports liking, following, and posting short and long form content', }; export default function RootLayout({ diff --git a/src/app/login/[[...login]]/page.tsx b/src/app/login/[[...login]]/page.tsx index 4db6deb..faf47d7 100644 --- a/src/app/login/[[...login]]/page.tsx +++ b/src/app/login/[[...login]]/page.tsx @@ -1,25 +1,14 @@ -'use client'; +import LoginForm from '@/components/LoginForm'; +import { Metadata } from 'next'; -import { SignIn } from '@clerk/nextjs'; +export const metadata: Metadata = { + title: 'conveX | Login', +}; export default function LoginPage() { return (
- +
); } diff --git a/src/app/page.tsx b/src/app/page.tsx index 3a82499..85b8afc 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,7 +1,11 @@ import AllPosts from '@/components/AllPosts'; +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'conveX | Home', +}; export default function Home() { - throw Error(); return (
diff --git a/src/app/posts/[id]/edit/page.tsx b/src/app/posts/[id]/edit/page.tsx index a1c6ed7..1c091e3 100644 --- a/src/app/posts/[id]/edit/page.tsx +++ b/src/app/posts/[id]/edit/page.tsx @@ -1,69 +1,14 @@ -'use client'; +import EditPostForm from '@/components/EditPostForm'; +import { Metadata } from 'next'; -import Editor, { SubmitActionParams } from '@/components/Editor'; -import SpinningLoader from '@/components/SpinningLoader'; -import { useRouter } from '@/hooks'; -import { useUser } from '@clerk/nextjs'; -import { useMutation, useQuery } from 'convex/react'; -import { api } from '../../../../../convex/_generated/api'; -import { Id } from '../../../../../convex/_generated/dataModel'; +export const metadata: Metadata = { + title: 'conveX | Edit post', +}; export default function EditPostPage({ params }: { params: { id: string } }) { - const user = useUser(); - const post = useQuery(api.posts.getPostById, { - id: params.id as Id<'posts'>, - }); - const createOrUpdatePost = useMutation(api.posts.createOrUpdatePost); - const router = useRouter(); - - if (user.isSignedIn === false) { - router.push('/login'); - return; - } - - if (post === 'POST_NOT_FOUND' || post === 'USER_FOR_POST_NOT_FOUND') { - // TODO: Return actual not found component - return

Not Found

; - } - - async function submitAction({ - clerkUserId, - parentPostId, - post, - currentDoc, - }: SubmitActionParams) { - const result = await createOrUpdatePost({ - parentPostId: parentPostId, - id: post?._id, - clerkUserId: clerkUserId, - content: currentDoc, - }); - - switch (result) { - case 'USER_NOT_FOUND': - case 'USER_NOT_AUTHORIZED': - case 'CANNOT_POST_ON_BEHALF_OF_ANOTHER_USER': - throw Error('Unable to update post'); - default: - router.push(`/posts/${post?._id}`); - return; - } - } - return (
- {user.isLoaded && post !== undefined ? ( - - ) : ( -
- - Loading... -
- )} +
); } diff --git a/src/app/posts/[id]/page.tsx b/src/app/posts/[id]/page.tsx index b5452cb..d6050ce 100644 --- a/src/app/posts/[id]/page.tsx +++ b/src/app/posts/[id]/page.tsx @@ -1,16 +1,9 @@ -'use client'; +import DisplayPost from '@/components/DisplayPost'; +import { Metadata } from 'next'; -import Editor, { SubmitActionParams } from '@/components/Editor'; -import Post from '@/components/Post'; -import PostReplies from '@/components/PostReplies'; -import SpinningLoader from '@/components/SpinningLoader'; -import { useRouter } from '@/hooks'; -import { useUser } from '@clerk/nextjs'; -import { useMutation, useQuery } from 'convex/react'; -import Image from 'next/image'; -import { useEffect, useRef } from 'react'; -import { api } from '../../../../convex/_generated/api'; -import { Id } from '../../../../convex/_generated/dataModel'; +export const metadata: Metadata = { + title: 'conveX | Post', +}; export default function PostPage({ params, @@ -19,90 +12,13 @@ export default function PostPage({ params: { id: string }; searchParams: { [key: string]: string | string[] | undefined }; }) { - const replyRef = useRef(null); - const user = useUser(); - const router = useRouter(); - const post = useQuery(api.posts.getPostById, { - id: params.id as Id<'posts'>, - }); - const createOrUpdatePost = useMutation(api.posts.createOrUpdatePost); - const isReply = searchParams.reply === 'true'; - - useEffect(() => { - if (replyRef.current !== null && isReply) { - replyRef.current.scrollIntoView(); - } - }, [isReply, user]); - - async function submitAction({ - clerkUserId, - parentPostId, - post, - currentDoc, - }: SubmitActionParams) { - const result = await createOrUpdatePost({ - parentPostId: parentPostId, - id: post?._id, - clerkUserId: clerkUserId, - content: currentDoc, - }); - - switch (result) { - case 'USER_NOT_FOUND': - case 'USER_NOT_AUTHORIZED': - case 'CANNOT_POST_ON_BEHALF_OF_ANOTHER_USER': - throw Error('Unable to create post'); - default: - return; - } - } - - if (user.isSignedIn === false) { - router.push('/login'); - return; - } - - if (post === 'POST_NOT_FOUND' || post === 'USER_FOR_POST_NOT_FOUND') { - // TODO: Return actual not found component - return

Not Found

; - } - return (
- {post !== undefined && user.isLoaded ? ( - <> -
- -
-
- -
-
-
- user profile image -
-
- -
-
- - ) : ( -
- Loading post... -
- )} +
); diff --git a/src/app/posts/add/page.tsx b/src/app/posts/add/page.tsx index dfd1a01..f05ae6d 100644 --- a/src/app/posts/add/page.tsx +++ b/src/app/posts/add/page.tsx @@ -1,57 +1,14 @@ -'use client'; +import AddPostForm from '@/components/AddPostForm'; +import { Metadata } from 'next'; -import Editor, { SubmitActionParams } from '@/components/Editor'; -import SpinningLoader from '@/components/SpinningLoader'; -import { useRouter } from '@/hooks'; -import { useUser } from '@clerk/nextjs'; -import { useMutation } from 'convex/react'; -import { api } from '../../../../convex/_generated/api'; +export const metadata: Metadata = { + title: 'conveX | Add post', +}; export default function AddPost() { - const user = useUser(); - const createOrUpdatePost = useMutation(api.posts.createOrUpdatePost); - const router = useRouter(); - - async function submitAction({ - clerkUserId, - parentPostId, - post, - currentDoc, - }: SubmitActionParams) { - const result = await createOrUpdatePost({ - parentPostId: parentPostId, - id: post?._id, - clerkUserId: clerkUserId, - content: currentDoc, - }); - - switch (result) { - case 'USER_NOT_FOUND': - case 'USER_NOT_AUTHORIZED': - case 'CANNOT_POST_ON_BEHALF_OF_ANOTHER_USER': - throw Error('Unable to create post'); - default: - 'use server'; - router.push('/'); - return; - } - } - - if (user.isSignedIn === false) { - router.push('/login'); - return; - } - return (
- {user.isLoaded ? ( - - ) : ( -
- - Loading... -
- )} +
); } diff --git a/src/app/profile/[id]/page.tsx b/src/app/profile/[id]/page.tsx index a9be135..8636556 100644 --- a/src/app/profile/[id]/page.tsx +++ b/src/app/profile/[id]/page.tsx @@ -1,8 +1,13 @@ import UserPosts from '@/components/UserPosts'; import UserProfile from '@/components/UserProfile'; import { getConvexClient } from '@/lib/convex'; +import { Metadata } from 'next'; import { api } from '../../../../convex/_generated/api'; +export const metadata: Metadata = { + title: 'conveX | Profile', +}; + export default async function UserProfilePage({ params, }: { diff --git a/src/app/search/page.tsx b/src/app/search/page.tsx index 899eaeb..5b9d05f 100644 --- a/src/app/search/page.tsx +++ b/src/app/search/page.tsx @@ -1,4 +1,9 @@ import SearchResults from '@/components/SearchResults'; +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'conveX | Search', +}; export default function SearchPage({ searchParams, diff --git a/src/app/signup/[[...signup]]/page.tsx b/src/app/signup/[[...signup]]/page.tsx index 67b7c29..0c1f7f0 100644 --- a/src/app/signup/[[...signup]]/page.tsx +++ b/src/app/signup/[[...signup]]/page.tsx @@ -1,25 +1,14 @@ -'use client'; +import SignUpForm from '@/components/SignUpForm'; +import { Metadata } from 'next'; -import { SignUp } from '@clerk/nextjs'; +export const metadata: Metadata = { + title: 'conveX | Sign up', +}; export default function SignUpPage() { return (
- +
); } diff --git a/src/app/types/index.ts b/src/app/types/index.ts index 4a6b278..8606582 100644 --- a/src/app/types/index.ts +++ b/src/app/types/index.ts @@ -8,6 +8,8 @@ export type UserDto = { clerkUserId: string; }; -export type PostWithUserDto = Omit, 'contentText'> & { +export type PostDto = Omit, 'contentText'>; + +export type PostWithUserDto = PostDto & { user: UserDto; }; diff --git a/src/components/AccountForm.tsx b/src/components/AccountForm.tsx new file mode 100644 index 0000000..a0dbf0f --- /dev/null +++ b/src/components/AccountForm.tsx @@ -0,0 +1,20 @@ +'use client'; + +import { UserProfile } from '@clerk/nextjs'; + +export default function AccountForm() { + return ( + + ); +} diff --git a/src/components/AddPostForm.tsx b/src/components/AddPostForm.tsx new file mode 100644 index 0000000..1d4408f --- /dev/null +++ b/src/components/AddPostForm.tsx @@ -0,0 +1,55 @@ +'use client'; + +import { useUser } from '@clerk/nextjs'; +import { useMutation } from 'convex/react'; +import { useRouter } from '@/hooks'; +import { api } from '../../convex/_generated/api'; +import Editor, { SubmitActionParams } from './Editor'; +import SpinningLoader from './SpinningLoader'; + +export default function AddPostForm() { + const user = useUser(); + const createOrUpdatePost = useMutation(api.posts.createOrUpdatePost); + const router = useRouter(); + + async function submitAction({ + clerkUserId, + parentPostId, + post, + currentDoc, + }: SubmitActionParams) { + const result = await createOrUpdatePost({ + parentPostId: parentPostId, + id: post?._id, + clerkUserId: clerkUserId, + content: currentDoc, + }); + + switch (result) { + case 'USER_NOT_FOUND': + case 'USER_NOT_AUTHORIZED': + case 'CANNOT_POST_ON_BEHALF_OF_ANOTHER_USER': + throw Error('Unable to create post'); + default: + 'use server'; + router.push('/'); + return; + } + } + + if (user.isSignedIn === false) { + router.push('/login'); + return; + } + + if (user.isLoaded) { + return ; + } + + return ( +
+ + Loading... +
+ ); +} diff --git a/src/components/DisplayPost.tsx b/src/components/DisplayPost.tsx new file mode 100644 index 0000000..d638976 --- /dev/null +++ b/src/components/DisplayPost.tsx @@ -0,0 +1,106 @@ +'use client'; + +import { useUser } from '@clerk/nextjs'; +import { useMutation, useQuery } from 'convex/react'; +import Image from 'next/image'; +import { useRouter } from '@/hooks'; +import { useEffect, useRef } from 'react'; +import { api } from '../../convex/_generated/api'; +import { Id } from '../../convex/_generated/dataModel'; +import Editor, { SubmitActionParams } from './Editor'; +import Post from './Post'; +import PostReplies from './PostReplies'; +import SpinningLoader from './SpinningLoader'; + +export default function DisplayPost({ + postId, + isReply, +}: { + postId: string; + isReply: boolean; +}) { + const replyRef = useRef(null); + const user = useUser(); + const router = useRouter(); + const post = useQuery(api.posts.getPostById, { + id: postId as Id<'posts'>, + }); + const createOrUpdatePost = useMutation(api.posts.createOrUpdatePost); + + useEffect(() => { + if (replyRef.current !== null && isReply) { + replyRef.current.scrollIntoView(); + } + }, [isReply, user]); + + async function submitAction({ + clerkUserId, + parentPostId, + post, + currentDoc, + }: SubmitActionParams) { + const result = await createOrUpdatePost({ + parentPostId: parentPostId, + id: post?._id, + clerkUserId: clerkUserId, + content: currentDoc, + }); + + switch (result) { + case 'USER_NOT_FOUND': + case 'USER_NOT_AUTHORIZED': + case 'CANNOT_POST_ON_BEHALF_OF_ANOTHER_USER': + throw Error('Unable to create post'); + default: + return; + } + } + + if (user.isSignedIn === false) { + router.push('/login'); + return; + } + + if (post === 'POST_NOT_FOUND' || post === 'USER_FOR_POST_NOT_FOUND') { + // TODO: Return actual not found component + return

Not Found

; + } + + if (post !== undefined && user.isLoaded) { + return ( + <> +
+ +
+
+ +
+
+
+ user profile image +
+
+ +
+
+ + ); + } + + return ( +
+ Loading post... +
+ ); +} diff --git a/src/components/EditPostForm.tsx b/src/components/EditPostForm.tsx new file mode 100644 index 0000000..8929c48 --- /dev/null +++ b/src/components/EditPostForm.tsx @@ -0,0 +1,69 @@ +'use client'; + +import { useRouter } from '@/hooks'; +import { useUser } from '@clerk/nextjs'; +import { useMutation, useQuery } from 'convex/react'; +import { api } from '../../convex/_generated/api'; +import { Id } from '../../convex/_generated/dataModel'; +import Editor, { SubmitActionParams } from './Editor'; +import SpinningLoader from './SpinningLoader'; + +export default function EditPostForm({ postId }: { postId: string }) { + const user = useUser(); + const post = useQuery(api.posts.getPostById, { + id: postId as Id<'posts'>, + }); + const createOrUpdatePost = useMutation(api.posts.createOrUpdatePost); + const router = useRouter(); + + if (user.isSignedIn === false) { + router.push('/login'); + return; + } + + if (post === 'POST_NOT_FOUND' || post === 'USER_FOR_POST_NOT_FOUND') { + // TODO: Return actual not found component + return

Not Found

; + } + + async function submitAction({ + clerkUserId, + parentPostId, + post, + currentDoc, + }: SubmitActionParams) { + const result = await createOrUpdatePost({ + parentPostId: parentPostId, + id: post?._id, + clerkUserId: clerkUserId, + content: currentDoc, + }); + + switch (result) { + case 'USER_NOT_FOUND': + case 'USER_NOT_AUTHORIZED': + case 'CANNOT_POST_ON_BEHALF_OF_ANOTHER_USER': + throw Error('Unable to update post'); + default: + router.push(`/posts/${post?._id}`); + return; + } + } + + if (user.isLoaded && post !== undefined) { + return ( + + ); + } + + return ( +
+ + Loading... +
+ ); +} diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index 7e4e1ff..3844b20 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -1,5 +1,6 @@ 'use client'; +import { PostDto } from '@/app/types'; import { useCodeMirror, useRouter } from '@/hooks'; import { basicDark } from '@/lib/codemirror'; import { indentWithTab } from '@codemirror/commands'; @@ -9,13 +10,13 @@ import { Compartment, EditorState, Text } from '@codemirror/state'; import { EditorView, keymap } from '@codemirror/view'; import { basicSetup } from 'codemirror'; import { FormEvent, useEffect, useState } from 'react'; -import { Doc, Id } from '../../convex/_generated/dataModel'; +import { Id } from '../../convex/_generated/dataModel'; import PostContent from './PostContent'; export type SubmitActionParams = { clerkUserId: string; parentPostId?: Id<'posts'>; - post?: Doc<'posts'>; + post?: PostDto; currentDoc: string[]; }; @@ -28,7 +29,7 @@ export default function Editor({ }: { clerkUserId: string; parentPostId?: Id<'posts'>; - post?: Doc<'posts'>; + post?: PostDto; submitAction: ({ clerkUserId, parentPostId, diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx new file mode 100644 index 0000000..3b78e13 --- /dev/null +++ b/src/components/LoginForm.tsx @@ -0,0 +1,23 @@ +'use client'; + +import { SignIn } from '@clerk/nextjs'; + +export default function LoginForm() { + return ( + + ); +} diff --git a/src/components/SignUpForm.tsx b/src/components/SignUpForm.tsx new file mode 100644 index 0000000..220b807 --- /dev/null +++ b/src/components/SignUpForm.tsx @@ -0,0 +1,23 @@ +'use client'; + +import { SignUp } from '@clerk/nextjs'; + +export default function SignUpForm() { + return ( + + ); +}