Merge pull request #42 from StevanFreeborn/stevanfreeborn/feat/add-finishing-touches
feat: add missing pages
This commit is contained in:
+6
-1
@@ -3,6 +3,11 @@
|
||||
"extends": ["next/core-web-vitals", "plugin:prettier/recommended"],
|
||||
"rules": {
|
||||
"no-console": "warn",
|
||||
"prettier/prettier": "error"
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,3 +8,4 @@ An [X](https://twitter.com/home) clone built as a submission for the [WebDevCody
|
||||
- [Clerk](https://clerk.com/) - User authentication and management
|
||||
- [Tailwind](https://tailwindcss.com/) - Styling
|
||||
- [Vercel](https://vercel.com/) - Hosting
|
||||
- [Codemirror](https://codemirror.net/) - Editor
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'conveX | About',
|
||||
};
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<main className="flex flex-col items-center flex-1">
|
||||
<article className="flex flex-col w-full max-w-4xl gap-8">
|
||||
<section className="flex flex-col items-center gap-2">
|
||||
<h1 className="text-4xl mt-4">
|
||||
Welcome to{' '}
|
||||
<span className="italic">
|
||||
conve<span className="font-bold text-primary-accent">X</span>
|
||||
</span>
|
||||
</h1>
|
||||
<p className="max-w-xl">
|
||||
An X inspired social site that supports following other users,
|
||||
liking posts, and sharing both long and short posts formatted with{' '}
|
||||
<a
|
||||
className="text-primary-accent font-semibold dark:text-white dark:underline"
|
||||
href="https://commonmark.org/help/"
|
||||
target="_blank"
|
||||
>
|
||||
Markdown
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</section>
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-bold">About</h2>
|
||||
<p>
|
||||
This site was built as a submission for the WebDevCody{' '}
|
||||
<a
|
||||
className="text-primary-accent font-semibold dark:text-white dark:underline"
|
||||
href="https://hackathon.webdevcody.com/"
|
||||
target="_blank"
|
||||
>
|
||||
hackathon
|
||||
</a>
|
||||
. The following are some technologies that were used to build the
|
||||
site:
|
||||
</p>
|
||||
<ul className=" flex flex-col gap-2 list-disc ml-4">
|
||||
<li>
|
||||
<a
|
||||
className="text-primary-accent font-semibold dark:text-white dark:underline"
|
||||
href="https://www.typescriptlang.org/"
|
||||
target="_blank"
|
||||
>
|
||||
TypeScript
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="text-primary-accent font-semibold dark:text-white dark:underline"
|
||||
href="https://nextjs.org/"
|
||||
target="_blank"
|
||||
>
|
||||
NEXT.js
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="text-primary-accent font-semibold dark:text-white dark:underline"
|
||||
href="https://www.convex.dev/"
|
||||
target="_blank"
|
||||
>
|
||||
Convex
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="text-primary-accent font-semibold dark:text-white dark:underline"
|
||||
href="https://clerk.com/"
|
||||
target="_blank"
|
||||
>
|
||||
Clerk
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="text-primary-accent font-semibold dark:text-white dark:underline"
|
||||
href="https://tailwindcss.com/"
|
||||
target="_blank"
|
||||
>
|
||||
Tailwind
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="text-primary-accent font-semibold dark:text-white dark:underline"
|
||||
href="https://codemirror.net/"
|
||||
target="_blank"
|
||||
>
|
||||
Codemirror
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="text-primary-accent font-semibold dark:text-white dark:underline"
|
||||
href="https://vercel.com/"
|
||||
target="_blank"
|
||||
>
|
||||
Vercel
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</article>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<main className="flex flex-col items-center">
|
||||
<UserProfile
|
||||
appearance={{
|
||||
elements: {
|
||||
card: 'sm:pb-4 shadow-md dark:bg-secondary-gray [&_*:not(button):not(a)]:dark:text-white [&_.cl-internal-b3fm6y]:hidden',
|
||||
navbar: 'hidden',
|
||||
navbarMobileMenuRow: 'hidden',
|
||||
formFieldInput: 'dark:bg-primary-gray dark:text-white',
|
||||
profileSectionTitle: 'dark:border-white dark:border-opacity-10',
|
||||
breadcrumbsItem: 'dark:text-white',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<AccountForm />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Metadata } from 'next';
|
||||
import { RiTwitterXLine } from 'react-icons/ri';
|
||||
import { TiSocialLinkedin } from 'react-icons/ti';
|
||||
import { VscGithub } from 'react-icons/vsc';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'conveX | Contact',
|
||||
};
|
||||
|
||||
export default function ContactPage() {
|
||||
return (
|
||||
<main className="flex flex-col items-center justify-center flex-1">
|
||||
<article className="flex flex-col w-full max-w-4xl">
|
||||
<section className="flex flex-col items-center gap-8">
|
||||
<h1 className="text-4xl font-bold">Get in touch</h1>
|
||||
<ul className="flex flex-col items-center gap-8 sm:flex-row">
|
||||
<li className="bg-primary-accent rounded-full p-1 shadow-md hover:scale-125 transition duration-300 ease-in transform">
|
||||
<a
|
||||
href="https://github.com/StevanFreeborn"
|
||||
target="_blank"
|
||||
className="text-white"
|
||||
>
|
||||
<VscGithub className="w-16 h-16" />
|
||||
</a>
|
||||
</li>
|
||||
<li className="bg-primary-accent rounded-full p-1 shadow-md hover:scale-125 transition duration-300 ease-in transform">
|
||||
<a
|
||||
href="https://twitter.com/stevan_freeborn"
|
||||
target="_blank"
|
||||
className="text-white"
|
||||
>
|
||||
<RiTwitterXLine className="w-16 h-16" />
|
||||
</a>
|
||||
</li>
|
||||
<li className="bg-primary-accent rounded-full p-1 shadow-md hover:scale-125 transition duration-300 ease-in transform">
|
||||
<a
|
||||
href="https://www.linkedin.com/in/stevan-freeborn-77917b75/"
|
||||
target="_blank"
|
||||
className="text-white"
|
||||
>
|
||||
<TiSocialLinkedin className="w-16 h-16" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</article>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
'use client';
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error;
|
||||
reset: () => void;
|
||||
}) {
|
||||
console.log(error);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-center items-center flex-1">
|
||||
<div className="flex flex-col items-center w-full max-w-4xl gap-4">
|
||||
<h2 className="text-2xl">
|
||||
Oops it looks like we've run into an issue.
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
className="px-3 py-1 text-white bg-primary-accent rounded-md"
|
||||
onClick={() => reset()}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -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 (
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
'use client';
|
||||
|
||||
import { Inter } from 'next/font/google';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
export default function GlobalError({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error;
|
||||
reset: () => void;
|
||||
}) {
|
||||
console.log(error);
|
||||
|
||||
return (
|
||||
<html lang="en" className="w-full h-full">
|
||||
<body
|
||||
className={`w-full h-full flex flex-col bg-white text-primary-gray dark:bg-primary-gray dark:text-white ${inter.className}`}
|
||||
>
|
||||
<main className="flex flex-col justify-center items-center flex-1">
|
||||
<div className="flex flex-col items-center w-full max-w-4xl gap-4">
|
||||
<h2 className="text-2xl">
|
||||
Oops it looks like we've run into an issue.
|
||||
</h2>
|
||||
<button
|
||||
className="px-3 py-1 text-white bg-primary-accent rounded-md"
|
||||
onClick={() => reset()}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
+2
-1
@@ -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({
|
||||
|
||||
@@ -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 (
|
||||
<main className="flex flex-col flex-1 items-center">
|
||||
<SignIn
|
||||
appearance={{
|
||||
elements: {
|
||||
card: 'shadow-md dark:bg-secondary-gray [&_*:not(button):not(a)]:dark:text-white [&_.cl-internal-b3fm6y]:hidden',
|
||||
socialButtonsIconButton__github:
|
||||
'hover:bg-gradient-to-br from-blue-600 via-purple-600 to-red-600 dark:border-white dark:border-opacity-10',
|
||||
socialButtonsIconButton__google:
|
||||
'hover:bg-gradient-to-r from-blue-500 via-green-500 to-yellow-500 dark:border-white dark:border-opacity-10',
|
||||
socialButtonsIconButton__microsoft:
|
||||
'hover:bg-gradient-to-r from-red-600 via-green-600 to-blue-600 dark:border-white dark:border-opacity-10',
|
||||
dividerLine: 'dark:bg-white dark:bg-opacity-10',
|
||||
formFieldInput: 'dark:bg-primary-gray dark:text-white',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<LoginForm />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function NotFound({
|
||||
heading = 'Not Found',
|
||||
message = "Hmmm it looks like we couldn't find what you were looking for.",
|
||||
}: {
|
||||
heading?: string;
|
||||
message?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col justify-center items-center flex-1">
|
||||
<div className="flex flex-col items-center w-full max-w-4xl gap-4">
|
||||
<h2 className="text-2xl">{heading}</h2>
|
||||
<p>{message}</p>
|
||||
<Link
|
||||
className="px-3 py-1 text-white bg-primary-accent rounded-md"
|
||||
href="/"
|
||||
>
|
||||
Go to home page
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
import AllPosts from '@/components/AllPosts';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'conveX | Home',
|
||||
};
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
|
||||
@@ -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 <h1>Not Found</h1>;
|
||||
}
|
||||
|
||||
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 (
|
||||
<main className="flex flex-col flex-1 w-full items-center">
|
||||
{user.isLoaded && post !== undefined ? (
|
||||
<Editor
|
||||
clerkUserId={user.user.id}
|
||||
post={post}
|
||||
submitAction={submitAction}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex gap-2 items-center">
|
||||
<SpinningLoader className="animate-spin w-5 h-5" />
|
||||
Loading...
|
||||
</div>
|
||||
)}
|
||||
<EditPostForm postId={params.id} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<HTMLDivElement>(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 <h1>Not Found</h1>;
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="flex flex-col flex-1 w-full items-center">
|
||||
<div className="flex flex-col w-full max-w-4xl gap-4">
|
||||
{post !== undefined && user.isLoaded ? (
|
||||
<>
|
||||
<div className="flex flex-col min-w-0 p-1 rounded-md border border-gray-600">
|
||||
<Post post={post} limit={false} showReply={false} />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<PostReplies parentId={post._id} />
|
||||
</div>
|
||||
<div className="flex w-full h-80 gap-4">
|
||||
<div>
|
||||
<Image
|
||||
alt="user profile image"
|
||||
src={user.user.imageUrl}
|
||||
width={40}
|
||||
height={40}
|
||||
className="rounded-full object-cover border-4 border-primary-accent"
|
||||
/>
|
||||
</div>
|
||||
<div ref={replyRef} className="flex w-full">
|
||||
<Editor
|
||||
clerkUserId={user.user.id}
|
||||
parentPostId={post._id}
|
||||
submitAction={submitAction}
|
||||
autofocus={isReply}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex w-full justify-center gap-2">
|
||||
<SpinningLoader className="animate-spin w-5 h-5" /> Loading post...
|
||||
</div>
|
||||
)}
|
||||
<DisplayPost
|
||||
postId={params.id}
|
||||
isReply={searchParams.reply === 'true'}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<main className="flex flex-col flex-1 w-full items-center">
|
||||
{user.isLoaded ? (
|
||||
<Editor clerkUserId={user.user.id} submitAction={submitAction} />
|
||||
) : (
|
||||
<div className="flex gap-2 items-center">
|
||||
<SpinningLoader className="animate-spin w-5 h-5" />
|
||||
Loading...
|
||||
</div>
|
||||
)}
|
||||
<AddPostForm />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'conveX | Privacy',
|
||||
};
|
||||
|
||||
export default function PrivacyPage() {
|
||||
return (
|
||||
<main className="flex flex-col items-center flex-1">
|
||||
<article className="flex flex-col w-full max-w-4xl gap-8">
|
||||
<section className="flex flex-col gap-2">
|
||||
<h1 className="text-4xl font-bold mb-2">Privacy Policy for conveX</h1>
|
||||
<p>
|
||||
At conveX, accessible from https://conve-x.stevanfreeborn.com, one
|
||||
of our main priorities is the privacy of our visitors. This Privacy
|
||||
Policy document contains types of information that is collected and
|
||||
recorded by conveX and how we use it.
|
||||
</p>
|
||||
<p>
|
||||
If you have additional questions or require more information about
|
||||
our Privacy Policy, do not hesitate to contact us.
|
||||
</p>
|
||||
</section>
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-bold">Log Files</h2>
|
||||
<p>
|
||||
conveX follows a standard procedure of using log files. These files
|
||||
log visitors when they visit websites. All hosting companies do this
|
||||
and a part of hosting services' analytics. The information
|
||||
collected by log files include internet protocol (IP) addresses,
|
||||
browser type, Internet Service Provider (ISP), date and time stamp,
|
||||
referring/exit pages, and possibly the number of clicks. These are
|
||||
not linked to any information that is personally identifiable. The
|
||||
purpose of the information is for analyzing trends, administering
|
||||
the site, tracking users' movement on the website, and
|
||||
gathering demographic information. Our Privacy Policy was created
|
||||
with the help of the{' '}
|
||||
<a
|
||||
className="underline"
|
||||
href="https://www.privacypolicyonline.com/privacy-policy-generator/"
|
||||
>
|
||||
Privacy Policy Generator
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</section>
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-bold">Cookies and Web Beacons</h2>
|
||||
<p>
|
||||
Like any other website, conveX uses "cookies". These
|
||||
cookies are used to store information including visitors'
|
||||
preferences, and the pages on the website that the visitor accessed
|
||||
or visited. The information is used to optimize the users'
|
||||
experience by customizing our web page content based on
|
||||
visitors' browser type and/or other information.
|
||||
</p>
|
||||
<p>
|
||||
For more general information on cookies, please read{' '}
|
||||
<a
|
||||
className="underline"
|
||||
href="https://www.privacypolicyonline.com/what-are-cookies/"
|
||||
>
|
||||
the "Cookies" article from the Privacy Policy Generator
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</section>
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-bold">Privacy Policies</h2>
|
||||
<p>
|
||||
You may consult this list to find the Privacy Policy for each of the
|
||||
advertising partners of conveX.
|
||||
</p>
|
||||
<p>
|
||||
Third-party ad servers or ad networks uses technologies like
|
||||
cookies, JavaScript, or Web Beacons that are used in their
|
||||
respective advertisements and links that appear on conveX, which are
|
||||
sent directly to users' browser. They automatically receive
|
||||
your IP address when this occurs. These technologies are used to
|
||||
measure the effectiveness of their advertising campaigns and/or to
|
||||
personalize the advertising content that you see on websites that
|
||||
you visit.
|
||||
</p>
|
||||
<p>
|
||||
Note that conveX has no access to or control over these cookies that
|
||||
are used by third-party advertisers.
|
||||
</p>
|
||||
<h2 className="text-2xl font-bold">Third Party Privacy Policies</h2>
|
||||
<p>
|
||||
conveX's Privacy Policy does not apply to other advertisers or
|
||||
websites. Thus, we are advising you to consult the respective
|
||||
Privacy Policies of these third-party ad servers for more detailed
|
||||
information. It may include their practices and instructions about
|
||||
how to opt-out of certain options.{' '}
|
||||
</p>
|
||||
<p>
|
||||
You can choose to disable cookies through your individual browser
|
||||
options. To know more detailed information about cookie management
|
||||
with specific web browsers, it can be found at the browsers'
|
||||
respective websites. What Are Cookies?
|
||||
</p>
|
||||
</section>
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-bold">Children's Information</h2>
|
||||
<p>
|
||||
Another part of our priority is adding protection for children while
|
||||
using the internet. We encourage parents and guardians to observe,
|
||||
participate in, and/or monitor and guide their online activity.
|
||||
</p>
|
||||
<p>
|
||||
conveX does not knowingly collect any Personal Identifiable
|
||||
Information from children under the age of 13. If you think that
|
||||
your child provided this kind of information on our website, we
|
||||
strongly encourage you to contact us immediately and we will do our
|
||||
best efforts to promptly remove such information from our records.
|
||||
</p>
|
||||
<h2 className="text-2xl font-bold">Online Privacy Policy Only</h2>
|
||||
<p>
|
||||
This Privacy Policy applies only to our online activities and is
|
||||
valid for visitors to our website with regards to the information
|
||||
that they shared and/or collect in conveX. This policy is not
|
||||
applicable to any information collected offline or via channels
|
||||
other than this website.
|
||||
</p>
|
||||
</section>
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-bold">Consent</h2>
|
||||
<p>
|
||||
By using our website, you hereby consent to our Privacy Policy and
|
||||
agree to its Terms and Conditions.
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
import NotFound from '@/app/not-found';
|
||||
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,
|
||||
}: {
|
||||
@@ -14,8 +20,7 @@ export default async function UserProfilePage({
|
||||
});
|
||||
|
||||
if (user === 'USER_NOT_FOUND') {
|
||||
// TODO: Show real not found component
|
||||
return <div>Not Found</div>;
|
||||
return <NotFound />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
<main className="flex flex-col flex-1 items-center">
|
||||
<SignUp
|
||||
appearance={{
|
||||
elements: {
|
||||
card: 'shadow-md dark:bg-secondary-gray [&_*:not(button):not(a):not(svg)]:dark:text-white [&_.cl-internal-b3fm6y]:hidden',
|
||||
socialButtonsIconButton__github:
|
||||
'hover:bg-gradient-to-br from-blue-600 via-purple-600 to-red-600 dark:border-white dark:border-opacity-10',
|
||||
socialButtonsIconButton__google:
|
||||
'hover:bg-gradient-to-r from-blue-500 via-green-500 to-yellow-500 dark:border-white dark:border-opacity-10',
|
||||
socialButtonsIconButton__microsoft:
|
||||
'hover:bg-gradient-to-r from-red-600 via-green-600 to-blue-600 dark:border-white dark:border-opacity-10',
|
||||
dividerLine: 'dark:bg-white dark:bg-opacity-10',
|
||||
formFieldInput: 'dark:bg-primary-gray dark:text-white',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<SignUpForm />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'conveX | Terms',
|
||||
};
|
||||
|
||||
export default function TermsPage() {
|
||||
return (
|
||||
<main className="flex flex-col items-center flex-1">
|
||||
<article className="flex flex-col w-full max-w-4xl gap-8">
|
||||
<section className="flex flex-col gap-2">
|
||||
<h1 className="text-4xl font-bold mb-2">Terms and Conditions</h1>
|
||||
<p>Welcome to conveX!</p>
|
||||
<p>
|
||||
These terms and conditions outline the rules and regulations for the
|
||||
use of conveX's Website, located at
|
||||
https://conve-x.stevanfreeborn.com.
|
||||
</p>
|
||||
<p>
|
||||
By accessing this website we assume you accept these terms and
|
||||
conditions. Do not continue to use conveX if you do not agree to
|
||||
take all of the terms and conditions stated on this page.
|
||||
</p>
|
||||
<p>
|
||||
The following terminology applies to these Terms and Conditions,
|
||||
Privacy Statement and Disclaimer Notice and all Agreements:
|
||||
"Client", "You" and "Your" refers to
|
||||
you, the person log on this website and compliant to the Company’s
|
||||
terms and conditions. "The Company",
|
||||
"Ourselves", "We", "Our" and
|
||||
"Us", refers to our Company. "Party",
|
||||
"Parties", or "Us", refers to both the Client
|
||||
and ourselves. All terms refer to the offer, acceptance and
|
||||
consideration of payment necessary to undertake the process of our
|
||||
assistance to the Client in the most appropriate manner for the
|
||||
express purpose of meeting the Client’s needs in respect of
|
||||
provision of the Company’s stated services, in accordance with and
|
||||
subject to, prevailing law of Netherlands. Any use of the above
|
||||
terminology or other words in the singular, plural, capitalization
|
||||
and/or he/she or they, are taken as interchangeable and therefore as
|
||||
referring to same. Our Terms and Conditions were created with the
|
||||
help of the{' '}
|
||||
<a
|
||||
className="underline"
|
||||
href="https://www.privacypolicyonline.com/terms-conditions-generator/"
|
||||
>
|
||||
Terms & Conditions Generator
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-bold">Cookies</h2>
|
||||
<p>
|
||||
We employ the use of cookies. By accessing conveX, you agreed to use
|
||||
cookies in agreement with the conveX's Privacy Policy.
|
||||
</p>
|
||||
<p>
|
||||
Most interactive websites use cookies to let us retrieve the
|
||||
user's details for each visit. Cookies are used by our website
|
||||
to enable the functionality of certain areas to make it easier for
|
||||
people visiting our website. Some of our affiliate/advertising
|
||||
partners may also use cookies.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-bold">License</h2>
|
||||
<p>
|
||||
Unless otherwise stated, conveX and/or its licensors own the
|
||||
intellectual property rights for all material on conveX. All
|
||||
intellectual property rights are reserved. You may access this from
|
||||
conveX for your own personal use subjected to restrictions set in
|
||||
these terms and conditions.
|
||||
</p>
|
||||
<p>You must not:</p>
|
||||
<ul className="list-disc ml-4">
|
||||
<li>Republish material from conveX</li>
|
||||
<li>Sell, rent or sub-license material from conveX</li>
|
||||
<li>Reproduce, duplicate or copy material from conveX</li>
|
||||
<li>Redistribute content from conveX</li>
|
||||
</ul>
|
||||
<p>This Agreement shall begin on the date hereof.</p>
|
||||
<p>
|
||||
Parts of this website offer an opportunity for users to post and
|
||||
exchange opinions and information in certain areas of the website.
|
||||
conveX does not filter, edit, publish or review Comments prior to
|
||||
their presence on the website. Comments do not reflect the views and
|
||||
opinions of conveX,its agents and/or affiliates. Comments reflect
|
||||
the views and opinions of the person who post their views and
|
||||
opinions. To the extent permitted by applicable laws, conveX shall
|
||||
not be liable for the Comments or for any liability, damages or
|
||||
expenses caused and/or suffered as a result of any use of and/or
|
||||
posting of and/or appearance of the Comments on this website.
|
||||
</p>
|
||||
<p>
|
||||
conveX reserves the right to monitor all Comments and to remove any
|
||||
Comments which can be considered inappropriate, offensive or causes
|
||||
breach of these Terms and Conditions.
|
||||
</p>
|
||||
<p>You warrant and represent that:</p>
|
||||
<ul className="list-disc ml-4">
|
||||
<li>
|
||||
You are entitled to post the Comments on our website and have all
|
||||
necessary licenses and consents to do so;
|
||||
</li>
|
||||
<li>
|
||||
The Comments do not invade any intellectual property right,
|
||||
including without limitation copyright, patent or trademark of any
|
||||
third party;
|
||||
</li>
|
||||
<li>
|
||||
The Comments do not contain any defamatory, libelous, offensive,
|
||||
indecent or otherwise unlawful material which is an invasion of
|
||||
privacy
|
||||
</li>
|
||||
<li>
|
||||
The Comments will not be used to solicit or promote business or
|
||||
custom or present commercial activities or unlawful activity.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
You hereby grant conveX a non-exclusive license to use, reproduce,
|
||||
edit and authorize others to use, reproduce and edit any of your
|
||||
Comments in any and all forms, formats or media.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-bold">Hyperlinking to our Content</h2>
|
||||
<p>
|
||||
The following organizations may link to our Website without prior
|
||||
written approval:
|
||||
</p>
|
||||
<ul className="list-disc ml-4">
|
||||
<li>Government agencies;</li>
|
||||
<li>Search engines;</li>
|
||||
<li>News organizations;</li>
|
||||
<li>
|
||||
Online directory distributors may link to our Website in the same
|
||||
manner as they hyperlink to the Websites of other listed
|
||||
businesses; and
|
||||
</li>
|
||||
<li>
|
||||
System wide Accredited Businesses except soliciting non-profit
|
||||
organizations, charity shopping malls, and charity fundraising
|
||||
groups which may not hyperlink to our Web site.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
These organizations may link to our home page, to publications or to
|
||||
other Website information so long as the link: (a) is not in any way
|
||||
deceptive; (b) does not falsely imply sponsorship, endorsement or
|
||||
approval of the linking party and its products and/or services; and
|
||||
(c) fits within the context of the linking party's site.
|
||||
</p>
|
||||
<p>
|
||||
We may consider and approve other link requests from the following
|
||||
types of organizations:
|
||||
</p>
|
||||
<ul className="list-disc ml-4">
|
||||
<li>
|
||||
commonly-known consumer and/or business information sources;
|
||||
</li>
|
||||
<li>dot.com community sites;</li>
|
||||
<li>associations or other groups representing charities;</li>
|
||||
<li>online directory distributors;</li>
|
||||
<li>internet portals;</li>
|
||||
<li>accounting, law and consulting firms; and</li>
|
||||
<li>educational institutions and trade associations.</li>
|
||||
</ul>
|
||||
<p>
|
||||
We will approve link requests from these organizations if we decide
|
||||
that: (a) the link would not make us look unfavorably to ourselves
|
||||
or to our accredited businesses; (b) the organization does not have
|
||||
any negative records with us; (c) the benefit to us from the
|
||||
visibility of the hyperlink compensates the absence of conveX; and
|
||||
(d) the link is in the context of general resource information.
|
||||
</p>
|
||||
<p>
|
||||
These organizations may link to our home page so long as the link:
|
||||
(a) is not in any way deceptive; (b) does not falsely imply
|
||||
sponsorship, endorsement or approval of the linking party and its
|
||||
products or services; and (c) fits within the context of the linking
|
||||
party's site.
|
||||
</p>
|
||||
<p>
|
||||
If you are one of the organizations listed in paragraph 2 above and
|
||||
are interested in linking to our website, you must inform us by
|
||||
sending an e-mail to conveX. Please include your name, your
|
||||
organization name, contact information as well as the URL of your
|
||||
site, a list of any URLs from which you intend to link to our
|
||||
Website, and a list of the URLs on our site to which you would like
|
||||
to link. Wait 2-3 weeks for a response.
|
||||
</p>
|
||||
<p>Approved organizations may hyperlink to our Website as follows:</p>
|
||||
<ul className="list-disc ml-4">
|
||||
<li>By use of our corporate name; or</li>
|
||||
<li>By use of the uniform resource locator being linked to; or</li>
|
||||
<li>
|
||||
By use of any other description of our Website being linked to
|
||||
that makes sense within the context and format of content on the
|
||||
linking party's site.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
No use of conveX's logo or other artwork will be allowed for
|
||||
linking absent a trademark license agreement.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-bold">iFrames</h2>
|
||||
<p>
|
||||
Without prior approval and written permission, you may not create
|
||||
frames around our Webpages that alter in any way the visual
|
||||
presentation or appearance of our Website.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold">Content Liability</h2>
|
||||
<p>
|
||||
We shall not be hold responsible for any content that appears on
|
||||
your Website. You agree to protect and defend us against all claims
|
||||
that is rising on your Website. No link(s) should appear on any
|
||||
Website that may be interpreted as libelous, obscene or criminal, or
|
||||
which infringes, otherwise violates, or advocates the infringement
|
||||
or other violation of, any third party rights.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-bold">Reservation of Rights</h2>
|
||||
<p>
|
||||
We reserve the right to request that you remove all links or any
|
||||
particular link to our Website. You approve to immediately remove
|
||||
all links to our Website upon request. We also reserve the right to
|
||||
amen these terms and conditions and it's linking policy at any
|
||||
time. By continuously linking to our Website, you agree to be bound
|
||||
to and follow these linking terms and conditions.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-bold">
|
||||
Removal of links from our website
|
||||
</h2>
|
||||
<p>
|
||||
If you find any link on our Website that is offensive for any
|
||||
reason, you are free to contact and inform us any moment. We will
|
||||
consider requests to remove links but we are not obligated to or so
|
||||
or to respond to you directly.
|
||||
</p>
|
||||
<p>
|
||||
We do not ensure that the information on this website is correct, we
|
||||
do not warrant its completeness or accuracy; nor do we promise to
|
||||
ensure that the website remains available or that the material on
|
||||
the website is kept up to date.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-bold">Disclaimer</h2>
|
||||
<p>
|
||||
To the maximum extent permitted by applicable law, we exclude all
|
||||
representations, warranties and conditions relating to our website
|
||||
and the use of this website. Nothing in this disclaimer will:
|
||||
</p>
|
||||
<ul className="list-disc ml-4">
|
||||
<li>
|
||||
limit or exclude our or your liability for death or personal
|
||||
injury;
|
||||
</li>
|
||||
<li>
|
||||
limit or exclude our or your liability for fraud or fraudulent
|
||||
misrepresentation;
|
||||
</li>
|
||||
<li>
|
||||
limit any of our or your liabilities in any way that is not
|
||||
permitted under applicable law; or
|
||||
</li>
|
||||
<li>
|
||||
exclude any of our or your liabilities that may not be excluded
|
||||
under applicable law.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
The limitations and prohibitions of liability set in this Section
|
||||
and elsewhere in this disclaimer: (a) are subject to the preceding
|
||||
paragraph; and (b) govern all liabilities arising under the
|
||||
disclaimer, including liabilities arising in contract, in tort and
|
||||
for breach of statutory duty.
|
||||
</p>
|
||||
<p>
|
||||
As long as the website and the information and services on the
|
||||
website are provided free of charge, we will not be liable for any
|
||||
loss or damage of any nature.
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,8 @@ export type UserDto = {
|
||||
clerkUserId: string;
|
||||
};
|
||||
|
||||
export type PostWithUserDto = Omit<Doc<'posts'>, 'contentText'> & {
|
||||
export type PostDto = Omit<Doc<'posts'>, 'contentText'>;
|
||||
|
||||
export type PostWithUserDto = PostDto & {
|
||||
user: UserDto;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
'use client';
|
||||
|
||||
import { UserProfile } from '@clerk/nextjs';
|
||||
|
||||
export default function AccountForm() {
|
||||
return (
|
||||
<UserProfile
|
||||
appearance={{
|
||||
elements: {
|
||||
card: 'sm:pb-4 shadow-md dark:bg-secondary-gray [&_*:not(button):not(a)]:dark:text-white [&_.cl-internal-b3fm6y]:hidden',
|
||||
navbar: 'hidden',
|
||||
navbarMobileMenuRow: 'hidden',
|
||||
formFieldInput: 'dark:bg-primary-gray dark:text-white',
|
||||
profileSectionTitle: 'dark:border-white dark:border-opacity-10',
|
||||
breadcrumbsItem: 'dark:text-white',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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 <Editor clerkUserId={user.user.id} submitAction={submitAction} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center">
|
||||
<SpinningLoader className="animate-spin w-5 h-5" />
|
||||
Loading...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
'use client';
|
||||
|
||||
import NotFound from '@/app/not-found';
|
||||
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';
|
||||
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<HTMLDivElement>(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') {
|
||||
return <NotFound />;
|
||||
}
|
||||
|
||||
if (post !== undefined && user.isLoaded) {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col min-w-0 p-1 rounded-md border border-gray-600">
|
||||
<Post post={post} limit={false} showReply={false} />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<PostReplies parentId={post._id} />
|
||||
</div>
|
||||
<div className="flex w-full h-80 gap-4">
|
||||
<div>
|
||||
<Image
|
||||
alt="user profile image"
|
||||
src={user.user.imageUrl}
|
||||
width={40}
|
||||
height={40}
|
||||
className="rounded-full object-cover border-4 border-primary-accent"
|
||||
/>
|
||||
</div>
|
||||
<div ref={replyRef} className="flex w-full">
|
||||
<Editor
|
||||
clerkUserId={user.user.id}
|
||||
parentPostId={post._id}
|
||||
submitAction={submitAction}
|
||||
autofocus={isReply}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex w-full justify-center gap-2">
|
||||
<SpinningLoader className="animate-spin w-5 h-5" /> Loading post...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
'use client';
|
||||
|
||||
import NotFound from '@/app/not-found';
|
||||
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') {
|
||||
return <NotFound />;
|
||||
}
|
||||
|
||||
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 (
|
||||
<Editor
|
||||
clerkUserId={user.user.id}
|
||||
post={post}
|
||||
submitAction={submitAction}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center">
|
||||
<SpinningLoader className="animate-spin w-5 h-5" />
|
||||
Loading...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -20,6 +20,7 @@ export default function Footer() {
|
||||
<Link
|
||||
href="https://github.com/StevanFreeborn/conve-x"
|
||||
className="hover:text-primary-orange"
|
||||
target="_blank"
|
||||
>
|
||||
Code
|
||||
</Link>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
'use client';
|
||||
|
||||
import { SignIn } from '@clerk/nextjs';
|
||||
|
||||
export default function LoginForm() {
|
||||
return (
|
||||
<SignIn
|
||||
appearance={{
|
||||
elements: {
|
||||
card: 'shadow-md dark:bg-secondary-gray [&_*:not(button):not(a)]:dark:text-white [&_.cl-internal-b3fm6y]:hidden',
|
||||
socialButtonsIconButton__github:
|
||||
'hover:bg-gradient-to-br from-blue-600 via-purple-600 to-red-600 dark:border-white dark:border-opacity-10',
|
||||
socialButtonsIconButton__google:
|
||||
'hover:bg-gradient-to-r from-blue-500 via-green-500 to-yellow-500 dark:border-white dark:border-opacity-10',
|
||||
socialButtonsIconButton__microsoft:
|
||||
'hover:bg-gradient-to-r from-red-600 via-green-600 to-blue-600 dark:border-white dark:border-opacity-10',
|
||||
dividerLine: 'dark:bg-white dark:bg-opacity-10',
|
||||
formFieldInput: 'dark:bg-primary-gray dark:text-white',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
'use client';
|
||||
|
||||
import { SignUp } from '@clerk/nextjs';
|
||||
|
||||
export default function SignUpForm() {
|
||||
return (
|
||||
<SignUp
|
||||
appearance={{
|
||||
elements: {
|
||||
card: 'shadow-md dark:bg-secondary-gray [&_*:not(button):not(a):not(svg)]:dark:text-white [&_.cl-internal-b3fm6y]:hidden',
|
||||
socialButtonsIconButton__github:
|
||||
'hover:bg-gradient-to-br from-blue-600 via-purple-600 to-red-600 dark:border-white dark:border-opacity-10',
|
||||
socialButtonsIconButton__google:
|
||||
'hover:bg-gradient-to-r from-blue-500 via-green-500 to-yellow-500 dark:border-white dark:border-opacity-10',
|
||||
socialButtonsIconButton__microsoft:
|
||||
'hover:bg-gradient-to-r from-red-600 via-green-600 to-blue-600 dark:border-white dark:border-opacity-10',
|
||||
dividerLine: 'dark:bg-white dark:bg-opacity-10',
|
||||
formFieldInput: 'dark:bg-primary-gray dark:text-white',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { authMiddleware } from '@clerk/nextjs';
|
||||
|
||||
export default authMiddleware({
|
||||
publicRoutes: [],
|
||||
publicRoutes: ['/about', '/contact', '/terms', '/privacy'],
|
||||
});
|
||||
|
||||
export const config = {
|
||||
|
||||
Reference in New Issue
Block a user