diff --git a/.eslintrc.json b/.eslintrc.json index 76c069e..05dcbe8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,6 +3,11 @@ "extends": ["next/core-web-vitals", "plugin:prettier/recommended"], "rules": { "no-console": "warn", - "prettier/prettier": "error" + "prettier/prettier": [ + "error", + { + "endOfLine": "auto" + } + ] } } diff --git a/README.md b/README.md index 954ee26..0f7b4cf 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx new file mode 100644 index 0000000..69fa838 --- /dev/null +++ b/src/app/about/page.tsx @@ -0,0 +1,114 @@ +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'conveX | About', +}; + +export default function AboutPage() { + return ( +
+
+
+

+ Welcome to{' '} + + conveX + +

+

+ An X inspired social site that supports following other users, + liking posts, and sharing both long and short posts formatted with{' '} + + Markdown + + . +

+
+
+

About

+

+ This site was built as a submission for the WebDevCody{' '} + + hackathon + + . The following are some technologies that were used to build the + site: +

+ +
+
+
+ ); +} 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/contact/page.tsx b/src/app/contact/page.tsx new file mode 100644 index 0000000..b4c871d --- /dev/null +++ b/src/app/contact/page.tsx @@ -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 ( +
+
+
+

Get in touch

+ +
+
+
+ ); +} diff --git a/src/app/error.tsx b/src/app/error.tsx new file mode 100644 index 0000000..9e2d2c1 --- /dev/null +++ b/src/app/error.tsx @@ -0,0 +1,28 @@ +'use client'; + +export default function Error({ + error, + reset, +}: { + error: Error; + reset: () => void; +}) { + console.log(error); + + return ( +
+
+

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

+ +
+
+ ); +} diff --git a/src/app/favicon.ico b/src/app/favicon.ico new file mode 100644 index 0000000..473f0b3 Binary files /dev/null and b/src/app/favicon.ico differ 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..e65fe3c --- /dev/null +++ b/src/app/global-error.tsx @@ -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 ( + + +
+
+

+ 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/not-found.tsx b/src/app/not-found.tsx new file mode 100644 index 0000000..bcbe4f0 --- /dev/null +++ b/src/app/not-found.tsx @@ -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 ( +
+
+

{heading}

+

{message}

+ + Go to home page + +
+
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index ce81220..85b8afc 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -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 ( 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/privacy/page.tsx b/src/app/privacy/page.tsx new file mode 100644 index 0000000..61c1760 --- /dev/null +++ b/src/app/privacy/page.tsx @@ -0,0 +1,136 @@ +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'conveX | Privacy', +}; + +export default function PrivacyPage() { + return ( +
+
+
+

Privacy Policy for conveX

+

+ 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. +

+

+ If you have additional questions or require more information about + our Privacy Policy, do not hesitate to contact us. +

+
+
+

Log Files

+

+ 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{' '} + + Privacy Policy Generator + + . +

+
+
+

Cookies and Web Beacons

+

+ 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. +

+

+ For more general information on cookies, please read{' '} + + the "Cookies" article from the Privacy Policy Generator + + . +

+
+
+

Privacy Policies

+

+ You may consult this list to find the Privacy Policy for each of the + advertising partners of conveX. +

+

+ 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. +

+

+ Note that conveX has no access to or control over these cookies that + are used by third-party advertisers. +

+

Third Party Privacy Policies

+

+ 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.{' '} +

+

+ 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? +

+
+
+

Children's Information

+

+ 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. +

+

+ 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. +

+

Online Privacy Policy Only

+

+ 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. +

+
+
+

Consent

+

+ By using our website, you hereby consent to our Privacy Policy and + agree to its Terms and Conditions. +

+
+
+
+ ); +} diff --git a/src/app/profile/[id]/page.tsx b/src/app/profile/[id]/page.tsx index a9be135..70c5f8d 100644 --- a/src/app/profile/[id]/page.tsx +++ b/src/app/profile/[id]/page.tsx @@ -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
Not Found
; + return ; } return ( 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/terms/page.tsx b/src/app/terms/page.tsx new file mode 100644 index 0000000..d555318 --- /dev/null +++ b/src/app/terms/page.tsx @@ -0,0 +1,305 @@ +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'conveX | Terms', +}; + +export default function TermsPage() { + return ( +
+
+
+

Terms and Conditions

+

Welcome to conveX!

+

+ These terms and conditions outline the rules and regulations for the + use of conveX's Website, located at + https://conve-x.stevanfreeborn.com. +

+

+ 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. +

+

+ 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{' '} + + Terms & Conditions Generator + + . +

+
+ +
+

Cookies

+

+ We employ the use of cookies. By accessing conveX, you agreed to use + cookies in agreement with the conveX's Privacy Policy. +

+

+ 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. +

+
+ +
+

License

+

+ 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. +

+

You must not:

+
    +
  • Republish material from conveX
  • +
  • Sell, rent or sub-license material from conveX
  • +
  • Reproduce, duplicate or copy material from conveX
  • +
  • Redistribute content from conveX
  • +
+

This Agreement shall begin on the date hereof.

+

+ 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. +

+

+ 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. +

+

You warrant and represent that:

+
    +
  • + You are entitled to post the Comments on our website and have all + necessary licenses and consents to do so; +
  • +
  • + The Comments do not invade any intellectual property right, + including without limitation copyright, patent or trademark of any + third party; +
  • +
  • + The Comments do not contain any defamatory, libelous, offensive, + indecent or otherwise unlawful material which is an invasion of + privacy +
  • +
  • + The Comments will not be used to solicit or promote business or + custom or present commercial activities or unlawful activity. +
  • +
+

+ 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. +

+
+ +
+

Hyperlinking to our Content

+

+ The following organizations may link to our Website without prior + written approval: +

+
    +
  • Government agencies;
  • +
  • Search engines;
  • +
  • News organizations;
  • +
  • + Online directory distributors may link to our Website in the same + manner as they hyperlink to the Websites of other listed + businesses; and +
  • +
  • + System wide Accredited Businesses except soliciting non-profit + organizations, charity shopping malls, and charity fundraising + groups which may not hyperlink to our Web site. +
  • +
+

+ 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. +

+

+ We may consider and approve other link requests from the following + types of organizations: +

+
    +
  • + commonly-known consumer and/or business information sources; +
  • +
  • dot.com community sites;
  • +
  • associations or other groups representing charities;
  • +
  • online directory distributors;
  • +
  • internet portals;
  • +
  • accounting, law and consulting firms; and
  • +
  • educational institutions and trade associations.
  • +
+

+ 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. +

+

+ 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. +

+

+ 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. +

+

Approved organizations may hyperlink to our Website as follows:

+
    +
  • By use of our corporate name; or
  • +
  • By use of the uniform resource locator being linked to; or
  • +
  • + 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. +
  • +
+

+ No use of conveX's logo or other artwork will be allowed for + linking absent a trademark license agreement. +

+
+ +
+

iFrames

+

+ 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. +

+
+ +
+

Content Liability

+

+ 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. +

+
+ +
+

Reservation of Rights

+

+ 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. +

+
+ +
+

+ Removal of links from our website +

+

+ 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. +

+

+ 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. +

+
+ +
+

Disclaimer

+

+ 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: +

+
    +
  • + limit or exclude our or your liability for death or personal + injury; +
  • +
  • + limit or exclude our or your liability for fraud or fraudulent + misrepresentation; +
  • +
  • + limit any of our or your liabilities in any way that is not + permitted under applicable law; or +
  • +
  • + exclude any of our or your liabilities that may not be excluded + under applicable law. +
  • +
+

+ 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. +

+

+ 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. +

+
+
+
+ ); +} 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..77f5ab2 --- /dev/null +++ b/src/components/DisplayPost.tsx @@ -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(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 ; + } + + 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..e77ad94 --- /dev/null +++ b/src/components/EditPostForm.tsx @@ -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 ; + } + + 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/Footer.tsx b/src/components/Footer.tsx index cee649e..164751e 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -20,6 +20,7 @@ export default function Footer() { Code 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 ( + + ); +} diff --git a/src/middleware.ts b/src/middleware.ts index a29eb28..1ca7664 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,7 +1,7 @@ import { authMiddleware } from '@clerk/nextjs'; export default authMiddleware({ - publicRoutes: [], + publicRoutes: ['/about', '/contact', '/terms', '/privacy'], }); export const config = {