diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c65d7b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/README.md b/README.md new file mode 100644 index 0000000..efc3c7d --- /dev/null +++ b/README.md @@ -0,0 +1,174 @@ +
+

+ + Logo + +

Reviews.io

+ +

+ A Modern website which accumulates crowd-sourced reviews about restaurants +
+

+

+ +![Forks](https://img.shields.io/github/forks/Kaushik612/reviews.io?style=social) ![Stargazers](https://img.shields.io/github/stars/Kaushik612/reviews.io?style=social) ![License](https://img.shields.io/github/license/Kaushik612/reviews.io) + +## Table Of Contents + +- [Screenshots](#screenshots) +- [About the Project](#about-the-project) +- [Built With](#built-with) +- [Getting Started](#getting-started) + - [Prerequisites](#prerequisites) + - [Installation](#installation) +- [License](#license) +- [Authors](#authors) + +## Screenshots + +![HomePage-Dark](images/screenshots/homepage1.png) + +![HomePage-Light](images/screenshots/homepage-light.png) + +![Restaurant-Detail](images/screenshots/restaurant-detail-page.png) + +![Clerk-Auth](images/screenshots/clerk-auth.png) + +![Post-Review](images/screenshots/post-review.png) + +![Pagination](images/screenshots/pagination.png) + +![Mobile-Responsive](images/screenshots/mobile-responsive-1.png) + +## About The Project + +A Modern website for reviewing your favorite Restaurants and checking out reviews from other customers. + +The idea behind building this website is to get a good understanding of developing a fully functional Full stack application using Next.js framework and MongoDB as a backend database. + +This application is built using all the new Next.js 13 features and also features TypeScript which makes everything just better! + +I use Clerk for authentication which I feel makes setting up auth for your Next.js projects a breeze. + +### Built With + +- [![Next][Next.js]][Next-url] +- [![React][React.js]][React-url] +- [![Tailwind][Tailwind.css]][Tailwind-url] +- [![Typescript][Typescript]][Typescript-url] +- [![Prisma][Prisma]][Prisma-url] +- [![MongoDB][MongoDB]][MongoDB-url] + +### Features: + +- Tailwind design and Framer-motion animations +- Fully Responsive +- Clerk Authentication (Email, Google, 9+ Social Logins) +- Client form validation and handling using react-hook-form & zod +- Server error handling using react-toast +- Page loading state +- How to write POST, DELETE, and GET routes in route handlers (app/api) +- How to fetch data in server react components by directly accessing database (WITHOUT API! like Magic!) +- How to handle relations between Server and Child components! +- How to reuse layouts +- Folder structure in Next 13 App Router +- Sample About and Contact page that can be expanded as required + +

(back to top)

+ +## Getting Started + +### Prerequisites + +**Node version 18.x.x** + +### Cloning the repository + +```shell +git clone https://github.com/Kaushik612/reviews.io.git +``` + +### Install packages + +```sh +npm install +``` + +### Setup .env file + +```js +NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= +CLERK_SECRET_KEY= + +NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in +NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up +NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard +NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard + +DATABASE_URL= + +NEXT_PUBLIC_APP_URL="http://localhost:3000" +``` + +### Setup Prisma + +Add MongoDb Database (I am using Mongo Atlas) + +```shell +npx prisma init +npx prisma generate +npx prisma db push +``` + +Seed Data: + +I am using ChatGPT to generate seed data that will be fed to the database and I would suggest you do the same too. This saves a lot of time in creating test data and you can use this time to work on the UI development. + +```shell +node lib/seed.ts +``` + +### Start the app + +```shell +npm run dev +``` + +

(back to top)

+ +## License + +Distributed under the MIT License. See [LICENSE](https://github.com/Kaushik612/reviews.io/blob/main/LICENSE.md) for more information. + +## Authors + +**Kaushik Ravikumar** - _Full Stack Web Developer_ - [Kaushik Ravikumar](https://github.com/kaushik612) + + + + +[contributors-shield]: https://img.shields.io/github/contributors/github_username/repo_name.svg?style=for-the-badge +[contributors-url]: https://github.com/github_username/repo_name/graphs/contributors +[forks-shield]: https://img.shields.io/github/forks/github_username/repo_name.svg?style=for-the-badge +[forks-url]: https://github.com/github_username/repo_name/network/members +[stars-shield]: https://img.shields.io/github/stars/github_username/repo_name.svg?style=for-the-badge +[stars-url]: https://github.com/github_username/repo_name/stargazers +[issues-shield]: https://img.shields.io/github/issues/github_username/repo_name.svg?style=for-the-badge +[issues-url]: https://github.com/github_username/repo_name/issues +[license-shield]: https://img.shields.io/github/license/github_username/repo_name.svg?style=for-the-badge +[license-url]: https://github.com/github_username/repo_name/blob/master/LICENSE.txt +[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 +[linkedin-url]: https://linkedin.com/in/linkedin_username +[product-screenshot]: images/screenshot.png +[Next.js]: https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white +[Next-url]: https://nextjs.org/ +[React.js]: https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB +[React-url]: https://reactjs.org/ +[Tailwind.css]: https://img.shields.io/badge/Tailwind_CSS-38B2AC?style=for-the-badge&logo=tailwind-css&logoColor=white +[Tailwind-url]: https://tailwindcss.com/ +[Typescript]: https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white +[Typescript-url]: https:/typescript.org +[Prisma]: https://img.shields.io/badge/Prisma-3982CE?style=for-the-badge&logo=Prisma&logoColor=white +[Prisma-url]: https:/prisma.io +[MongoDB]: https://img.shields.io/badge/MongoDB-4EA94B?style=for-the-badge&logo=mongodb&logoColor=white +[MongoDB-url]: https:/mongodb.com diff --git a/app/(auth)/(routes)/layout.tsx b/app/(auth)/(routes)/layout.tsx new file mode 100644 index 0000000..1bcb95d --- /dev/null +++ b/app/(auth)/(routes)/layout.tsx @@ -0,0 +1,11 @@ +export default function AuthLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+ {children} +
+ ); +} diff --git a/app/(auth)/(routes)/sign-in/[[...sign-in]]/page.tsx b/app/(auth)/(routes)/sign-in/[[...sign-in]]/page.tsx new file mode 100644 index 0000000..2cc13d4 --- /dev/null +++ b/app/(auth)/(routes)/sign-in/[[...sign-in]]/page.tsx @@ -0,0 +1,5 @@ +import { SignIn } from "@clerk/nextjs"; + +export default function Page() { + return ; +} diff --git a/app/(auth)/(routes)/sign-up/[[...sign-up]]/page.tsx b/app/(auth)/(routes)/sign-up/[[...sign-up]]/page.tsx new file mode 100644 index 0000000..2743945 --- /dev/null +++ b/app/(auth)/(routes)/sign-up/[[...sign-up]]/page.tsx @@ -0,0 +1,5 @@ +import { SignUp } from "@clerk/nextjs"; + +export default function Page() { + return ; +} diff --git a/app/(links)/(routes)/about/page.tsx b/app/(links)/(routes)/about/page.tsx new file mode 100644 index 0000000..f2f9e82 --- /dev/null +++ b/app/(links)/(routes)/about/page.tsx @@ -0,0 +1,28 @@ +"use client"; +import React from "react"; + +const AboutPage = () => { + return ( +
+
+

+ About Us +

+

+ This is a sample About us page. Write your own unique story here! +

+
+

+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Velit + dolores dignissimos accusantium perferendis officia culpa voluptatem + labore aut! Nemo facere, similique voluptatibus nulla itaque cum + doloremque quidem exercitationem, vero adipisci incidunt voluptatum + quaerat blanditiis mollitia, provident beatae sed culpa odit? +

+
+
+
+ ); +}; + +export default AboutPage; diff --git a/app/(links)/(routes)/contact/page.tsx b/app/(links)/(routes)/contact/page.tsx new file mode 100644 index 0000000..793efc7 --- /dev/null +++ b/app/(links)/(routes)/contact/page.tsx @@ -0,0 +1,70 @@ +"use client"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import React from "react"; + +const ContactPage = () => { + return ( +
+
+

+ Contact Us +

+

+ Got a technical issue? Want to send feedback about a beta feature? + Need details about our Business plan? Let us know. +

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ ); +}; + +export default ContactPage; diff --git a/app/(restaurtants)/(routes)/create/loading.tsx b/app/(restaurtants)/(routes)/create/loading.tsx new file mode 100644 index 0000000..33e70a2 --- /dev/null +++ b/app/(restaurtants)/(routes)/create/loading.tsx @@ -0,0 +1,14 @@ +"use client"; +import React from "react"; + +import { PuffLoader } from "react-spinners"; + +const loading = () => { + return ( +
+ +
+ ); +}; + +export default loading; diff --git a/app/(restaurtants)/(routes)/create/page.tsx b/app/(restaurtants)/(routes)/create/page.tsx new file mode 100644 index 0000000..8015098 --- /dev/null +++ b/app/(restaurtants)/(routes)/create/page.tsx @@ -0,0 +1,197 @@ +"use client"; +import React from "react"; +import { useRouter } from "next/navigation"; +import { Controller, useForm } from "react-hook-form"; +import * as z from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; + +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { useToast } from "@/components/ui/use-toast"; +import axios from "axios"; +import { Wand2 } from "lucide-react"; + +import GooglePlacesAutocompleteComponent from "@/components/google/GooglePlacesAutocompleteComponent"; +import CuisineSelectComponent from "@/components/restaurants/CuisineSelectComponent"; +import { MdClear } from "react-icons/md"; + +export interface TagOption { + readonly value: string; + readonly label: string; +} + +const tagOptions: readonly TagOption[] = [ + { value: "thai", label: "Thai" }, + { value: "asian", label: "Asian" }, + { value: "american", label: "American" }, + { value: "indian", label: "Indian" }, + { value: "korean", label: "Korean" }, + { value: "glutenfriendly", label: "Gluten-Friendly" }, + { value: "vegan", label: "Vegan" }, + { value: "vegetarian", label: "Vegetarian" }, + { value: "italian", label: "Italian" }, + { value: "japanese", label: "Japanese" }, + { value: "vietnamese", label: "Vietnamese" }, + { value: "sportsbar", label: "Sports Bar" }, + { value: "pub", label: "Pub" }, +]; + +const formSchema = z.object({ + name: z.string().min(1, { message: "Please enter the restaurnant name." }), + description: z + .string() + .min(1, { message: "Please write a few words about this restaurtant." }), + tags: z + .array(z.string()) + .nonempty({ message: "Please select at least one cuisine" }), + location: z.object({ + label: z.string().nonempty({ message: "Please select a location" }), + value: z.any(), + }), +}); + +const CreateReview = () => { + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: "", + description: "", + location: undefined, + tags: undefined, + }, + }); + + const isLoading = form.formState.isSubmitting; + + const onSubmit = async (values: z.infer) => { + try { + await axios.post("/api/restaurants", values); + toast({ + description: "Successfully created your restaurant.", + duration: 3000, + }); + + router.refresh(); + router.push("/"); + } catch (error: any) { + toast({ + variant: "destructive", + description: error.response.data, + duration: 3000, + }); + } + }; + + const clearForm = () => { + form.reset(); + }; + + const { toast } = useToast(); + const router = useRouter(); + + return ( +
+
+

+ Add a New Restaurtant +

+

+ Don't see a restaurant listed? This is where you can add one. +

+
+
+ + ( + + Name + + + + + + )} + /> + ( + + Description + +