├── .eslintrc.json
├── .gitignore
├── README.md
├── components.json
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── prisma
└── schema.prisma
├── public
├── companyaccount.svg
├── congratz.svg
├── cover_bg.png
├── next.svg
├── privateaccount.svg
├── publicaccount.svg
└── vercel.svg
├── src
├── app
│ ├── (auth)
│ │ └── signin
│ │ │ ├── layout.tsx
│ │ │ ├── loading.tsx
│ │ │ └── page.tsx
│ ├── api
│ │ ├── auth
│ │ │ └── [...nextauth]
│ │ │ │ └── route.ts
│ │ ├── link
│ │ │ └── route.ts
│ │ ├── search
│ │ │ └── route.ts
│ │ └── user
│ │ │ ├── aboutme
│ │ │ └── route.ts
│ │ │ ├── avatar
│ │ │ └── route.ts
│ │ │ ├── follow
│ │ │ └── route.ts
│ │ │ ├── post
│ │ │ ├── comment
│ │ │ │ ├── commentlike
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── create
│ │ │ │ └── route.ts
│ │ │ ├── newlike
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ │ └── username
│ │ │ └── route.ts
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ ├── loading.tsx
│ ├── newuser
│ │ ├── layout.tsx
│ │ ├── loading.tsx
│ │ └── page.tsx
│ ├── page.tsx
│ ├── post
│ │ └── [postId]
│ │ │ └── page.tsx
│ ├── profile
│ │ └── [userId]
│ │ │ ├── loading.tsx
│ │ │ └── page.tsx
│ └── settings
│ │ ├── loading.tsx
│ │ └── page.tsx
├── components
│ ├── BasicInfoWidget.tsx
│ ├── CommentInput.tsx
│ ├── CommentLike.tsx
│ ├── CommentOuterBox.tsx
│ ├── Comments.tsx
│ ├── CreatePostOuterBox.tsx
│ ├── CreatePostValidator.tsx
│ ├── CustomComponents
│ │ ├── CommentInputBox.tsx
│ │ └── hooks.ts
│ ├── Editor.tsx
│ ├── EditorOutput.tsx
│ ├── QuerryProvider.tsx
│ ├── SearchUserInput.tsx
│ ├── TopPageNavbar.tsx
│ ├── button
│ │ ├── CommentButton.tsx
│ │ ├── CreatePostExitPost.tsx
│ │ ├── ExitProfileBtn.tsx
│ │ ├── FollowButton.tsx
│ │ ├── LogOutButton.tsx
│ │ ├── LoginGoogleBtn.tsx
│ │ ├── NewUserButton.tsx
│ │ ├── PostLikeBtn.tsx
│ │ ├── PostLikeServerside.tsx
│ │ ├── SettingBtn.tsx
│ │ └── SlideBarResponsiveExitButton.tsx
│ ├── feed
│ │ ├── CreatePostActivator.tsx
│ │ ├── FeedColumn.tsx
│ │ ├── FeedPage.tsx
│ │ ├── FeedPostsBox.tsx
│ │ ├── MyPost.tsx
│ │ ├── ProfilePostsColumn.tsx
│ │ ├── SuggestUsers.tsx
│ │ └── UsersSuggested.tsx
│ ├── loaders
│ │ └── PostLoader.tsx
│ ├── newuserpage
│ │ ├── NewUserImageUpload.tsx
│ │ ├── ProgressBar.tsx
│ │ ├── Progresstitle.tsx
│ │ ├── Signup1.tsx
│ │ ├── Signup2.tsx
│ │ ├── Signup3.tsx
│ │ ├── Signup4.tsx
│ │ ├── Signup5.tsx
│ │ └── cards
│ │ │ └── Signupcard.tsx
│ ├── renderers
│ │ ├── CustomCodeRender.tsx
│ │ ├── CustomImageRender.tsx
│ │ └── CustomListRenderer.tsx
│ ├── slidebar
│ │ ├── SearchDropdown.tsx
│ │ ├── SearchedUsers.tsx
│ │ ├── Slidebar.tsx
│ │ └── SlidebarLink.tsx
│ └── ui
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── dialog.tsx
│ │ ├── form.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── popover.tsx
│ │ └── textarea.tsx
├── config.ts
├── lib
│ ├── Apis.ts
│ ├── Firebase.tsx
│ ├── Functions.tsx
│ ├── NewUserFormValidator.tsx
│ ├── Prisma.db.ts
│ ├── auth.ts
│ ├── commentValidator.ts
│ ├── redis.ts
│ └── utils.ts
├── middleware.ts
└── types
│ ├── PostLikeValidator.tsx
│ ├── db.d.ts
│ ├── next-auth.d.ts
│ ├── redis.d.ts
│ └── types.ts
├── tailwind.config.js
└── tsconfig.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 | .env
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project with Shadcn ui [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 | ## Best Project To Begineer to learn =>
3 | ## Nextsjs
4 | ## Typescript
5 | ## Shadcn ui [https://ui.shadcn.com/]
6 | ## Editorjs [https://editorjs.io/]
7 | ## @mantine/hooks [https://www.npmjs.com/package/@mantine/hooks]
8 | ## useform-hooks
9 |
10 |
11 |
12 | ## Getting Started
13 |
14 | First, run the development server:
15 |
16 | ```bash
17 | npm run dev
18 | # or
19 | yarn dev
20 | # or
21 | pnpm dev
22 | ```
23 |
24 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
25 |
26 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
27 |
28 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
29 |
30 | ## Learn More
31 |
32 | To learn more about Next.js, take a look at the following resources:
33 |
34 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
35 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
36 |
37 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
38 |
39 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "src/app/globals.css",
9 | "baseColor": "stone",
10 | "cssVariables": true
11 | },
12 | "aliases": {
13 | "components": "@/components",
14 | "utils": "@/lib/utils"
15 | }
16 | }
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images:{
4 | remotePatterns: [
5 | {
6 | protocol: 'https',
7 | hostname: 'firebasestorage.googleapis.com',
8 |
9 | },
10 | {
11 | protocol: 'https',
12 | hostname: 'lh3.googleusercontent.com',
13 |
14 | }
15 | ],
16 | },
17 |
18 | }
19 |
20 | module.exports = nextConfig
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "postinstall": "prisma generate"
11 | },
12 | "dependencies": {
13 | "@auth/prisma-adapter": "^1.0.1",
14 | "@editorjs/code": "^2.8.0",
15 | "@editorjs/editorjs": "^2.27.2",
16 | "@editorjs/embed": "^2.5.3",
17 | "@editorjs/header": "^2.7.0",
18 | "@editorjs/image": "^2.8.1",
19 | "@editorjs/inline-code": "^1.4.0",
20 | "@editorjs/link": "^2.5.0",
21 | "@editorjs/list": "^1.8.0",
22 | "@editorjs/table": "^2.2.2",
23 | "@hookform/resolvers": "^3.2.0",
24 | "@mantine/hooks": "^6.0.19",
25 | "@prisma/client": "^5.1.1",
26 | "@radix-ui/react-dialog": "^1.0.4",
27 | "@radix-ui/react-label": "^2.0.2",
28 | "@radix-ui/react-popover": "^1.0.6",
29 | "@radix-ui/react-slot": "^1.0.2",
30 | "@tanstack/react-query": "^4.36.1",
31 | "@types/node": "20.4.7",
32 | "@types/react": "18.2.18",
33 | "@types/react-dom": "18.2.7",
34 | "@upstash/redis": "^1.22.0",
35 | "autoprefixer": "10.4.14",
36 | "axios": "^1.4.0",
37 | "class-variance-authority": "^0.7.0",
38 | "clsx": "^2.0.0",
39 | "editorjs-react-renderer": "^3.5.1",
40 | "encoding": "^0.1.13",
41 | "eslint": "8.46.0",
42 | "eslint-config-next": "^13.4.16",
43 | "firebase": "^10.1.0",
44 | "lodash": "^4.17.21",
45 | "lucide-react": "^0.263.1",
46 | "next": "^13.4.19",
47 | "next-auth": "^4.23.1",
48 | "postcss": "8.4.27",
49 | "prisma": "^5.1.1",
50 | "react": "^18.2.0",
51 | "react-dom": "^18.2.0",
52 | "react-hook-form": "^7.45.4",
53 | "react-hot-toast": "^2.4.1",
54 | "react-icons": "^4.10.1",
55 | "react-textarea-autosize": "^8.5.2",
56 | "server-only": "^0.0.1",
57 | "sonner": "^2.0.1",
58 | "tailwind-merge": "^1.14.0",
59 | "tailwindcss": "3.3.3",
60 | "tailwindcss-animate": "^1.0.6",
61 | "timeago.js": "^4.0.2",
62 | "typescript": "5.1.6",
63 | "zod": "^3.22.1"
64 | },
65 | "devDependencies": {
66 | "@types/lodash": "^4.17.15"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | generator client {
2 | provider = "prisma-client-js"
3 | }
4 |
5 | datasource db {
6 | provider = "mongodb"
7 | url = env("DATABASE_URL")
8 | }
9 |
10 | model Account {
11 | id String @id @default(auto()) @map("_id") @db.ObjectId
12 | userId String @db.ObjectId
13 | type String
14 | provider String
15 | providerAccountId String
16 | refresh_token String?
17 | access_token String?
18 | expires_at Int?
19 | token_type String?
20 | scope String?
21 | id_token String?
22 | session_state String?
23 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
24 |
25 | @@unique([provider, providerAccountId])
26 | }
27 |
28 | model Session {
29 | id String @id @default(auto()) @map("_id") @db.ObjectId
30 | sessionToken String @unique
31 | userId String @db.ObjectId
32 | expires DateTime
33 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
34 | }
35 |
36 | model User {
37 | id String @id @default(auto()) @map("_id") @db.ObjectId
38 | name String?
39 | email String? @unique
40 | emailVerified DateTime?
41 | username String? @unique
42 | image String?
43 | onboardingCompleted Boolean? @default(false)
44 | location String?
45 | Bio String?
46 | accounts Account[]
47 | sessions Session[]
48 | Post Post[]
49 | like Like[]
50 | Comment Comment[]
51 | CommentVote CommentVote[]
52 | followers Follows[] @relation("following")
53 | following Follows[] @relation("follower")
54 | }
55 |
56 | model Post {
57 | id String @id @default(auto()) @map("_id") @db.ObjectId
58 | title String
59 | content Json?
60 | createdAt DateTime @default(now())
61 | updatedAt DateTime @updatedAt
62 | author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
63 | authorId String @db.ObjectId
64 | comments Comment[]
65 | like Like[]
66 | }
67 |
68 | model Like {
69 | id String @id @default(auto()) @map("_id") @db.ObjectId
70 | user User @relation(fields: [userId], references: [id])
71 | userId String @db.ObjectId
72 | post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
73 | postId String @db.ObjectId
74 | }
75 |
76 | model Comment {
77 | id String @id @default(auto()) @map("_id") @db.ObjectId
78 | text String
79 | createdAt DateTime @default(now())
80 | author User @relation(fields: [authorId], references: [id])
81 | authorId String @db.ObjectId
82 | post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
83 | postId String @db.ObjectId
84 |
85 | replyToId String? @db.ObjectId
86 | replyTo Comment? @relation("ReplyTo", fields: [replyToId], references: [id], onDelete: NoAction, onUpdate: NoAction)
87 | replies Comment[] @relation("ReplyTo")
88 |
89 | like CommentVote[]
90 | commentId String? @db.ObjectId
91 | }
92 |
93 | model CommentVote {
94 | id String @id @default(auto()) @map("_id") @db.ObjectId
95 |
96 | user User @relation(fields: [userId], references: [id])
97 | userId String @db.ObjectId
98 | comment Comment @relation(fields: [commentId], references: [id], onDelete: Cascade)
99 | commentId String @db.ObjectId
100 | }
101 |
102 | model Follows {
103 | id String @id @default(auto()) @map("_id") @db.ObjectId
104 | follower User @relation("follower", fields: [followerId], references: [id])
105 | followerId String @db.ObjectId
106 | following User @relation("following", fields: [followingId], references: [id])
107 | followingId String @db.ObjectId
108 |
109 | }
110 |
111 | // COMMANDS PRISMA
112 | // 1 . npx prisma db push
113 | // 2. npx prisma generate
114 |
--------------------------------------------------------------------------------
/public/companyaccount.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/cover_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taqui-786/project-friendz/fa1e3b9613ed12fcd9b795ea48d0ff217fb6b09b/public/cover_bg.png
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/publicaccount.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/(auth)/signin/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from "next"
2 |
3 | export const metadata: Metadata = {
4 | title: 'Signin | Signup',
5 | description: 'secure signin with next-auth',
6 | }
7 | export default function SigninLayout({
8 | children, // will be a page or nested layout
9 | }: {
10 | children: React.ReactNode
11 | }) {
12 | return (
13 |
14 |
15 |
16 | {children}
17 |
18 | )
19 | }
--------------------------------------------------------------------------------
/src/app/(auth)/signin/loading.tsx:
--------------------------------------------------------------------------------
1 | export default function Loading() {
2 | return (<>
3 |
4 |
5 |
6 | >)
7 | }
--------------------------------------------------------------------------------
/src/app/(auth)/signin/page.tsx:
--------------------------------------------------------------------------------
1 | import LoginGoogleBtn from "@/components/button/LoginGoogleBtn";
2 | import LoginButton from "@/components/button/LoginGoogleBtn";
3 | import { FaUserFriends } from "react-icons/fa";
4 | import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
5 | import { getAuthSession } from "@/lib/auth";
6 |
7 | async function page() {
8 | const session = await getAuthSession();
9 | return (
10 | <>
11 |
12 | {/* Hero Section */}
13 |
14 |
15 |
16 | Join an
Exciting Social
Experience.
17 |
18 |
19 |
20 | {/* middle section */}
21 |
22 |
27 |
28 |
29 | {/* Auth Section */}
30 |
31 |
32 |
33 |
34 | Sign in to your account
35 |
36 |
37 |
38 |
39 |
40 | More sign-in options will be available soon
41 |
42 |
43 |
44 |
45 |
46 | >
47 | );
48 | }
49 |
50 | export default page;
51 |
--------------------------------------------------------------------------------
/src/app/api/auth/[...nextauth]/route.ts:
--------------------------------------------------------------------------------
1 | import { authOptions } from "@/lib/auth"
2 | import NextAuth from "next-auth"
3 |
4 | const handler = NextAuth(authOptions)
5 |
6 | export { handler as GET, handler as POST }
--------------------------------------------------------------------------------
/src/app/api/link/route.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export async function GET(req: Request) {
4 | const url = new URL(req.url)
5 | const href = url.searchParams.get('url')
6 |
7 | if (!href) {
8 | return new Response('Invalid href', { status: 400 })
9 | }
10 |
11 | const res = await axios.get(href)
12 |
13 | // Parse the HTML using regular expressions
14 | const titleMatch = res.data.match(/(.*?)<\/title>/)
15 | const title = titleMatch ? titleMatch[1] : ''
16 |
17 | const descriptionMatch = res.data.match(
18 | /{
6 | const {searchParams} = new URL(req.url);
7 | var data = searchParams.get('value');
8 | if(data === '') return NextResponse.json({message:"error",status:201})
9 | try {
10 | const users = await db.user.findMany({
11 | where: {
12 | OR: [
13 | {
14 | username: {
15 | contains: data as string, // Search for users with names containing the searchTerm
16 | mode: 'insensitive'
17 | },
18 | },
19 | ],
20 | },
21 | take: 5
22 | })
23 | return NextResponse.json(users,{status:201})
24 | } catch (error) {
25 | return NextResponse.json({ message: 'Internal server error',error });
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/api/user/aboutme/route.ts:
--------------------------------------------------------------------------------
1 | import { LocationAndBioValidator } from '@/lib/NewUserFormValidator'
2 | import { db } from '@/lib/Prisma.db'
3 | import { getAuthSession } from '@/lib/auth'
4 | import { z } from 'zod'
5 |
6 | export async function PATCH(req: Request) {
7 | try {
8 | const session = await getAuthSession()
9 |
10 | if (!session?.user) {
11 | return new Response('Unauthorized', { status: 401 })
12 | }
13 |
14 | const body = await req.json()
15 | const { location , Bio } = LocationAndBioValidator.parse(body)
16 |
17 | // check if username is taken
18 | const usernameExist = await db.user.findFirst({
19 | where: {
20 | id: session.user.id,
21 | },
22 | })
23 |
24 | if (!usernameExist) {
25 | return new Response('User does not exist', { status: 401 })
26 | }
27 |
28 | // update username
29 | await db.user.update({
30 | where: {
31 | id: session.user.id,
32 | },
33 | data: {
34 | location: location,
35 | Bio: Bio,
36 | onboardingCompleted:true
37 | },
38 | })
39 |
40 | return new Response('OK')
41 | } catch (error) {
42 | (error)
43 |
44 | if (error instanceof z.ZodError) {
45 | return new Response(error.message, { status: 400 })
46 | }
47 |
48 | return new Response(
49 | 'Could not update username at this time. Please try later',
50 | { status: 500 }
51 | )
52 | }
53 | }
--------------------------------------------------------------------------------
/src/app/api/user/avatar/route.ts:
--------------------------------------------------------------------------------
1 | import { LocationAndBioValidator } from '@/lib/NewUserFormValidator'
2 | import { db } from '@/lib/Prisma.db'
3 | import { getAuthSession } from '@/lib/auth'
4 | import { z } from 'zod'
5 |
6 | export async function PATCH(req: Request) {
7 | try {
8 | const session = await getAuthSession()
9 |
10 | if (!session?.user) {
11 | return new Response('Unauthorized', { status: 401 })
12 | }
13 |
14 | const body = await req.json()
15 | // const { location , Bio } = LocationAndBioValidator.parse(body)
16 |
17 | // check if username is taken
18 | const usernameExist = await db.user.findFirst({
19 | where: {
20 | id: session.user.id,
21 | },
22 | })
23 |
24 | if (!usernameExist) {
25 | return new Response('User does not exist', { status: 401 })
26 | }
27 |
28 | // update username
29 | await db.user.update({
30 | where: {
31 | id: session.user.id,
32 | },
33 | data: {
34 | image: body.uploading.file.url
35 | },
36 | })
37 |
38 | return new Response('OK')
39 | } catch (error) {
40 | (error)
41 |
42 | if (error instanceof z.ZodError) {
43 | return new Response(error.message, { status: 400 })
44 | }
45 |
46 | return new Response(
47 | 'Could not update username at this time. Please try later',
48 | { status: 500 }
49 | )
50 | }
51 | }
--------------------------------------------------------------------------------
/src/app/api/user/follow/route.ts:
--------------------------------------------------------------------------------
1 | import { db } from "@/lib/Prisma.db";
2 | import { getAuthSession } from "@/lib/auth";
3 | import {
4 | FollowUserValidator,
5 | } from "@/types/PostLikeValidator";
6 | import { z } from "zod";
7 |
8 | export async function POST(req: Request) {
9 | try {
10 | const body = await req.json();
11 | const { toFollowId } = FollowUserValidator.parse(body);
12 | const session = await getAuthSession();
13 |
14 | if (!session?.user) {
15 | return new Response("Unauthorized", { status: 401 });
16 | }
17 | // IF THE FOLLOWING USER IS THE USER ONLY
18 | if (session.user.id === toFollowId) {
19 | return new Response("Action Prohibited !", { status: 40 });
20 | }
21 | // check if user has already voted on this post
22 | const existingUser = await db.follows.findFirst({
23 | where: {
24 | followingId: toFollowId,
25 | followerId: session.user.id,
26 | },
27 | });
28 |
29 | if (existingUser) {
30 | await db.follows.delete({
31 | where: {
32 | id: existingUser.id,
33 | },
34 | });
35 |
36 | return new Response("unfollowed");
37 | } else {
38 | await db.follows.create({
39 | data: {
40 | followerId: session.user.id,
41 | followingId: toFollowId,
42 | },
43 | });
44 |
45 | return new Response("followed");
46 | }
47 |
48 | return new Response("ok");
49 | } catch (error) {
50 | if (error instanceof z.ZodError) {
51 | return new Response(error.message, { status: 400 });
52 | }
53 |
54 | return new Response("Could not like.. Please try later" + error, {
55 | status: 500,
56 | });
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/app/api/user/post/comment/commentlike/route.ts:
--------------------------------------------------------------------------------
1 | import { db } from "@/lib/Prisma.db";
2 | import { getAuthSession } from "@/lib/auth";
3 | import { CommentVoteValidator } from "@/lib/commentValidator";
4 |
5 | import { z } from "zod";
6 |
7 | export async function POST(req: Request) {
8 | try {
9 | const body = await req.json();
10 | const { commentId } = CommentVoteValidator.parse(body);
11 | const session = await getAuthSession();
12 |
13 | if (!session?.user) {
14 | return new Response("Unauthorized", { status: 401 });
15 | }
16 | // check if user has already voted on this post
17 | const existingLike = await db.commentVote.findFirst({
18 | where: {
19 | userId: session.user.id,
20 | commentId,
21 | },
22 | });
23 |
24 | if (existingLike) {
25 | await db.commentVote.delete({
26 | where: {
27 | id: existingLike.id,
28 | },
29 | });
30 |
31 | return new Response("Deleted Comment like");
32 | } else {
33 | await db.commentVote.create({
34 | data: {
35 | userId: session.user.id,
36 | commentId,
37 | },
38 | });
39 | return new Response("Liked Comment");
40 | }
41 |
42 | return new Response("error");
43 | } catch (error) {
44 | if (error instanceof z.ZodError) {
45 | return new Response(error.message, { status: 400 });
46 | }
47 |
48 | return new Response("Could not like.. Please try later" + error, {
49 | status: 500,
50 | });
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/app/api/user/post/comment/route.ts:
--------------------------------------------------------------------------------
1 |
2 | import { db } from '@/lib/Prisma.db'
3 | import { getAuthSession } from '@/lib/auth'
4 | import { CommentValidator } from '@/lib/commentValidator'
5 | import { z } from 'zod'
6 |
7 | export async function PATCH(req: Request) {
8 | try {
9 | const body = await req.json()
10 |
11 | const { postId, text, replyToId } = CommentValidator.parse(body)
12 |
13 | const session = await getAuthSession()
14 |
15 | if (!session?.user) {
16 | return new Response('Unauthorized', { status: 401 })
17 | }
18 |
19 | // if no existing vote, create a new vote
20 | await db.comment.create({
21 | data: {
22 | text,
23 | postId,
24 | authorId: session.user.id,
25 | replyToId,
26 | },
27 | })
28 |
29 | return new Response('OK')
30 | } catch (error) {
31 | if (error instanceof z.ZodError) {
32 | return new Response(error.message, { status: 400 })
33 | }
34 |
35 | return new Response(
36 | 'Could not post to subreddit at this time. Please try later',
37 | { status: 500 }
38 | )
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/app/api/user/post/create/route.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | import { postValidator } from '@/components/CreatePostValidator'
8 | import { db } from '@/lib/Prisma.db'
9 | import { getAuthSession } from '@/lib/auth'
10 | import { z } from 'zod'
11 |
12 | export async function POST(req: Request) {
13 | try {
14 | const body = await req.json()
15 |
16 | const { title, content } = postValidator.parse(body)
17 |
18 | const session = await getAuthSession()
19 |
20 | if (!session?.user) {
21 | return new Response('Unauthorized', { status: 401 })
22 | }
23 |
24 |
25 |
26 | await db.post.create({
27 | data: {
28 | title,
29 | content,
30 | //@ts-ignore
31 | authorId: session.user.id,
32 | },
33 | })
34 |
35 | return new Response('OK')
36 | } catch (error) {
37 | if (error instanceof z.ZodError) {
38 | return new Response(error.message, { status: 400 })
39 | }
40 |
41 | return new Response(
42 | 'Could not post Error :'+error,
43 | { status: 500 }
44 | )
45 | }
46 | }
--------------------------------------------------------------------------------
/src/app/api/user/post/newlike/route.ts:
--------------------------------------------------------------------------------
1 | import { db } from "@/lib/Prisma.db";
2 | import { getAuthSession } from "@/lib/auth";
3 | import { PostVoteValidator } from "@/types/PostLikeValidator";
4 | import { z } from "zod";
5 |
6 | export async function POST(req: Request) {
7 | try {
8 | // Parse request and check auth in parallel
9 | const [body, session] = await Promise.all([
10 | req.json(),
11 | getAuthSession()
12 | ]);
13 |
14 | const { postId } = PostVoteValidator.parse(body);
15 |
16 | if (!session?.user) {
17 | return new Response("Unauthorized", { status: 401 });
18 | }
19 |
20 | // Use upsert with delete for atomic operation
21 | const result = await db.like.deleteMany({
22 | where: {
23 | userId: session.user.id,
24 | postId,
25 | },
26 | });
27 |
28 | if (result.count === 0) {
29 | // No like existed, so create one
30 | await db.like.create({
31 | data: {
32 | userId: session.user.id,
33 | postId,
34 | },
35 | });
36 | return new Response('Liked successfully', { status: 200 });
37 | }
38 |
39 | return new Response('Unliked successfully', { status: 200 });
40 |
41 | } catch (error) {
42 | if (error instanceof z.ZodError) {
43 | return new Response("Invalid request data", { status: 400 });
44 | }
45 |
46 | console.error('Like operation failed:', error);
47 | return new Response(
48 | "Operation failed. Please try again later",
49 | { status: 500 }
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/app/api/user/post/route.ts:
--------------------------------------------------------------------------------
1 | import { db } from '@/lib/Prisma.db'
2 | import { getAuthSession } from '@/lib/auth'
3 | import { z } from 'zod'
4 |
5 | export async function GET(req: Request) {
6 | const url = new URL(req.url)
7 |
8 | const session = await getAuthSession()
9 | if(!session) return new Response('Login first to see poat', { status: 401 })
10 |
11 |
12 | try {
13 | const { limit, page } = z
14 | .object({
15 | limit: z.string(),
16 | page: z.string(),
17 | })
18 | .parse({
19 | limit: url.searchParams.get('limit'),
20 | page: url.searchParams.get('page'),
21 | })
22 |
23 |
24 |
25 |
26 | const posts = await db.post.findMany({
27 | take: parseInt(limit),
28 | skip: (parseInt(page) - 1) * parseInt(limit), // skip should start from 0 for page 1
29 | orderBy: {
30 | createdAt: 'desc',
31 | },
32 | include: {
33 | like: true,
34 | author: true,
35 | comments:true
36 | },
37 | })
38 |
39 | return new Response(JSON.stringify(posts))
40 | } catch (error) {
41 | return new Response('Could not fetch posts', { status: 500 })
42 | }
43 | }
--------------------------------------------------------------------------------
/src/app/api/user/username/route.ts:
--------------------------------------------------------------------------------
1 | import { UsernameValidator } from '@/lib/NewUserFormValidator'
2 | import { db } from '@/lib/Prisma.db'
3 | import { getAuthSession } from '@/lib/auth'
4 | import { z } from 'zod'
5 |
6 | export async function PATCH(req: Request) {
7 | try {
8 | const session = await getAuthSession()
9 |
10 | if (!session?.user) {
11 | return new Response('Unauthorized', { status: 401 })
12 | }
13 |
14 | const body = await req.json()
15 | const { username } = UsernameValidator.parse(body)
16 |
17 | // check if username is taken
18 | const usernameExist = await db.user.findFirst({
19 | where: {
20 | username: username,
21 | },
22 | })
23 |
24 | if (usernameExist) {
25 | return new Response('Username is taken', { status: 409 })
26 | }
27 |
28 | // update username
29 | await db.user.update({
30 | where: {
31 | id: session.user.id,
32 | },
33 | data: {
34 | username: username,
35 | },
36 | })
37 |
38 | return new Response('OK')
39 | } catch (error) {
40 | (error)
41 |
42 | if (error instanceof z.ZodError) {
43 | return new Response(error.message, { status: 400 })
44 | }
45 |
46 | return new Response(
47 | 'Could not update username at this time. Please try later',
48 | { status: 500 }
49 | )
50 | }
51 | }
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taqui-786/project-friendz/fa1e3b9613ed12fcd9b795ea48d0ff217fb6b09b/src/app/favicon.ico
--------------------------------------------------------------------------------
/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 20 14.3% 4.1%;
9 |
10 | --muted: 60 4.8% 95.9%;
11 | --muted-foreground: 25 5.3% 44.7%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 20 14.3% 4.1%;
15 |
16 | --card: 0 0% 100%;
17 | --card-foreground: 20 14.3% 4.1%;
18 |
19 | --border: 20 5.9% 90%;
20 | --input: 20 5.9% 90%;
21 |
22 | --primary: 24 9.8% 10%;
23 | --primary-foreground: 60 9.1% 97.8%;
24 |
25 | --secondary: 60 4.8% 95.9%;
26 | --secondary-foreground: 24 9.8% 10%;
27 |
28 | --accent: 60 4.8% 95.9%;
29 | --accent-foreground: 24 9.8% 10%;
30 |
31 | --destructive: 0 84.2% 60.2%;
32 | --destructive-foreground: 60 9.1% 97.8%;
33 |
34 | --ring: 24 5.4% 63.9%;
35 |
36 | --radius: 0.5rem;
37 | }
38 |
39 | .dark {
40 | --background: 20 14.3% 4.1%;
41 | --foreground: 60 9.1% 97.8%;
42 |
43 | --muted: 12 6.5% 15.1%;
44 | --muted-foreground: 24 5.4% 63.9%;
45 |
46 | --popover: 20 14.3% 4.1%;
47 | --popover-foreground: 60 9.1% 97.8%;
48 |
49 | --card: 20 14.3% 4.1%;
50 | --card-foreground: 60 9.1% 97.8%;
51 |
52 | --border: 12 6.5% 15.1%;
53 | --input: 12 6.5% 15.1%;
54 |
55 | --primary: 60 9.1% 97.8%;
56 | --primary-foreground: 24 9.8% 10%;
57 |
58 | --secondary: 12 6.5% 15.1%;
59 | --secondary-foreground: 60 9.1% 97.8%;
60 |
61 | --accent: 12 6.5% 15.1%;
62 | --accent-foreground: 60 9.1% 97.8%;
63 |
64 | --destructive: 0 62.8% 30.6%;
65 | --destructive-foreground: 0 85.7% 97.3%;
66 |
67 | --ring: 12 6.5% 15.1%;
68 | }
69 | }
70 | body {
71 | background: #f4f4f4;
72 | height: 100%;
73 | width: 100%;
74 | min-height: 100vh;
75 | color: #344258;
76 | overflow: hidden;
77 | }
78 |
79 | /* /// CUSTOM STYLES // */
80 |
81 | /* SIGNIN */
82 | .signin_animate{
83 | animation: gradientShift 12s ease infinite;
84 |
85 | }
86 | .signin_text_shadow{
87 | text-shadow: 4px 4px #3180e1, 8px 8px #3180e1;
88 | }
89 |
90 | .slide_link_active{
91 | border-color: #3d70b2;
92 | font-weight: 500;
93 | color: #3d70b2;
94 | }
95 | .slide_link_active svg{
96 | color: #3d70b2;
97 | }
98 | .popupbox::before{
99 | border-bottom-color: #dcdcdc;
100 | border-width: 9px;
101 | left: 42px;
102 | margin-left: -9px;
103 | }
104 | .popupbox::before,.popupbox::after{
105 | position: absolute;
106 | pointer-events: none;
107 | border: solid transparent;
108 | bottom: 100%;
109 | content: "";
110 | height: 0;
111 | width: 0;
112 | }
113 | .popupbox::after {
114 | border-bottom-color: #fff;
115 | border-width: 8px;
116 | left: 42px;
117 | margin-left: -8px;
118 | }
119 | /* CUSTOM STYLES FOR NEWUSER PAGE ->> */
120 | .w-calc-100-min-24{
121 | width: calc(100% - 24px);
122 | }
123 | .h-calc-100-min-113{
124 | min-height: calc(100vh - 133px);
125 | }
126 | .custom_transition_width{
127 | transition: width 0.4s;
128 | }
129 | .activeDot{
130 | color: #039be5;
131 | border-color: #039be5;
132 | }
133 | .firstDot {
134 | left: -19px;
135 | color: #039be5;
136 | border-color: #039be5;
137 | }
138 | .secondDot {
139 | left: calc(25% - 19px);
140 | }
141 | .thirdDot {
142 | left: calc(50% - 19px);
143 | }
144 | .fourthDot {
145 | left: calc(75% - 19px);
146 | }
147 | .fifthDot {
148 | right: -19px;
149 | }
150 | /* HIDING SLIDE BAR CLASS AND MAKING HOME WIDTH FULL--> */
151 | .slidebar_hide{
152 | transform: translate(-100%);
153 | }
154 | .homepage_full{
155 | width: 100%;
156 | margin-left: 0;
157 | }
158 | .home_width{
159 | width: calc(100% - 280px);
160 | }
161 | /* SCROLLBAR HIDE */
162 | .hidescrollbar::-webkit-scrollbar{
163 | display: none;
164 | }
165 | /* LOADING ANIMATION */
166 | .loads{
167 | animation-duration: 1s;
168 | animation-fill-mode: forwards;
169 | animation-iteration-count: infinite;
170 | animation-name: placeload;
171 | animation-timing-function: linear;
172 | background: linear-gradient(to right,#eeeeee 8%,#dddddd 18%,#eeeeee 33%);
173 | background-size: 1200px 104px;
174 | position: relative;
175 | }
176 | /* CUSTOM PAGE LOADER FOR PROFILE AND POST */
177 | .loader {
178 | width: 48px;
179 | height: 48px;
180 | position: relative;
181 | }
182 | .loader::before , .loader::after{
183 | content: '';
184 | position: absolute;
185 | left: 50%;
186 | top: 50%;
187 | transform: translate(-50% , -50%);
188 | width: 48em;
189 | height: 48em;
190 | background-image:
191 | radial-gradient(circle 10px, #3180e1 100%, transparent 0),
192 | radial-gradient(circle 10px, #3180e1 100%, transparent 0),
193 | radial-gradient(circle 10px, #3180e1 100%, transparent 0),
194 | radial-gradient(circle 10px, #3180e1 100%, transparent 0),
195 | radial-gradient(circle 10px, #3180e1 100%, transparent 0),
196 | radial-gradient(circle 10px, #3180e1 100%, transparent 0),
197 | radial-gradient(circle 10px, #3180e1 100%, transparent 0),
198 | radial-gradient(circle 10px, #3180e1 100%, transparent 0);
199 | background-position: 0em -18em, 0em 18em, 18em 0em, -18em 0em,
200 | 13em -13em, -13em -13em, 13em 13em, -13em 13em;
201 | background-repeat: no-repeat;
202 | font-size: 0.5px;
203 | border-radius: 50%;
204 | animation: blast 1s ease-in infinite;
205 | }
206 | .loader::after {
207 | font-size: 1px;
208 | background: #3180e1;
209 | animation: bounce 1s ease-in infinite;
210 | }
211 |
212 | @keyframes bounce {
213 | 0% , 100%{ font-size: 0.75px }
214 | 50% { font-size: 1.5px }
215 | }
216 | @keyframes blast {
217 | 0% , 40% {
218 | font-size: 0.5px;
219 | }
220 | 70% {
221 | opacity: 1;
222 | font-size: 4px;
223 | }
224 | 100% {
225 | font-size: 6px;
226 | opacity: 0;
227 | }
228 | }
229 | @keyframes placeload {
230 | 0% {
231 | background-position: -468px 0;
232 | }
233 |
234 | 100% {
235 | background-position: 468px 0;
236 | }
237 | }
238 | @media (max-width:768px){
239 | .home_width{
240 | width: 100%;
241 | margin-left: 0;
242 | }
243 | .myslidebar{
244 | transform: translate(-100%)
245 | }
246 | }
247 | /* ---------------------------- */
248 | /* ------- */
249 | @layer base {
250 | * {
251 | @apply border-border;
252 | }
253 | body {
254 | @apply bg-background text-foreground;
255 | }
256 | }
257 |
258 | @layer base {
259 | * {
260 | @apply border-border outline-ring/50;
261 | }
262 | body {
263 | @apply bg-background text-foreground;
264 | }
265 | }
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import Slidebar from '@/components/slidebar/Slidebar'
2 | import './globals.css'
3 | import type { Metadata } from 'next'
4 | import { Inter } from 'next/font/google'
5 | import QuerryProvider from '@/components/QuerryProvider'
6 | import { Toaster } from 'sonner';
7 | const inter = Inter({ subsets: ['latin'] })
8 |
9 | export const metadata: Metadata = {
10 | title: 'Friendz',
11 | description: 'Generated by create next app',
12 | }
13 |
14 | export default function RootLayout({
15 | children,
16 | }: {
17 | children: React.ReactNode
18 | }) {
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 | {children}
27 |
28 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/src/app/loading.tsx:
--------------------------------------------------------------------------------
1 | export default function Loading() {
2 | return (
3 | <>
4 |
8 |
9 | {/* TOP NAV TOOLBAR */}
10 |
27 | {/* POST FEED */}
28 |
29 |
30 | {/* MIDDLE COLUMN --> */}
31 |
32 | {/* CREATE POST LOADER */}
33 |
45 | {/* FEED LOADER */}
46 |
47 |
51 | {/* HEADER */}
52 |
59 | {/* BODY */}
60 |
61 | {/* FOOTER */}
62 |
71 |
72 |
73 |
74 |
75 | {/* RIGHT COLUMN --> */}
76 |
77 |
78 |
81 |
82 |
89 |
96 |
103 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | >
118 | )
119 | }
--------------------------------------------------------------------------------
/src/app/newuser/layout.tsx:
--------------------------------------------------------------------------------
1 | export default function NewuserLayout({
2 | children, // will be a page or nested layout
3 | }: {
4 | children: React.ReactNode
5 | }) {
6 | return (
7 |
8 |
9 |
10 | {children}
11 |
12 | )
13 | }
--------------------------------------------------------------------------------
/src/app/newuser/loading.tsx:
--------------------------------------------------------------------------------
1 | export default function Loading() {
2 | return (<>
3 |
4 |
5 |
6 | >)
7 | }
--------------------------------------------------------------------------------
/src/app/newuser/page.tsx:
--------------------------------------------------------------------------------
1 | import ProgressBar from "@/components/newuserpage/ProgressBar";
2 | import Progresstitle from "@/components/newuserpage/Progresstitle";
3 | import { getAuthSession } from "@/lib/auth";
4 | import { sessionUserType } from "@/types/types";
5 | import dynamic from "next/dynamic";
6 | const Signup1 = dynamic(() => import("@/components/newuserpage/Signup1"));
7 | const Signup3 = dynamic(() => import("@/components/newuserpage/Signup3"), {
8 | ssr: false,
9 | });
10 | const Signup2 = dynamic(() => import("@/components/newuserpage/Signup2"), {
11 | ssr: false,
12 | });
13 | const Signup4 = dynamic(() => import("@/components/newuserpage/Signup4"), {
14 | ssr: false,
15 | });
16 | const Signup5 = dynamic(() => import("@/components/newuserpage/Signup5"), {
17 | ssr: false,
18 | });
19 |
20 | const page = async () => {
21 | const session = await getAuthSession();
22 |
23 |
24 | return (
25 | <>
26 |
27 | {/* FAKE NAVBAR */}
28 |
29 | {/* PROGRESSBAR */}
30 |
31 |
32 |
33 | {/* POST TITLE */}
34 |
37 | {/* SIGNUP STEP 1 _ PUBLIC - PRIVATE - COMPANY */}
38 |
42 |
43 |
44 | {/* SIGNUP STEP 2 _ NAME AND EMAIL */}
45 |
49 |
50 |
51 | {/* SIGNUP STEP 3 Password - OTP */}
52 |
56 |
57 |
58 | {/* SIGNUP STEP 4 PHOTO UPLOAD */}
59 |
63 |
64 |
65 | {/* SIGNUP FINAL STEP CONGRATZ */}
66 |
70 |
71 |
72 |
73 |
74 |
75 | >
76 | );
77 | };
78 |
79 | export default page;
80 |
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import CreatePostOuterBox from "@/components/CreatePostOuterBox"
2 | import FeedPage from "@/components/feed/FeedPage"
3 |
4 |
5 | export default async function Home() {
6 |
7 | return (
8 | <>
9 |
10 |
11 | {/* CREATE POST POPUP -> */}
12 |
13 |
14 | >
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/post/[postId]/page.tsx:
--------------------------------------------------------------------------------
1 | import CommentInput from "@/components/CommentInput";
2 | import CommentOuterBox from "@/components/CommentOuterBox";
3 | import { CommentInputBox } from "@/components/CustomComponents/CommentInputBox";
4 | import EditorOutput from "@/components/EditorOutput";
5 | import ExitProfileBtn from "@/components/button/ExitProfileBtn";
6 | import FollowButton from "@/components/button/FollowButton";
7 | import PostLikeServer from "@/components/button/PostLikeServerside";
8 | import { db } from "@/lib/Prisma.db";
9 | import { getAuthSession } from "@/lib/auth";
10 | import { Like, User, Post } from "@prisma/client";
11 | import { Loader2 } from "lucide-react";
12 | import Image from "next/image";
13 | import { notFound } from "next/navigation";
14 | import React, { Suspense } from "react";
15 | import { AiOutlineHeart } from "react-icons/ai";
16 | import { BiComment } from "react-icons/bi";
17 | import { format } from 'timeago.js'
18 | interface postpagerpops {
19 | params: {
20 | postId: string;
21 | };
22 | }
23 | export const dynamic = "force-dynamic";
24 | export const fetchCache = "force-no-store";
25 | const page = async ({ params }: postpagerpops) => {
26 | const session = await getAuthSession();
27 |
28 | const post = await db.post.findFirst({
29 | where: {
30 | id: params.postId,
31 | },
32 | include: {
33 | like: true,
34 | author: true
35 | },
36 | });
37 |
38 | const isFollowed = await db.user.findUnique({
39 | where:{
40 | id: post?.author.id
41 | },
42 | include:{
43 | followers:true
44 | }
45 | })
46 | let isUserFollowed = isFollowed?.followers.find(
47 | (val) => val.followerId === session?.user.id
48 | );
49 |
50 | if (!post) return notFound();
51 |
52 | return (
53 |
54 | {/* pageId: {params.postId} */}
55 | {/* left side */}
56 |
57 |
58 |
59 | {/* HEAD */}
60 |
61 | {/* BODY */}
62 |
63 |
64 |
65 |
{post.title}
66 |
67 |
68 |
69 |
70 | {/* FOOTER */}
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | {/* **** HEAD ---> */}
79 |
80 | {post.author.image && (
81 |
89 | )}
90 |
91 | {post.author.name}
92 |
93 | {format(post.createdAt)}
94 |
95 |
96 | {/* FOLLOW BUTTON */}
97 |
102 |
103 | {/* ----- Inner Content LIKE ANd Comment */}
104 |
105 |
106 | {/* LIKE SERVER SIDE */}
107 |
}>
108 |
{
111 | return await db.post.findUnique({
112 | where: {
113 | id: params.postId,
114 | },
115 | include: {
116 | like: true,
117 | comments: true,
118 | },
119 | });
120 | }}
121 | />
122 |
123 |
124 |
125 |
129 |
130 |
131 |
132 | Comment
133 |
134 |
135 |
136 |
137 | {/* COMMENT BOX -> */}
138 | {/*
*/}
139 |
142 | {/* Multiple skeleton comments for better UX */}
143 | {[1, 2, 3].map((i) => (
144 |
145 | {/* Avatar skeleton */}
146 |
147 |
148 | {/* Username and time skeleton */}
149 |
153 | {/* Comment text skeleton */}
154 |
158 |
159 |
160 | ))}
161 |
162 | }
163 | >
164 | {/* @ts-expect-error Server Component */}
165 |
166 |
167 |
168 |
169 | {/* EXIT BUTTON */}
170 |
171 |
172 |
173 |
174 |
175 |
176 | );
177 | };
178 |
179 | export default page;
180 |
181 | function MyLoader() {
182 | return (
183 | <>
184 |
185 | >
186 | );
187 | }
188 |
--------------------------------------------------------------------------------
/src/app/profile/[userId]/loading.tsx:
--------------------------------------------------------------------------------
1 | export default function Loading() {
2 | return (<>
3 |
4 |
5 |
6 | >)
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/profile/[userId]/page.tsx:
--------------------------------------------------------------------------------
1 | import TopPageNavbar from "@/components/TopPageNavbar";
2 | import Image from "next/image";
3 | import banner from "../../../../public/cover_bg.png";
4 | import { db } from "@/lib/Prisma.db";
5 | import { AiOutlinePlus } from "react-icons/ai";
6 |
7 | import { getAuthSession } from "@/lib/auth";
8 | import FollowButton from "@/components/button/FollowButton";
9 | import ProfilePostsColumn from "@/components/feed/ProfilePostsColumn";
10 | import { Suspense } from "react";
11 | import { Loader2 } from "lucide-react";
12 |
13 | import BasicInfoWidget from "@/components/BasicInfoWidget";
14 | import Link from "next/link";
15 |
16 |
17 | interface profilepageprops {
18 | params: {
19 | userId: string;
20 | };
21 | }
22 | const Profile = async ({ params }: profilepageprops) => {
23 | const session = await getAuthSession();
24 |
25 | let user = await db.user.findUnique({
26 | where: {
27 | id: params.userId,
28 | },
29 | include: {
30 | followers: true,
31 | following: true,
32 | Post: {
33 | include: {
34 | like: true,
35 | author: true,
36 | comments:true
37 | },
38 | // take: 2
39 | },
40 | },
41 | });
42 | let isUserFollowed;
43 | if (user) {
44 | isUserFollowed = user?.followers.find(
45 | (val) => val.followerId === session?.user.id
46 | );
47 | }
48 |
49 | return (
50 | <>
51 |
55 |
56 | {/* TOP NAV TOOLBAR */}
57 |
58 |
59 |
60 |
61 | {/* COVER IMAGE */}
62 |
63 |
72 |
73 | {user?.image && (
74 |
83 | )}
84 | {session?.user.id === user?.id && (
85 |
90 | )}
91 |
92 |
93 | {/* PROFILE FOLLOW BUTTON */}
94 |
95 |
96 |
97 |
98 |
99 | {user?.followers.length}
100 |
101 |
102 | FOLLOWERS
103 |
104 |
105 |
106 |
107 | {user?.following.length}
108 |
109 |
110 | FOLLOWINGS
111 |
112 |
113 |
114 |
115 | {user?.Post.length}
116 |
117 |
118 | POSTS
119 |
120 |
121 |
122 | {/* USER NAME */}
123 |
124 |
125 | {user?.username}
126 |
127 |
128 | {user?.name}
129 |
130 |
131 |
132 | {(session?.user.id !== user?.id) &&
133 |
138 | }
139 |
140 |
141 |
142 |
143 |
144 | {/* POST SIDE */}
145 |
146 | {/* BASIC INFO WIDGET -> */}
147 |
152 |
153 |
154 | {/* WIDGET HEADING */}
155 |
156 |
Posts
157 |
158 |
159 | {/* POSTS */}
160 |
163 | }
164 | >
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 | >
174 | );
175 | };
176 |
177 | export default Profile;
178 |
--------------------------------------------------------------------------------
/src/app/settings/loading.tsx:
--------------------------------------------------------------------------------
1 | export default function Loading() {
2 | return (<>
3 |
4 |
5 |
6 | >)
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/settings/page.tsx:
--------------------------------------------------------------------------------
1 | import Signup2 from "@/components/newuserpage/Signup2";
2 | import Signup3 from "@/components/newuserpage/Signup3";
3 | import Signup4 from "@/components/newuserpage/Signup4";
4 | import { getAuthSession } from "@/lib/auth";
5 | import { Metadata } from "next";
6 | export const metadata: Metadata = {
7 | title: 'Settings',
8 | description: 'user setting page ',
9 | }
10 | const page = async () => {
11 | const session = await getAuthSession()
12 | return (
13 | <>
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Settings
25 |
26 |
27 | This information will be displayed publicly so be careful
28 | what you changes.
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | >
51 | );
52 | };
53 |
54 | export default page;
55 |
--------------------------------------------------------------------------------
/src/components/BasicInfoWidget.tsx:
--------------------------------------------------------------------------------
1 | import { User } from "@prisma/client"
2 | import { FC } from "react"
3 | import {SiAboutdotme} from 'react-icons/si'
4 | import { MdNotificationsActive, MdLocationPin, MdAccountCircle, MdEmail } from "react-icons/md"
5 |
6 | type basicinfowidgetprops ={
7 | user:Pick
8 | follower: number
9 | }
10 |
11 |
12 | const BasicInfoWidget:FC = ({user , follower}) =>{
13 |
14 |
15 |
16 | return(
17 |
18 | {/* WIDGET HEADING */}
19 |
20 |
Basic Infos
21 |
22 | {/* WIDGET BODY */}
23 |
24 |
25 | {/* WIDGET ITEMS */}
26 |
27 |
28 |
29 | Account Type
30 |
31 |
32 | Public
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | Email
41 |
42 |
43 | {user.email}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | Lives In
52 |
53 |
54 | {user.location}
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Followers
63 |
64 |
65 | {follower} Followers
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | About Me
74 |
75 |
76 | {user.Bio}
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | )
85 | }
86 |
87 | export default BasicInfoWidget
--------------------------------------------------------------------------------
/src/components/CommentInput.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { FC, useState } from "react"
4 | import { Textarea } from "./ui/textarea"
5 | import { Button } from "./ui/button"
6 | import { useMutation } from "@tanstack/react-query"
7 | import axios, { AxiosError } from "axios"
8 | import { CommentRequest } from "@/lib/commentValidator"
9 | import { useRouter } from "next/navigation"
10 | interface CreateCommentProps {
11 | postId: string
12 | replyToId?: string
13 | }
14 | const CommentInput: FC = ({ postId, replyToId }) =>{
15 | const router = useRouter()
16 | const [input, setInput] = useState('')
17 | const { mutate: comment, isLoading } = useMutation({
18 | mutationFn: async ({ postId, text, replyToId }: CommentRequest) => {
19 | const payload: CommentRequest = { postId, text, replyToId }
20 |
21 | const { data } = await axios.patch(
22 | `/api/user/post/comment`,
23 | payload
24 | )
25 | return data
26 | },
27 |
28 | onError: (err) => {
29 | if (err instanceof AxiosError) {
30 | if (err.response?.status === 401) {
31 | // return loginToast()
32 | }
33 | }
34 | return window.alert('Something went wrong')
35 |
36 | },
37 | onSuccess: () => {
38 | router.refresh()
39 | setInput('')
40 | },
41 | })
42 |
43 | return(
44 |
45 | <>
46 |
47 |
48 | {/* INPUT box */}
49 |
50 |
60 |
61 |
68 |
69 |
70 |
71 |
72 | >
73 | )
74 | }
75 | export default CommentInput
--------------------------------------------------------------------------------
/src/components/CommentLike.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { FC, useEffect, useState } from "react";
3 | import { AiFillHeart } from "react-icons/ai";
4 | import { Button } from "./ui/button";
5 | import { useMutation, useQueryClient } from "@tanstack/react-query";
6 | import { CommentVoteRequest } from "@/lib/commentValidator";
7 | import axios, { AxiosError } from "axios";
8 | import { toast } from "sonner";
9 |
10 | interface CommmentLikeProps {
11 | commentId: string;
12 | votesAmt: number;
13 | currentVote?: any;
14 | }
15 |
16 | const CommmentLike: FC = ({
17 | commentId,
18 | votesAmt: initialVotesAmt,
19 | currentVote: initialVote,
20 | }) => {
21 | const [optimisticVotes, setOptimisticVotes] = useState(initialVotesAmt);
22 | const [optimisticLiked, setOptimisticLiked] = useState(!!initialVote);
23 | const queryClient = useQueryClient();
24 |
25 | const { mutate: like, isLoading } = useMutation({
26 | mutationFn: async () => {
27 | const payload: CommentVoteRequest = { commentId };
28 | return await axios.post('/api/user/post/comment/commentlike', payload);
29 | },
30 | mutationKey: ['comment-like', commentId],
31 | onError: (err) => {
32 | // Revert optimistic update
33 | setOptimisticVotes(initialVotesAmt);
34 | setOptimisticLiked(!!initialVote);
35 |
36 | if (err instanceof AxiosError) {
37 | if (err.response?.status === 401) {
38 | return toast.error('Please login to like comments');
39 | }
40 | }
41 | return toast.error('Failed to update like');
42 | },
43 | onMutate: () => {
44 | // Optimistic update
45 | setOptimisticLiked((current) => !current);
46 | setOptimisticVotes((prev) => prev + (optimisticLiked ? -1 : 1));
47 | },
48 | onSettled: () => {
49 | // Invalidate and refetch
50 | queryClient.invalidateQueries(['comments', commentId]);
51 | },
52 | });
53 |
54 | return (
55 | <>
56 |
57 |
70 |
{optimisticVotes}
71 |
72 | >
73 | );
74 | };
75 |
76 | export default CommmentLike;
77 |
--------------------------------------------------------------------------------
/src/components/CommentOuterBox.tsx:
--------------------------------------------------------------------------------
1 | import { db } from "@/lib/Prisma.db";
2 | import { CommentVote, User } from "@prisma/client";
3 | import { FC } from "react";
4 | import Comments from "./Comments";
5 | import { getAuthSession } from "@/lib/auth";
6 | import { BsArrowReturnRight } from "react-icons/bs";
7 | import { CommentInputBox } from "./CustomComponents/CommentInputBox";
8 |
9 | type ExtendedComment = Comment & {
10 | votes: CommentVote[];
11 | author: User;
12 | replies: ReplyComment[];
13 | };
14 |
15 | type ReplyComment = Comment & {
16 | votes: CommentVote[];
17 | author: User;
18 | };
19 |
20 | interface CommentOuterBoxProps {
21 | postId: string;
22 | comments: ExtendedComment[];
23 | }
24 | const CommentOuterBox = async ({ postId }: CommentOuterBoxProps) => {
25 | const session = await getAuthSession();
26 | const comments = await db.comment.findMany({
27 | where: {
28 | postId: postId,
29 | replyToId: undefined, // only fetch top-level comments
30 | },
31 | include: {
32 | author: true,
33 | like: true,
34 | replies: {
35 | // first level replies
36 | include: {
37 | author: true,
38 | like: true,
39 | },
40 | },
41 | },
42 | });
43 |
44 | return (
45 | <>
46 | {/* COMMENT OUTER BOX */}
47 |
51 | {/* COMMENTS */}
52 | {comments
53 | .filter((comment) => !comment.replyToId)
54 | .map((topLevelComment, index, array) => {
55 | // CHECKING THAT COMMENT IS LIKED OR NOT
56 | const isLiked = topLevelComment.like.find(
57 | (vote) => vote.userId === session?.user.id
58 | );
59 | // LIKES
60 | const topLevelCommentVotesAmt = topLevelComment?.like.length;
61 | return (
62 | <>
63 |
71 | {topLevelComment.replies
72 | .sort((a, b) => b.like.length - a.like.length) // Sort replies by most liked
73 | .map((reply) => {
74 | const replyVote = reply.like.find(
75 | (vote) => vote.userId === session?.user.id
76 | );
77 |
78 | const replyVoteAmt = reply?.like.length;
79 |
80 | return (
81 |
82 |
83 | {" "}
84 |
85 |
91 |
92 | );
93 | })}
94 | >
95 | );
96 | })}
97 |
98 | {/* COMMENT SENT INPUT BOX */}
99 |
100 | >
101 | );
102 | };
103 |
104 | export default CommentOuterBox;
105 |
--------------------------------------------------------------------------------
/src/components/Comments.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Comment, CommentVote, User } from "@prisma/client";
3 | import { useSession } from "next-auth/react";
4 | import Image from "next/image";
5 | import { FC, useRef, useState } from "react";
6 | import { Button } from "./ui/button";
7 | import CommmentLike from "./CommentLike";
8 | import { Textarea } from "./ui/textarea";
9 | import axios, { AxiosError } from "axios";
10 | import { CommentRequest } from "@/lib/commentValidator";
11 | import { useMutation } from "@tanstack/react-query";
12 | import { useRouter } from "next/navigation";
13 | import { format } from "timeago.js";
14 | import { toast } from "sonner";
15 | import { MessageCircleIcon } from "lucide-react";
16 | type ExtendedComment = Comment & {
17 | like: CommentVote[];
18 | author: User;
19 | replies?: Comment[];
20 | };
21 |
22 | interface PostCommentProps {
23 | comment: ExtendedComment;
24 | votesAmt: number;
25 | currentVote: CommentVote | undefined;
26 | postId: string;
27 | islastComment?: boolean;
28 | }
29 |
30 | const Comments: FC = ({
31 | comment,
32 | postId,
33 | currentVote,
34 | votesAmt,
35 | islastComment,
36 | }) => {
37 | // const { data: session } = useSession();
38 | const [isReplying, setIsReplying] = useState(false);
39 | const commentRef = useRef(null);
40 | const [input, setInput] = useState(`@${comment.author.name} `);
41 |
42 | // COMMENT REPLY FINCTION
43 | const router = useRouter();
44 | const { mutate: replyComment, isLoading } = useMutation({
45 | mutationFn: async ({ postId, text, replyToId }: CommentRequest) => {
46 | const payload: CommentRequest = { postId, text, replyToId };
47 |
48 | const { data } = await axios.patch(`/api/user/post/comment`, payload);
49 | return data;
50 | },
51 |
52 | onError: (err) => {
53 | if (err instanceof AxiosError) {
54 | if (err.response?.status === 401) {
55 | // return loginToast()
56 | }
57 | }
58 | return toast.error("Something went wrong");
59 | },
60 | onSuccess: () => {
61 | setIsReplying(false);
62 | router.refresh();
63 | setInput("");
64 | },
65 | });
66 |
67 | return (
68 |
69 |
70 |
71 |
72 | {comment.author.image && (
73 |
82 | )}
83 |
84 |
85 | {/* {!islastComment && (
86 |
87 | )} */}
88 |
89 |
90 |
91 | {comment.author.name}
92 |
93 |
94 | {format(comment.createdAt)}
95 |
96 |
97 |
98 | {comment.text}
99 |
100 |
101 |
106 |
115 |
116 |
117 |
118 | {isReplying ? (
119 |
120 |
121 | {/* INPUT box */}
122 |
123 |
138 |
139 |
153 |
160 |
161 |
162 |
163 | ) : null}
164 |
165 | );
166 | };
167 | export default Comments;
168 |
--------------------------------------------------------------------------------
/src/components/CreatePostOuterBox.tsx:
--------------------------------------------------------------------------------
1 | import { getAuthSession } from '@/lib/auth'
2 | import Image from 'next/image'
3 | import React from 'react'
4 | import CreatePostExitPost from './button/CreatePostExitPost'
5 | import Editor from './Editor'
6 | import { Button } from './ui/button'
7 | async function CreatePostOuterBox() {
8 | const session = await getAuthSession()
9 | return (
10 |
11 |
12 |
13 | {session?.user?.image &&
}
14 |
15 |
{session?.user.username}
16 | {session?.user.name}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | )
27 | }
28 |
29 | export default CreatePostOuterBox
--------------------------------------------------------------------------------
/src/components/CreatePostValidator.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | import {z} from 'zod'
7 |
8 |
9 | export const postValidator = z.object({
10 | title: z
11 | .string()
12 | .min(3,{message:'Title must be longer than 3 characters'})
13 | .max(60,{message:'Title not more than 60 characters'}),
14 | userId: z.string(),
15 | content: z.any(),
16 | })
17 | export type PostCreationRequest = z.infer
--------------------------------------------------------------------------------
/src/components/CustomComponents/CommentInputBox.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Button } from "@/components/ui/button";
4 | import { Textarea } from "@/components/ui/textarea";
5 | import { cn } from "@/lib/utils";
6 | import { useTextareaResize } from "./hooks";
7 | import { ArrowUpIcon, Send } from "lucide-react";
8 | import type React from "react";
9 | import { createContext, useContext, useState } from "react";
10 | import { RiLoader2Line } from "react-icons/ri";
11 | import { useMutation } from "@tanstack/react-query";
12 | import { CommentRequest } from "@/lib/commentValidator";
13 | import axios, { AxiosError } from "axios";
14 | import { useRouter } from "next/navigation";
15 | import { toast } from "sonner";
16 |
17 | interface ChatInputContextValue {
18 | value?: string;
19 | onChange?: React.ChangeEventHandler;
20 | onSubmit?: () => void;
21 | loading?: boolean;
22 | onStop?: () => void;
23 | variant?: "default" | "unstyled";
24 | rows?: number;
25 | }
26 |
27 | const ChatInputContext = createContext({});
28 |
29 | interface ChatInputProps extends Omit {
30 | children: React.ReactNode;
31 | className?: string;
32 | variant?: "default" | "unstyled";
33 | rows?: number;
34 | }
35 |
36 | function ChatInput({
37 | children,
38 | className,
39 | variant = "default",
40 | value,
41 | onChange,
42 | onSubmit,
43 | loading,
44 | onStop,
45 | rows = 1,
46 | }: ChatInputProps) {
47 | const contextValue: ChatInputContextValue = {
48 | value,
49 | onChange,
50 | onSubmit,
51 | loading,
52 | onStop,
53 | variant,
54 | rows,
55 | };
56 |
57 | return (
58 |
59 |
67 | {children}
68 |
69 |
70 | );
71 | }
72 |
73 | ChatInput.displayName = "ChatInput";
74 |
75 | interface ChatInputTextAreaProps extends React.ComponentProps {
76 | value?: string;
77 | onChange?: React.ChangeEventHandler;
78 | onSubmit?: () => void;
79 | variant?: "default" | "unstyled";
80 | }
81 |
82 | function ChatInputTextArea({
83 | onSubmit: onSubmitProp,
84 | value: valueProp,
85 | onChange: onChangeProp,
86 | className,
87 | variant: variantProp,
88 | ...props
89 | }: ChatInputTextAreaProps) {
90 | const context = useContext(ChatInputContext);
91 | const value = valueProp ?? context.value ?? "";
92 | const onChange = onChangeProp ?? context.onChange;
93 | const onSubmit = onSubmitProp ?? context.onSubmit;
94 | const rows = context.rows ?? 1;
95 |
96 | // Convert parent variant to textarea variant unless explicitly overridden
97 | const variant =
98 | variantProp ?? (context.variant === "default" ? "unstyled" : "default");
99 |
100 | const textareaRef = useTextareaResize(value, rows);
101 | const handleKeyDown = (e: React.KeyboardEvent) => {
102 | if (!onSubmit) {
103 | return;
104 | }
105 | if (e.key === "Enter" && !e.shiftKey) {
106 | if (typeof value !== "string" || value.trim().length === 0) {
107 | return;
108 | }
109 | e.preventDefault();
110 | onSubmit();
111 | }
112 | };
113 |
114 | return (
115 |
129 | );
130 | }
131 |
132 | ChatInputTextArea.displayName = "ChatInputTextArea";
133 |
134 | interface ChatInputSubmitProps extends React.ComponentProps {
135 | onSubmit?: () => void;
136 | loading?: boolean;
137 | onStop?: () => void;
138 | }
139 |
140 | function ChatInputSubmit({
141 | onSubmit: onSubmitProp,
142 | loading: loadingProp,
143 | onStop: onStopProp,
144 | className,
145 | ...props
146 | }: ChatInputSubmitProps) {
147 | const context = useContext(ChatInputContext);
148 | const loading = loadingProp ?? context.loading;
149 | const onStop = onStopProp ?? context.onStop;
150 | const onSubmit = onSubmitProp ?? context.onSubmit;
151 |
152 | if (loading ) {
153 | return (
154 |
164 | );
165 | }
166 |
167 | const isDisabled =
168 | typeof context.value !== "string" || context.value.trim().length === 0;
169 |
170 | return (
171 |
188 | );
189 | }
190 |
191 | ChatInputSubmit.displayName = "ChatInputSubmit";
192 |
193 | const CommentInputBox = ({ postId, replyToId }: { postId: string, replyToId?: string }) => {
194 | const router = useRouter()
195 | const [input, setInput] = useState('')
196 | const { mutate: comment, isLoading } = useMutation({
197 | mutationFn: async ({ postId, text, replyToId }: CommentRequest) => {
198 | const payload: CommentRequest = { postId, text, replyToId }
199 |
200 | const { data } = await axios.patch(
201 | `/api/user/post/comment`,
202 | payload
203 | )
204 | return data
205 | },
206 | mutationKey: ['comment'],
207 | onError: (err) => {
208 | if (err instanceof AxiosError) {
209 | if (err.response?.status === 401) {
210 | // return loginToast()
211 | }
212 | }
213 | return toast.error('Something went wrong')
214 |
215 | },
216 | onSuccess: () => {
217 | router.refresh()
218 | setInput('')
219 | },
220 | })
221 | return (
222 | setInput(e.target.value)}
225 | onSubmit={() => comment({ postId, text: input, replyToId })}
226 | loading={isLoading}
227 | className="bg-white"
228 | >
229 |
230 |
231 |
232 | );
233 | };
234 | export { ChatInput, ChatInputTextArea, ChatInputSubmit, CommentInputBox };
235 |
--------------------------------------------------------------------------------
/src/components/CustomComponents/hooks.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useLayoutEffect, useRef } from "react";
4 | import type { ComponentProps } from "react";
5 |
6 | export function useTextareaResize(
7 | value: ComponentProps<"textarea">["value"],
8 | rows = 1,
9 | ) {
10 | const textareaRef = useRef(null);
11 |
12 | // biome-ignore lint/correctness/useExhaustiveDependencies:
13 | useLayoutEffect(() => {
14 | const textArea = textareaRef.current;
15 |
16 | if (textArea) {
17 | // Get the line height to calculate minimum height based on rows
18 | const computedStyle = window.getComputedStyle(textArea);
19 | const lineHeight = Number.parseInt(computedStyle.lineHeight, 10) || 20;
20 | const padding =
21 | Number.parseInt(computedStyle.paddingTop, 10) +
22 | Number.parseInt(computedStyle.paddingBottom, 10);
23 |
24 | // Calculate minimum height based on rows
25 | const minHeight = lineHeight * rows + padding;
26 |
27 | // Reset height to auto first to get the correct scrollHeight
28 | textArea.style.height = "0px";
29 | const scrollHeight = Math.max(textArea.scrollHeight, minHeight);
30 |
31 | // Set the final height
32 | textArea.style.height = `${scrollHeight + 2}px`;
33 | }
34 | }, [textareaRef, value, rows]);
35 |
36 | return textareaRef;
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/Editor.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { FC, useCallback, useEffect, useRef, useState } from "react";
3 | import { useForm } from "react-hook-form";
4 | import ReactTextareaAutosize from "react-textarea-autosize";
5 | import { zodResolver } from "@hookform/resolvers/zod";
6 | import EditorJS from "@editorjs/editorjs";
7 | import { useMutation } from "@tanstack/react-query";
8 | import axios from "axios";
9 | import { handleImageUpload } from "@/lib/Functions";
10 | import { PostCreationRequest, postValidator } from "./CreatePostValidator";
11 | import { Loader2 } from "lucide-react";
12 | import { exitPopup } from "./button/CreatePostExitPost";
13 | import { useRouter } from "next/navigation";
14 | import toast from "react-hot-toast";
15 |
16 | interface editorProp {
17 | userId: any;
18 | }
19 |
20 | const Editor: FC = ({ userId }) => {
21 |
22 | const {
23 | register,
24 | handleSubmit,
25 | formState: { errors },
26 | } = useForm({
27 | resolver: zodResolver(postValidator),
28 | defaultValues: {
29 | userId,
30 | title: "",
31 | content: null,
32 | },
33 | });
34 | const router = useRouter()
35 | const ref = useRef();
36 | const [isMounted, setIsMounted] = useState(false);
37 | const _titleRef = useRef(null);
38 | useEffect(() => {
39 | if (typeof window !== "undefined") {
40 | setIsMounted(true);
41 | }
42 |
43 | }, []);
44 |
45 | // EDITOR INITALISATION FUNCTION --- >
46 | const initializeEditor = useCallback(async () => {
47 | const EditorJS = (await import("@editorjs/editorjs")).default;
48 | // @ts-ignore
49 | const Header = (await import("@editorjs/header")).default;
50 | // @ts-ignore
51 | const Embed = (await import("@editorjs/embed")).default;
52 | // @ts-ignore
53 | const Table = (await import("@editorjs/table")).default;
54 | // @ts-ignore
55 | const List = (await import("@editorjs/list")).default;
56 | // @ts-ignore
57 | const Code = (await import("@editorjs/code")).default;
58 | // @ts-ignore
59 | const LinkTool = (await import("@editorjs/link")).default;
60 | // @ts-ignore
61 | const InlineCode = (await import("@editorjs/inline-code")).default;
62 | // @ts-ignore
63 | const ImageTool = (await import("@editorjs/image")).default;
64 |
65 | if (!ref.current) {
66 | const editor = new EditorJS({
67 | holder: "editor",
68 | onReady() {
69 | ref.current = editor;
70 | },
71 | placeholder: "Type here to write your post...",
72 | inlineToolbar: true,
73 | data: { blocks: [] },
74 | tools: {
75 | header: Header,
76 | linkTool: {
77 | class: LinkTool,
78 | config: {
79 | endpoint: "/api/link",
80 | },
81 | },
82 | image: {
83 | class: ImageTool,
84 | config: {
85 | uploader: {
86 | uploadByFile: handleImageUpload,
87 | },
88 | },
89 | },
90 | list: List,
91 | code: Code,
92 | inlineCode: InlineCode,
93 | embed: Embed,
94 | table: Table,
95 | },
96 | });
97 | }
98 | }, []);
99 |
100 | // INITIALIZING
101 | useEffect(() => {
102 | const init = async () => {
103 | await initializeEditor();
104 |
105 | setTimeout(() => {
106 | _titleRef?.current?.focus();
107 | }, 0);
108 | };
109 |
110 | if (isMounted) {
111 | init();
112 |
113 | return () => {
114 | ref.current?.destroy();
115 | ref.current = undefined;
116 | };
117 | }
118 | }, [isMounted, initializeEditor]);
119 |
120 | // ERROR HANDLING OF EDITOR
121 | useEffect(() => {
122 | if (Object.keys(errors).length) {
123 | for (const [_key, value] of Object.entries(errors)) {
124 | toast.error((value as { message: string }).message);
125 | }
126 | }
127 | }, [errors]);
128 |
129 | const { mutate: createPost, isLoading } = useMutation({
130 | mutationFn: async ({ title, content, userId }: PostCreationRequest) => {
131 | const payload: PostCreationRequest = { title, content, userId };
132 | const { data } = await axios.post("/api/user/post/create", payload);
133 | return data;
134 | },
135 | onError: () => {
136 | toast.error("Post submit Error");
137 | },
138 | onSuccess: () => {
139 | // A WAY TO HANDLE CREATE POST AND REDIRECT IT
140 | router.refresh()
141 | ref.current?.blocks.clear();
142 | toast.success("Successfull Posted")
143 | exitPopup();
144 | },
145 | });
146 |
147 | // EDITOR ON SUBMIT
148 | async function onSubmit(data: PostCreationRequest) {
149 | const blocks = await ref.current?.save();
150 |
151 | const payload: PostCreationRequest = {
152 | title: data.title,
153 | content: blocks,
154 | userId,
155 | };
156 |
157 | createPost(payload);
158 | }
159 |
160 | if (!isMounted) {
161 | return null;
162 | }
163 | // CUSTOM REF SETTING
164 | const { ref: titleRef, ...rest } = register("title");
165 |
166 | return (
167 |
168 | {/* On SUBMIT LOADING */}
169 | {isLoading && (
170 |
171 |
172 |
173 | )}
174 |
195 |
196 | );
197 | };
198 |
199 | export default Editor;
200 |
--------------------------------------------------------------------------------
/src/components/EditorOutput.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { FC } from "react";
4 | import dynamic from "next/dynamic";
5 | import CustomCodeRenderer from "./renderers/CustomCodeRender";
6 | import CustomImageRenderer from "./renderers/CustomImageRender";
7 | import PostLoader from "./loaders/PostLoader";
8 | import CustomListRenderer from "./renderers/CustomListRenderer";
9 |
10 | const Output = dynamic(
11 | async () => (await import("editorjs-react-renderer")).default,
12 | { ssr: false, loading: () => }
13 | );
14 |
15 | interface EditorOutputProps {
16 | content: any;
17 | }
18 |
19 | const renderers = {
20 | image: CustomImageRenderer,
21 | code: CustomCodeRenderer,
22 | list: CustomListRenderer,
23 | };
24 |
25 | const style = {
26 | paragraph: {
27 | fontSize: "0.875rem",
28 | lineHeight: "1.25rem",
29 | },
30 | };
31 |
32 | const EditorOutput: FC = ({ content }) => {
33 | return (
34 |
40 | );
41 | };
42 |
43 | export default EditorOutput;
44 |
--------------------------------------------------------------------------------
/src/components/QuerryProvider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
4 | import { SessionProvider } from "next-auth/react";
5 | import { FC, ReactNode, useState } from "react";
6 | import { Toaster } from "react-hot-toast";
7 | interface LayoutProps {
8 | children: ReactNode;
9 | }
10 |
11 | const QuerryProvider: FC = ({ children }) => {
12 | const [queryClient] = useState(() => new QueryClient());
13 | return (
14 |
15 |
16 | {children}
17 |
18 | );
19 | };
20 |
21 | export default QuerryProvider;
22 |
--------------------------------------------------------------------------------
/src/components/SearchUserInput.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState, useCallback } from "react";
3 | import { FiSearch } from "react-icons/fi";
4 | import {
5 | Dialog,
6 | DialogContent,
7 | DialogHeader,
8 | DialogTitle,
9 | } from "@/components/ui/dialog";
10 | import { Input } from "@/components/ui/input";
11 | import axios from "axios";
12 | import { Loader2 } from "lucide-react";
13 | import debounce from "lodash/debounce";
14 | import { useSearch } from "@/lib/Apis";
15 |
16 | function SearchUserInput() {
17 | const [open, setOpen] = useState(false);
18 | const [searchQuery, setSearchQuery] = useState("");
19 | const [isTyping, setIsTyping] = useState(false);
20 |
21 | const { data: users = [], error, isFetching } = useSearch(searchQuery);
22 |
23 | const handleSearch = (value: string) => {
24 | setSearchQuery(value);
25 | };
26 |
27 | return (
28 | <>
29 |
30 |
31 |
32 |
33 |
34 |
setOpen(true)}
39 | className="w-full h-11 px-10 text-base rounded-lg outline-none border bg-colorF7 focus:bg-white focus:border-[#3180e1] cursor-pointer placeholder:text-muted-foreground/70"
40 | />
41 |
42 |
43 |
44 |
117 | >
118 | );
119 | }
120 |
121 | export default SearchUserInput;
122 |
--------------------------------------------------------------------------------
/src/components/TopPageNavbar.tsx:
--------------------------------------------------------------------------------
1 | import { AiFillGithub } from "react-icons/ai"
2 | import { SlideBarOuterButton } from "./button/SlideBarResponsiveExitButton"
3 | import { TiSocialLinkedin, TiSocialTwitter } from "react-icons/ti"
4 | import Image from "next/image"
5 | import { getAuthSession } from "@/lib/auth"
6 | import { FC } from "react"
7 | import Link from "next/link"
8 |
9 | interface TopPageNavbarprop{
10 | title: string
11 | }
12 |
13 | const TopPageNavbar:FC = async ({title}) =>{
14 |
15 | const session = await getAuthSession()
16 |
17 |
18 |
19 | return(
20 |
21 | {/* ARROW ICON */}
22 |
23 |
{title} Page
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
44 | {/* profile opition --> */}
45 |
46 |
47 |
48 | {session?.user?.image && }
49 |
50 |
51 |
52 |
53 |
54 | )
55 | }
56 |
57 | export default TopPageNavbar
--------------------------------------------------------------------------------
/src/components/button/CommentButton.tsx:
--------------------------------------------------------------------------------
1 | import { BiSolidComment } from "react-icons/bi";
2 | import { FC } from "react";
3 | import Link from "next/link";
4 | interface commentButtonProps{
5 | postId:string
6 | }
7 | const CommentButton:FC = ({postId}) => {
8 | return (
9 | <>
10 |
14 |
15 |
16 | >
17 | );
18 | };
19 |
20 | export default CommentButton;
21 |
--------------------------------------------------------------------------------
/src/components/button/CreatePostExitPost.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { RxCross1 } from 'react-icons/rx'
3 | export const exitPopup = () =>{
4 | const createPost = document.getElementById('createPost')
5 | createPost? createPost.style.display = "none" : ""
6 |
7 | }
8 | function CreatePostExitPost() {
9 |
10 | return (
11 | exitPopup()} className='absolute px-3 right-0'>
13 | )
14 | }
15 |
16 | export default CreatePostExitPost
--------------------------------------------------------------------------------
/src/components/button/ExitProfileBtn.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { ImCross } from 'react-icons/im'
3 | import { Button } from '../ui/button'
4 | import { useRouter } from 'next/navigation'
5 |
6 | function ExitProfileBtn() {
7 | const router = useRouter()
8 |
9 | return (
10 |
11 | )
12 | }
13 |
14 | export default ExitProfileBtn
--------------------------------------------------------------------------------
/src/components/button/FollowButton.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { FC, useEffect, useState } from "react";
3 | import { Button } from "../ui/button";
4 | import { AiOutlinePlus } from "react-icons/ai";
5 | import axios, { AxiosError } from "axios";
6 | import { FollowUserRequest } from "@/types/PostLikeValidator";
7 | import { useMutation } from "@tanstack/react-query";
8 | import toast from "react-hot-toast";
9 |
10 |
11 | interface FollowButtonprops {
12 | myId?: string;
13 | toFollow?: any;
14 | isFollowed?: any;
15 | }
16 |
17 | const FollowButton: FC = ({
18 | myId,
19 | toFollow,
20 | isFollowed,
21 | }) => {
22 | const [isFollow, setIsFollow] = useState(false);
23 |
24 | useEffect(() => {
25 | if (isFollowed) {
26 | setIsFollow(true);
27 | } else {
28 | setIsFollow(false);
29 | }
30 | }, [isFollowed]);
31 |
32 | const { mutate: follow, isLoading } = useMutation({
33 | mutationFn: async () => {
34 | const payload: FollowUserRequest = {
35 | toFollowId: toFollow,
36 | };
37 | await axios.post("/api/user/follow", payload);
38 | },
39 | mutationKey: ['follow',toFollow],
40 | onError: (err) => {
41 | setIsFollow(false);
42 | if (err instanceof AxiosError) {
43 | if (err.response?.status === 401) {
44 | return toast.error(" Login First. ");
45 | }
46 | }
47 |
48 | return toast.error(" Unable to Follow ");
49 | },
50 | onMutate: () => {
51 | if (isFollow) {
52 | setIsFollow(false);
53 | } else {
54 | setIsFollow(true);
55 | }
56 | },
57 | });
58 |
59 | return (
60 |
71 | );
72 | };
73 |
74 | export default FollowButton;
75 |
--------------------------------------------------------------------------------
/src/components/button/LogOutButton.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { signOut } from "next-auth/react"
3 | import { FiLogOut } from "react-icons/fi"
4 |
5 |
6 | const LogOutButton = () =>{
7 |
8 | return(
9 | signOut({callbackUrl:`${window.location.origin}/signin`})}
11 | className='flex text-[#393a4f] items-center py-3 px-8 border-l-[5px] border-l-transparent cursor-pointer ' >
12 |
13 | Logout
14 |
15 | )
16 | }
17 |
18 | export default LogOutButton
--------------------------------------------------------------------------------
/src/components/button/LoginGoogleBtn.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { FC, useState } from "react";
3 | import { Button } from "../ui/button";
4 | import { FcGoogle } from "react-icons/fc";
5 | import { signIn } from "next-auth/react";
6 | import toast from "react-hot-toast";
7 |
8 | const LoginGoogleBtn: FC = () => {
9 | const [loading, setLoading] = useState(false);
10 | const loginwithGoogle = async () => {
11 | setLoading(true);
12 | try {
13 | await signIn("google");
14 | } catch (error) {
15 | console.log(error);
16 | } finally {
17 | // LOADING OR ANY OTHER STUF
18 | setLoading(false);
19 | toast.success(" Good to go. ");
20 | }
21 | };
22 |
23 | return (
24 | <>
25 |
26 |
27 |
54 | >
55 | );
56 | };
57 |
58 | export default LoginGoogleBtn;
59 |
--------------------------------------------------------------------------------
/src/components/button/NewUserButton.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { HandleNext } from "@/lib/Functions";
3 | import { Button } from "../ui/button";
4 | import { BsArrowLeft, BsArrowRight } from "react-icons/bs";
5 | import { usePathname } from "next/navigation";
6 |
7 | export const AccountTypeSubmitBtn = (props: { value: number }) => {
8 | return (
9 |
17 | );
18 | };
19 |
20 | export const NewUserNextStepBtn = (props: {next: number , prev:number , disable:boolean}) =>{
21 |
22 | const params = usePathname()
23 |
24 |
25 | return(
26 |
27 |
31 |
35 |
36 | )
37 | }
--------------------------------------------------------------------------------
/src/components/button/PostLikeBtn.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { usePrevious } from '@mantine/hooks'
3 | import { useMutation } from '@tanstack/react-query'
4 | import React,{FC, useEffect, useState} from 'react'
5 | import { AiFillHeart, AiOutlineHeart } from 'react-icons/ai'
6 | import {PostVoteRequest} from '@/types/PostLikeValidator'
7 | import axios, { AxiosError } from 'axios'
8 | import { Button } from '../ui/button'
9 | import toast from 'react-hot-toast'
10 | import { Loader } from 'lucide-react'
11 |
12 | interface postlikebtnprops{
13 | postData: any
14 | isLiked?: any
15 | initialLikeAmt:any
16 | }
17 |
18 | const PostLikeBtn:FC = ({postData,isLiked,initialLikeAmt}) => {
19 | const postId = postData?.id
20 |
21 | const prevLike = usePrevious(initialLikeAmt)
22 | const [votesAmt, setVotesAmt] = useState(initialLikeAmt)
23 | const [liked,setLiked] = useState(false)
24 | // CHECKING USER HAD LIKED POST OR NOT
25 | useEffect(()=>{
26 | if(!isLiked){
27 | setLiked(false)
28 | }else{
29 | setLiked(true)
30 | }
31 | },[isLiked])
32 |
33 | useEffect(()=>{
34 | setVotesAmt(initialLikeAmt)
35 | },[initialLikeAmt])
36 |
37 | const {mutate: like , isLoading} = useMutation({
38 |
39 | mutationFn: async () => {
40 | const payload: PostVoteRequest ={
41 | postId: postId
42 | }
43 | await axios.post('/api/user/post/newlike',payload)
44 |
45 | },
46 | mutationKey: ['like',postId],
47 | onError: (err) => {
48 | setVotesAmt(prevLike)
49 | if (err instanceof AxiosError) {
50 | if (err.response?.status === 401) {
51 | return toast.error(" Login First. ");
52 |
53 | }
54 | }
55 |
56 | return toast.error(" Unable to Like ");
57 | },
58 | onMutate: () =>{
59 | if( !liked){
60 | setLiked(true)
61 | setVotesAmt((prev) => prev + 1)
62 | }else{
63 | setLiked(false)
64 | setVotesAmt((prev) => prev - 1)
65 | }
66 | },
67 |
68 |
69 | })
70 |
71 |
72 | return (
73 | <>
74 |
75 | {/* Like count */}
76 |
77 |
81 |
82 |
83 |
84 |
{votesAmt}
85 |
86 |
87 | >
88 | )
89 | }
90 |
91 | export default PostLikeBtn
--------------------------------------------------------------------------------
/src/components/button/PostLikeServerside.tsx:
--------------------------------------------------------------------------------
1 | import { getAuthSession } from "@/lib/auth";
2 | import { Comment, Like, Post } from "@prisma/client";
3 | import { notFound } from "next/navigation";
4 | import { AiOutlineHeart } from "react-icons/ai";
5 | import { BiComment } from "react-icons/bi";
6 |
7 | interface PostVoteServerProps {
8 | postId: string;
9 | initialVotesAmt?: number;
10 | initialVote?: any;
11 | getData?: () => Promise<(Post & { like: Like[] } & { comments: Comment[] }) | null>;
12 | }
13 |
14 | const PostLikeServer = async ({
15 | postId,
16 | initialVotesAmt,
17 | initialVote,
18 | getData,
19 | }: PostVoteServerProps) => {
20 | const session = await getAuthSession();
21 |
22 | let _votesAmt: number = 0;
23 | let _commentAmt: number = 0;
24 | let _currentVote: Like[];
25 |
26 | if (getData) {
27 | // fetch data in component
28 | const post = await getData();
29 | if (!post) return notFound();
30 |
31 |
32 | _votesAmt = post.like.length;
33 | _commentAmt = post.comments.length;
34 |
35 | // @ts-ignore
36 | _currentVote = post.like.find((vote) => vote.userId === session?.user?.id);
37 | } else {
38 | // passed as props
39 | _votesAmt = initialVotesAmt!;
40 | _currentVote = initialVote;
41 | }
42 |
43 | return (
44 | <>
45 |
46 |
47 |
48 |
{_votesAmt}
49 |
50 |
51 |
52 |
53 | {_commentAmt}
54 |
55 |
56 |
57 |
58 | {_commentAmt}
59 |
60 | Comments
61 |
62 |
63 |
64 | >
65 | );
66 | };
67 |
68 | export default PostLikeServer;
69 |
--------------------------------------------------------------------------------
/src/components/button/SettingBtn.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import Link from "next/link";
3 | import { usePathname } from "next/navigation";
4 | import { AiOutlineSetting } from "react-icons/ai";
5 |
6 | const SettingBtn = () => {
7 | const router = usePathname()
8 | return (
9 |
14 |
15 | Settings
16 |
17 | );
18 | };
19 |
20 | export default SettingBtn;
21 |
--------------------------------------------------------------------------------
/src/components/button/SlideBarResponsiveExitButton.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { useState } from 'react'
3 | import { AiOutlineArrowLeft } from 'react-icons/ai'
4 | import { IoIosArrowBack } from 'react-icons/io'
5 | import { RiMenu2Fill } from 'react-icons/ri'
6 |
7 | export const SlideBarInnerButton = () => {
8 | const handleResponsiveButton =() =>{
9 | const slidebar = document.getElementById('slidebar')
10 | slidebar ? slidebar.style.transform = 'translate(-100%)' : ""
11 | }
12 |
13 | return (
14 |
15 |
16 | )
17 | }
18 | export const SlideBarOuterButton = () => {
19 | const [open,setOpen] = useState(false)
20 | const hideSlidebar = () =>{
21 | const slidebar = document.getElementById('slidebar')
22 | const homePage = document.getElementById('homePage')
23 | if(!open){
24 | slidebar?.classList.add('slidebar_hide')
25 | homePage?.classList.replace('home_width','homepage_full')
26 | setOpen(true)
27 | }else{
28 | slidebar?.classList.remove('slidebar_hide')
29 | homePage?.classList.replace('homepage_full','home_width')
30 | setOpen(false)
31 | }
32 | }
33 | const handleResponsiveButton =() =>{
34 | const slidebar = document.getElementById('slidebar')
35 | slidebar ? slidebar.style.transform = 'translate(0)' : ""
36 | }
37 |
38 | return (
39 |
40 | {!open && }
41 | {open && }
42 |
43 |
44 |
45 | )
46 | }
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/components/feed/CreatePostActivator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import Image from 'next/image'
4 | import React,{FC} from 'react'
5 | interface CreatePostActivatorprops{
6 | image:string
7 | }
8 |
9 | const CreatePostActivator:FC = ({image}) => {
10 |
11 | const popupCreatePost = () =>{
12 | const createPost = document.getElementById('createPost')
13 | createPost? createPost.style.display = "flex" : ""
14 | }
15 |
16 |
17 | return (
18 |
20 |
21 |
22 |
23 | { image &&
}
24 |
25 |
Write Something here to create your post...
26 |
27 |
28 |
29 |
30 |
31 | )
32 | }
33 |
34 | export default CreatePostActivator
--------------------------------------------------------------------------------
/src/components/feed/FeedColumn.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { useInfiniteQuery } from "@tanstack/react-query"
3 | import axios from "axios"
4 | import { useSession } from "next-auth/react"
5 | import { FC, useEffect, useRef } from "react"
6 | import MyPost from "./MyPost"
7 | import { useIntersection } from '@mantine/hooks'
8 | import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config"
9 | import { ExtendedPost } from "@/types/db"
10 | import { Loader2 } from "lucide-react"
11 |
12 | interface postfeedprops {
13 | initialPosts: ExtendedPost[]
14 | }
15 |
16 |
17 | const FeedColumn: FC = ({ initialPosts }) => {
18 |
19 | const lastPostRef = useRef(null)
20 | const { ref, entry } = useIntersection({
21 | root: lastPostRef.current,
22 | threshold: 0.5,
23 | })
24 | const { data: session } = useSession()
25 |
26 | const { data, fetchNextPage, isFetchingNextPage } = useInfiniteQuery(
27 | ['infinite-query'],
28 | async ({ pageParam = 1 }) => {
29 | const query =
30 | `/api/user/post?limit=${INFINITE_SCROLL_PAGINATION_RESULTS}&page=${pageParam}`
31 |
32 | const { data } = await axios.get(query)
33 | return data as ExtendedPost[]
34 | },
35 |
36 | {
37 | getNextPageParam: (_, pages) => {
38 | return pages.length + 1
39 | },
40 | initialData: { pages: [initialPosts], pageParams: [1] },
41 | }
42 | )
43 |
44 | useEffect(() => {
45 | if (entry?.isIntersecting) {
46 | fetchNextPage() // Load more posts when the last post comes into view
47 | }
48 | }, [entry, fetchNextPage])
49 |
50 | const posts = data?.pages.flatMap((page) => page) ?? initialPosts
51 |
52 | // console.log(isFetchingNextPage);
53 |
54 | return (
55 |
56 | <>
57 |
58 |
59 | {
60 | posts.map((post, index) => {
61 |
62 | // CHECKING USER HAD LIKED THIS POST OR NOT
63 | const isLiked = post?.like?.find(
64 | (vote) => vote.userId === session?.user.id
65 | )
66 | // LIKES AMOUNT
67 | const LikeAmount = post?.like.length
68 | // COMMENTS COUNT
69 | const commentLength = post?.comments.length
70 |
71 | if (index === posts.length - 1) {
72 | // Add a ref to the last post in the list
73 | return (
74 | -
77 |
84 |
85 | )
86 | } else {
87 | return
94 | }
95 | })
96 | }
97 | {isFetchingNextPage && (
98 | -
99 |
100 |
101 | )}
102 |
103 | >
104 |
105 |
106 |
107 |
108 |
109 | )
110 | }
111 |
112 |
113 |
114 |
115 |
116 | export default FeedColumn
--------------------------------------------------------------------------------
/src/components/feed/FeedPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import FeedPostsBox from './FeedPostsBox'
3 | import TopPageNavbar from '../TopPageNavbar'
4 |
5 | async function FeedPage() {
6 | return (
7 | <>
8 |
9 |
10 | {/* TOP NAV TOOLBAR */}
11 |
12 | {/* POST FEED */}
13 |
14 |
15 | {/* ACTIVITY FEED -> */}
16 |
17 |
18 | >
19 |
20 | )
21 | }
22 |
23 | export default FeedPage
--------------------------------------------------------------------------------
/src/components/feed/FeedPostsBox.tsx:
--------------------------------------------------------------------------------
1 | import { getAuthSession } from '@/lib/auth'
2 | import React from 'react'
3 | import CreatePostActivator from './CreatePostActivator'
4 | import FeedColumn from './FeedColumn'
5 | import { db } from '@/lib/Prisma.db'
6 | import { INFINITE_SCROLL_PAGINATION_RESULTS } from '@/config'
7 | import { notFound } from 'next/navigation'
8 | import SuggestUsers from './SuggestUsers'
9 |
10 | async function FeedPostsBox() {
11 | const session = await getAuthSession()
12 | const myPost = await db.post.findMany({
13 | include:{
14 | like: true,
15 | author: true,
16 | comments: true,
17 | },
18 | orderBy: {
19 | createdAt: 'desc'
20 | },
21 | take: INFINITE_SCROLL_PAGINATION_RESULTS,
22 | })
23 |
24 | if (!myPost) return notFound()
25 | return (
26 |
27 |
28 | {/* MIDDLE COLUMN --> */}
29 |
30 | { session?.user?.image && }
31 | {/* @ts-ignore */}
32 | {myPost && }
33 |
34 | {/* RIGHT COLUMN --> */}
35 |
36 |
37 |
38 |
39 |
40 | )
41 | }
42 |
43 | export default FeedPostsBox
--------------------------------------------------------------------------------
/src/components/feed/MyPost.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import Link from "next/link";
3 | import React, { FC, useState } from "react";
4 | import EditorOutput from "../EditorOutput";
5 | import { BiSolidComment } from "react-icons/bi";
6 | import PostLikeBtn from "../button/PostLikeBtn";
7 | import { Post, User } from "@prisma/client";
8 | import CommentButton from "../button/CommentButton";
9 | import { format } from 'timeago.js'
10 | interface mypostProps {
11 | post: Post & {
12 | author: User;
13 |
14 | };
15 | commentLength?:number
16 | isPostLiked?: any;
17 | LikeAmt?:any
18 | }
19 | const MyPost: FC = ({ post, isPostLiked ,commentLength,LikeAmt}) => {
20 | return (
21 | <>
22 |
23 |
24 | {/* HEAD */}
25 |
26 |
27 |
28 | {post?.author.image && (
29 |
38 | )}
39 |
40 |
41 |
45 | {post.author.username}
46 |
47 | {format(post.createdAt)}
48 |
49 |
50 |
51 | {/* THREE DOT ICON AND DROPDOWN */}
52 |
53 |
54 | {/* BODY */}
55 |
56 |
60 |
61 |
62 | {post?.title}
63 |
64 |
65 |
66 |
67 |
68 | {/* FOOTER */}
69 |
70 |
71 |
72 | {/*
76 | Milly
77 |
78 | ,
79 |
83 |
84 | */}
85 |
86 | © FRIENDZ
87 |
88 |
89 |
90 |
91 | {/* Like count */}
92 |
93 |
94 | {/* comment count */}
95 |
96 |
97 | {commentLength}
98 |
99 |
100 |
101 |
102 | {/* COMMENT WILL BE HERE --> */}
103 |
104 | >
105 | );
106 | };
107 |
108 | export default MyPost;
109 |
--------------------------------------------------------------------------------
/src/components/feed/ProfilePostsColumn.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from "react"
2 | import MyPost from "./MyPost"
3 | import { getAuthSession } from "@/lib/auth"
4 |
5 |
6 | interface ProfilePostsColumnprops{
7 | profilePosts: any
8 | }
9 | const ProfilePostsColumn:FC = async({profilePosts}) =>{
10 | const session = await getAuthSession()
11 |
12 |
13 | return(
14 | <>
15 | {
16 | profilePosts.map((posts:any,index:number)=>{
17 |
18 | const isLiked = posts?.like?.find(
19 | (vote:any) => vote.userId === session?.user.id
20 | )
21 | const LikeAmt = posts?.like.length
22 | const CommentAmt = posts?.comments.length
23 | return(
24 |
25 |
33 |
34 |
35 | )
36 | })
37 | }
38 |
39 |
40 |
41 | >
42 | )
43 | }
44 |
45 |
46 | export default ProfilePostsColumn
--------------------------------------------------------------------------------
/src/components/feed/SuggestUsers.tsx:
--------------------------------------------------------------------------------
1 | import { getAuthSession } from "@/lib/auth";
2 | import UsersSuggested from "./UsersSuggested";
3 | import { db } from "@/lib/Prisma.db";
4 |
5 | const SuggestUsers = async () => {
6 | const session = await getAuthSession();
7 | const randomIndex = Math.floor(Math.random() * 5);
8 | const users = await db.user.findMany({
9 | // skip: randomIndex,
10 | where:{
11 | id:{
12 | not:session?.user.id
13 | },
14 | NOT:{
15 | followers:{
16 | some:{
17 | followerId:session?.user.id
18 | }
19 | }
20 | }
21 | },
22 | include: {
23 | followers: true,
24 | },
25 |
26 |
27 | });
28 | const randomUsers = users.sort(() => 0.5 - Math.random()).slice(0, 5);
29 |
30 | return (
31 | <>
32 |
33 |
34 |
35 | Suggested Friends
36 |
37 |
38 |
39 | {/* USERS */}
40 | {randomUsers.map((val) => {
41 | const isFollowed = val.followers.find((is) => is.followerId === session?.user.id)
42 |
43 | if(!isFollowed){
44 | return (
45 |
56 | );
57 | }
58 |
59 | })}
60 |
61 |
62 | >
63 | );
64 | };
65 |
66 | export default SuggestUsers;
67 |
--------------------------------------------------------------------------------
/src/components/feed/UsersSuggested.tsx:
--------------------------------------------------------------------------------
1 | import { User } from "@prisma/client"
2 | import Image from "next/image"
3 | import { FC } from "react"
4 | import FollowButton from "../button/FollowButton"
5 | import Link from "next/link"
6 |
7 | type usersuggestedprops ={
8 | user: Pick
9 | followers:any
10 | sessionid:string
11 | }
12 |
13 | const UsersSuggested:FC = ({user,followers,sessionid}) =>{
14 |
15 | let isUserFollowed = followers.find(
16 | (val:any) => val.followerId === sessionid
17 | );
18 |
19 | return(
20 |
21 |
22 |
23 |
24 | {user.username}
25 | {user.name}
26 |
27 |
28 |
29 |
30 |
31 |
32 | )
33 | }
34 |
35 |
36 | export default UsersSuggested
--------------------------------------------------------------------------------
/src/components/loaders/PostLoader.tsx:
--------------------------------------------------------------------------------
1 | const PostLoader = () => {
2 | return (
3 |
4 | {/* HEADER */}
5 |
11 | {/* BODY */}
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | export default PostLoader;
19 |
--------------------------------------------------------------------------------
/src/components/newuserpage/NewUserImageUpload.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { handleImageUpload } from '@/lib/Functions'
3 | import axios from 'axios'
4 | import Image from 'next/image'
5 | import React,{useState} from 'react'
6 | import {IoAdd} from 'react-icons/io5'
7 | import { NewUserNextStepBtn } from '../button/NewUserButton'
8 | import toast from 'react-hot-toast'
9 |
10 | type NewUserImagetype = {
11 | Myimage: string
12 | }
13 | const NewUserImageUpload = ({Myimage}:NewUserImagetype) =>{
14 | const [img,setImg] = useState(Myimage)
15 | const [loading ,setLoading] = useState(false)
16 | // FUNCTION IMAGE UPLOAD
17 | const onImageChange = async (e:React.ChangeEvent) => {
18 | setLoading(true)
19 | try {
20 | const [file]:any = e.target.files;
21 | if(!file) return
22 |
23 | const uploading = await handleImageUpload(file)
24 | setImg(uploading.file.url)
25 | const { data }= await axios.patch('/api/user/avatar',{uploading})
26 | toast.success('Avatar changed.')
27 |
28 | } catch (error) {
29 | console.log(error);
30 |
31 | } finally{
32 | setLoading(false)
33 | }
34 | };
35 | return(
36 | <>
37 |
38 |
39 |
40 |
41 |
43 |
44 |
55 |
56 | {loading &&
Uploading... }
57 |
58 |
59 | Only images with a size lower than 3MB are allowed.
60 |
61 |
62 |
63 |
64 |
65 | >
66 | )
67 | }
68 |
69 | export default NewUserImageUpload
--------------------------------------------------------------------------------
/src/components/newuserpage/ProgressBar.tsx:
--------------------------------------------------------------------------------
1 | import { BsEmojiSmile } from "react-icons/bs";
2 | import { AiOutlineUser } from "react-icons/ai";
3 | import { BiLockAlt, BiFlag } from "react-icons/bi";
4 | import { MdOutlineInsertPhoto } from "react-icons/md";
5 |
6 |
7 | function ProgressBar() {
8 | return (
9 |
10 |
11 |
12 | {/*track*/}
13 |
14 | {/* DOT */}
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | )
35 | }
36 |
37 | export default ProgressBar
--------------------------------------------------------------------------------
/src/components/newuserpage/Progresstitle.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 | function Progresstitle() {
4 | return (
5 | Welcome, select an account type.
6 | )
7 | }
8 |
9 | export default Progresstitle
--------------------------------------------------------------------------------
/src/components/newuserpage/Signup1.tsx:
--------------------------------------------------------------------------------
1 |
2 | import {SignupAsCompany, SignupAsPrivate, SignupAsPublic} from './cards/Signupcard'
3 |
4 | function Signup1() {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 | )
12 | }
13 |
14 | export default Signup1
--------------------------------------------------------------------------------
/src/components/newuserpage/Signup2.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState } from "react";
3 | import { Button } from "@/components/ui/button";
4 | import {
5 | Form,
6 | FormControl,
7 | FormDescription,
8 | FormField,
9 | FormItem,
10 | FormLabel,
11 | FormMessage,
12 | } from "@/components/ui/form";
13 | import { Input } from "@/components/ui/input";
14 | import { useForm } from "react-hook-form";
15 | import { z } from "zod";
16 | import { UsernameValidator } from "@/lib/NewUserFormValidator";
17 | import { zodResolver } from "@hookform/resolvers/zod";
18 | import { NewUserNextStepBtn } from "../button/NewUserButton";
19 | import { useMutation } from "@tanstack/react-query";
20 | import axios, { AxiosError } from "axios";
21 | import { useRouter } from "next/navigation";
22 | import {toast} from "sonner";
23 | type FormData = z.infer;
24 | function Signup2({existingUsername}:{existingUsername:string}) {
25 | const router = useRouter()
26 | const [btnDisable , setBtnDisable] = useState(!existingUsername ? true : false)
27 | const form = useForm({
28 | resolver: zodResolver(UsernameValidator),
29 | defaultValues: {
30 | username: existingUsername,
31 | },
32 |
33 | });
34 |
35 |
36 | // 2. Defining a submit handler here --.
37 | const {mutate:updateUsername , isLoading} = useMutation({
38 | mutationKey:['updateUsername'],
39 | mutationFn: async ({ username }: FormData) => {
40 | const payload: FormData = { username }
41 |
42 | const { data } = await axios.patch(`/api/user/username/`, payload)
43 | return data
44 | },
45 |
46 | onError: (err) => {
47 | if (err instanceof AxiosError) {
48 | if (err.response?.status === 409) {
49 | form.setError('username', {
50 | type: 'manual',
51 | message: 'Username already taken.'
52 | })
53 | return
54 | }
55 | }
56 | form.setError('username', {
57 | type: 'manual',
58 | message: 'Something went wrong.'
59 | })
60 | },
61 | onSuccess: () => {
62 | setBtnDisable(false)
63 | router.refresh()
64 | toast.success('Username Changed successfully.')
65 | },
66 | })
67 | return (
68 | <>
69 |
70 |
91 |
92 |
93 |
94 | >
95 | );
96 | }
97 |
98 | export default Signup2;
99 |
--------------------------------------------------------------------------------
/src/components/newuserpage/Signup3.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState } from "react";
3 | import { NewUserNextStepBtn } from "../button/NewUserButton";
4 | import { Button } from "@/components/ui/button";
5 | import {
6 | Form,
7 | FormControl,
8 | FormField,
9 | FormItem,
10 | FormLabel,
11 | FormMessage,
12 | } from "@/components/ui/form";
13 | import { Input } from "@/components/ui/input";
14 | import { Textarea } from "../ui/textarea";
15 | import { LocationAndBioValidator } from "@/lib/NewUserFormValidator";
16 | import { useForm } from "react-hook-form";
17 | import { zodResolver } from "@hookform/resolvers/zod";
18 | import { z } from "zod";
19 | import { useMutation } from "@tanstack/react-query";
20 | import axios, { AxiosError } from "axios";
21 | import { useRouter } from "next/navigation";
22 | import {toast} from "sonner";
23 |
24 | type FormData = z.infer;
25 |
26 | function Signup3() {
27 | const [isDisabled, setIsDisabled] = useState(true);
28 | const router = useRouter();
29 | const form = useForm({
30 | resolver: zodResolver(LocationAndBioValidator),
31 | defaultValues: {
32 | location: "",
33 | Bio: "",
34 | },
35 |
36 | });
37 |
38 | // 2. Define a submit handler.
39 | const { mutate: AddLocationAndBio, isLoading } = useMutation({
40 | mutationFn: async ({ location, Bio }: FormData) => {
41 | const payload: FormData = { location, Bio };
42 |
43 | const { data } = await axios.patch(`/api/user/aboutme/`, payload);
44 | return data;
45 | },
46 | onError: (err) => {
47 | if (err instanceof AxiosError) {
48 | if (err.response?.status === 409) {
49 | return toast.error(' Please enter properly')
50 | }
51 | }
52 |
53 | return toast.error('Something went wrong.')
54 |
55 | },
56 | onSuccess: () => {
57 | setIsDisabled(false);
58 | router.refresh();
59 | toast.success('Location and Bio Added.')
60 | },
61 | });
62 |
63 | return (
64 | <>
65 |
66 |
112 |
113 |
114 |
115 | >
116 | );
117 | }
118 |
119 | export default Signup3;
120 |
--------------------------------------------------------------------------------
/src/components/newuserpage/Signup4.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | import { NewUserNextStepBtn } from "../button/NewUserButton"
5 | import NewUserImageUpload from "./NewUserImageUpload"
6 |
7 | async function Signup4({userImage}:{userImage:string}) {
8 |
9 | return (
10 | <>
11 |
12 |
13 |
14 |
15 | >
16 | )
17 | }
18 |
19 | export default Signup4
--------------------------------------------------------------------------------
/src/components/newuserpage/Signup5.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image'
2 | import Link from 'next/link'
3 | import congratz from '../../../public/congratz.svg'
4 | function Signup5() {
5 | return (
6 |
7 |
8 |
9 |
Congratz, you successfully created your account.
10 |
Thankyou for registering, Your data will be secure in our data base
11 |
Let me in
13 |
14 |
15 | )
16 | }
17 |
18 | export default Signup5
--------------------------------------------------------------------------------
/src/components/newuserpage/cards/Signupcard.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image"
3 | import publicImg from "../../../../public/publicaccount.svg";
4 | import companyImg from "../../../../public/companyaccount.svg";
5 | import privateImg from "../../../../public/privateaccount.svg";
6 | import { AccountTypeSubmitBtn } from "@/components/button/NewUserButton";
7 | export const SignupAsCompany = () => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
Company
15 |
16 | Create a company account to be able to do some awesome things.
17 |
18 |
19 |
20 |
21 | )
22 | }
23 | export const SignupAsPublic = () => {
24 | return (
25 |
26 |
27 |
28 |
29 |
30 |
Public
31 |
32 | Create a company account to be able to do some awesome things.
33 |
34 |
35 |
36 |
37 |
38 | )
39 | }
40 | export const SignupAsPrivate = () => {
41 | return (
42 |
43 |
44 |
45 |
46 |
47 |
Private
48 |
49 | Create a company account to be able to do some awesome things.
50 |
51 |
52 |
53 |
54 |
55 | )
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/src/components/renderers/CustomCodeRender.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | function CustomCodeRenderer({ data }: any) {
4 | (data)
5 |
6 | return (
7 |
8 | {data.code}
9 |
10 | )
11 | }
12 |
13 | export default CustomCodeRenderer
--------------------------------------------------------------------------------
/src/components/renderers/CustomImageRender.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { FaClosedCaptioning } from "react-icons/fa";
3 | import Image from "next/image";
4 | import { Button } from "../ui/button";
5 | import {
6 | Popover,
7 | PopoverContent,
8 | PopoverTrigger,
9 | } from "@/components/ui/popover";
10 |
11 | function CustomImageRenderer({ data }: any) {
12 | const src = data.file.url;
13 | const caption = data.caption;
14 |
15 | return (
16 | <>
17 |
18 |
19 |
20 | {caption.length >= 1 && (
21 |
22 |
23 |
24 |
25 |
26 |
27 | {caption}
28 |
29 |
30 |
31 | )}
32 | >
33 | );
34 | }
35 |
36 | export default CustomImageRenderer;
37 |
--------------------------------------------------------------------------------
/src/components/renderers/CustomListRenderer.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { cn } from "@/lib/utils";
3 | import React from "react";
4 |
5 | function CustomListRenderer({ data }: any) {
6 | return {data.items.map((item: string, idx: number) => (
7 |
12 | {item}
13 |
14 | ))};
15 | }
16 |
17 | export default CustomListRenderer;
18 |
--------------------------------------------------------------------------------
/src/components/slidebar/SearchDropdown.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React, { FC } from 'react'
3 | import SearchedUsers from './SearchedUsers'
4 | import { Loader2 } from 'lucide-react'
5 |
6 |
7 | interface searchdropdownprops {
8 | users: any
9 | loading: boolean
10 | }
11 | const SearchDropdown: FC = ({ users, loading }) => {
12 | return (
13 |
14 |
15 |
16 | {
17 | users.length > 0 ? (
18 | users.map((val: any) =>
)
19 | ) : (
20 | !loading ?
No User Found
:
21 | searching...
22 |
23 | )
24 | }
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
32 | export default SearchDropdown
--------------------------------------------------------------------------------
/src/components/slidebar/SearchedUsers.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image'
2 | import Link from 'next/link'
3 | import { FC } from 'react'
4 |
5 |
6 | interface searcheduserprops{
7 | name:string,
8 | image:string,
9 | username:string
10 | id: string
11 | }
12 |
13 | const SearchedUsers:FC = ({name,image,id,username}) => {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 | {username}
22 |
{name}
23 |
24 |
25 |
26 | )
27 | }
28 |
29 | export default SearchedUsers
--------------------------------------------------------------------------------
/src/components/slidebar/Slidebar.tsx:
--------------------------------------------------------------------------------
1 | import { FiUser, FiUsers } from 'react-icons/fi'
2 | import React from 'react'
3 | import { AiOutlineSetting } from 'react-icons/ai'
4 | import { getAuthSession } from '@/lib/auth'
5 | import Image from 'next/image'
6 | import Link from 'next/link'
7 | import { SlideBarInnerButton } from '../button/SlideBarResponsiveExitButton'
8 | import SearchUserInput from '../SearchUserInput'
9 | import LogOutButton from '../button/LogOutButton'
10 | import SlidebarLink from './SlidebarLink'
11 | import SettingBtn from '../button/SettingBtn'
12 | async function Slidebar() {
13 | const session = await getAuthSession()
14 |
15 | return (
16 |
17 | {/* Top section */}
18 |
19 |
20 |
21 | {/* USER BLOCK */}
22 | {session?.user &&
23 |
24 |
25 |
26 | {session.user.name}
27 | Public
28 |
29 |
30 | }
31 |
32 | {/* Bottom section */}
33 |
34 |
35 |
36 |
37 | -
38 |
39 |
40 | -
41 |
42 |
43 |
44 |
45 |
46 |
47 | )
48 | }
49 |
50 | export default Slidebar
51 |
52 |
--------------------------------------------------------------------------------
/src/components/slidebar/SlidebarLink.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import Link from "next/link"
4 | import { usePathname } from "next/navigation"
5 | import { AiOutlineHome } from "react-icons/ai"
6 | import { CgProfile } from "react-icons/cg"
7 | import { FiUser, FiUsers } from "react-icons/fi"
8 |
9 | const SlidebarLink = ({session}:any) =>{
10 |
11 | const router = usePathname()
12 |
13 |
14 |
15 | return(
16 | <>
17 |
18 |
19 |
20 | Home
21 |
22 |
23 |
24 |
25 |
26 |
27 | Profile
28 |
29 |
30 | >
31 |
32 | )
33 | }
34 |
35 | export default SlidebarLink
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils'
2 | import { cva, VariantProps } from 'class-variance-authority'
3 | import { Loader2 } from 'lucide-react'
4 | import * as React from 'react'
5 |
6 | const buttonVariants = cva(
7 | 'active:scale-95 inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900',
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
12 | destructive:
13 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
14 | outline:
15 | 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
16 | custom:
17 | ' rounded-lg border border-input bg-background hover:bg-[#5596e6] hover:text-white hover:shadow-lg',
18 | subtle:
19 | 'border border-input bg-background hover:bg-[#6aa2e6] hover:text-white',
20 | ghost:
21 | 'bg-transparent hover:bg-zinc-100 text-zinc-800 data-[state=open]:bg-transparent data-[state=open]:bg-transparent',
22 | link: 'bg-transparent dark:bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent',
23 | },
24 | size: {
25 | default: 'h-10 py-2 px-4',
26 | sm: 'h-9 px-2 rounded-md',
27 | xs: 'h-8 px-1.5 rounded-sm',
28 | lg: 'h-11 px-8 rounded-md',
29 | },
30 | },
31 | defaultVariants: {
32 | variant: 'default',
33 | size: 'default',
34 | },
35 | }
36 | )
37 |
38 | export interface ButtonProps
39 | extends React.ButtonHTMLAttributes,
40 | VariantProps {
41 | isLoading?: boolean
42 | }
43 |
44 | const Button = React.forwardRef(
45 | ({ className, children, variant, isLoading, size, ...props }, ref) => {
46 | return (
47 |
55 | )
56 | }
57 | )
58 | Button.displayName = 'Button'
59 |
60 | export { Button, buttonVariants }
--------------------------------------------------------------------------------
/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLDivElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = "CardTitle"
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLDivElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = "CardDescription"
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = "CardContent"
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = "CardFooter"
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/src/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DialogPrimitive from "@radix-ui/react-dialog"
5 | import { X } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Dialog = DialogPrimitive.Root
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger
12 |
13 | const DialogPortal = ({
14 | className,
15 | ...props
16 | }: DialogPrimitive.DialogPortalProps) => (
17 |
18 | )
19 | DialogPortal.displayName = DialogPrimitive.Portal.displayName
20 |
21 | const DialogOverlay = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, ...props }, ref) => (
25 |
33 | ))
34 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
35 |
36 | const DialogContent = React.forwardRef<
37 | React.ElementRef,
38 | React.ComponentPropsWithoutRef
39 | >(({ className, children, ...props }, ref) => (
40 |
41 |
42 |
50 | {children}
51 |
52 |
53 | Close
54 |
55 |
56 |
57 | ))
58 | DialogContent.displayName = DialogPrimitive.Content.displayName
59 |
60 | const DialogHeader = ({
61 | className,
62 | ...props
63 | }: React.HTMLAttributes) => (
64 |
71 | )
72 | DialogHeader.displayName = "DialogHeader"
73 |
74 | const DialogFooter = ({
75 | className,
76 | ...props
77 | }: React.HTMLAttributes) => (
78 |
85 | )
86 | DialogFooter.displayName = "DialogFooter"
87 |
88 | const DialogTitle = React.forwardRef<
89 | React.ElementRef,
90 | React.ComponentPropsWithoutRef
91 | >(({ className, ...props }, ref) => (
92 |
100 | ))
101 | DialogTitle.displayName = DialogPrimitive.Title.displayName
102 |
103 | const DialogDescription = React.forwardRef<
104 | React.ElementRef,
105 | React.ComponentPropsWithoutRef
106 | >(({ className, ...props }, ref) => (
107 |
112 | ))
113 | DialogDescription.displayName = DialogPrimitive.Description.displayName
114 |
115 | export {
116 | Dialog,
117 | DialogTrigger,
118 | DialogContent,
119 | DialogHeader,
120 | DialogFooter,
121 | DialogTitle,
122 | DialogDescription,
123 | }
124 |
--------------------------------------------------------------------------------
/src/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { Slot } from "@radix-ui/react-slot"
4 | import {
5 | Controller,
6 | ControllerProps,
7 | FieldPath,
8 | FieldValues,
9 | FormProvider,
10 | useFormContext,
11 | } from "react-hook-form"
12 |
13 | import { cn } from "@/lib/utils"
14 | import { Label } from "@/components/ui/label"
15 |
16 | const Form = FormProvider
17 |
18 | type FormFieldContextValue<
19 | TFieldValues extends FieldValues = FieldValues,
20 | TName extends FieldPath = FieldPath
21 | > = {
22 | name: TName
23 | }
24 |
25 | const FormFieldContext = React.createContext(
26 | {} as FormFieldContextValue
27 | )
28 |
29 | const FormField = <
30 | TFieldValues extends FieldValues = FieldValues,
31 | TName extends FieldPath = FieldPath
32 | >({
33 | ...props
34 | }: ControllerProps) => {
35 | return (
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | const useFormField = () => {
43 | const fieldContext = React.useContext(FormFieldContext)
44 | const itemContext = React.useContext(FormItemContext)
45 | const { getFieldState, formState } = useFormContext()
46 |
47 | const fieldState = getFieldState(fieldContext.name, formState)
48 |
49 | if (!fieldContext) {
50 | throw new Error("useFormField should be used within ")
51 | }
52 |
53 | const { id } = itemContext
54 |
55 | return {
56 | id,
57 | name: fieldContext.name,
58 | formItemId: `${id}-form-item`,
59 | formDescriptionId: `${id}-form-item-description`,
60 | formMessageId: `${id}-form-item-message`,
61 | ...fieldState,
62 | }
63 | }
64 |
65 | type FormItemContextValue = {
66 | id: string
67 | }
68 |
69 | const FormItemContext = React.createContext(
70 | {} as FormItemContextValue
71 | )
72 |
73 | const FormItem = React.forwardRef<
74 | HTMLDivElement,
75 | React.HTMLAttributes
76 | >(({ className, ...props }, ref) => {
77 | const id = React.useId()
78 |
79 | return (
80 |
81 |
82 |
83 | )
84 | })
85 | FormItem.displayName = "FormItem"
86 |
87 | const FormLabel = React.forwardRef<
88 | React.ElementRef,
89 | React.ComponentPropsWithoutRef
90 | >(({ className, ...props }, ref) => {
91 | const { error, formItemId } = useFormField()
92 |
93 | return (
94 |
100 | )
101 | })
102 | FormLabel.displayName = "FormLabel"
103 |
104 | const FormControl = React.forwardRef<
105 | React.ElementRef,
106 | React.ComponentPropsWithoutRef
107 | >(({ ...props }, ref) => {
108 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109 |
110 | return (
111 |
122 | )
123 | })
124 | FormControl.displayName = "FormControl"
125 |
126 | const FormDescription = React.forwardRef<
127 | HTMLParagraphElement,
128 | React.HTMLAttributes
129 | >(({ className, ...props }, ref) => {
130 | const { formDescriptionId } = useFormField()
131 |
132 | return (
133 |
139 | )
140 | })
141 | FormDescription.displayName = "FormDescription"
142 |
143 | const FormMessage = React.forwardRef<
144 | HTMLParagraphElement,
145 | React.HTMLAttributes
146 | >(({ className, children, ...props }, ref) => {
147 | const { error, formMessageId } = useFormField()
148 | const body = error ? String(error?.message) : children
149 |
150 | if (!body) {
151 | return null
152 | }
153 |
154 | return (
155 |
161 | {body}
162 |
163 | )
164 | })
165 | FormMessage.displayName = "FormMessage"
166 |
167 | export {
168 | useFormField,
169 | Form,
170 | FormItem,
171 | FormLabel,
172 | FormControl,
173 | FormDescription,
174 | FormMessage,
175 | FormField,
176 | }
177 |
--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as PopoverPrimitive from "@radix-ui/react-popover"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Popover = PopoverPrimitive.Root
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ))
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
30 |
31 | export { Popover, PopoverTrigger, PopoverContent }
32 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | // 2 to demonstrate infinite scroll, should be higher in production
2 | export const INFINITE_SCROLL_PAGINATION_RESULTS = 4
--------------------------------------------------------------------------------
/src/lib/Apis.ts:
--------------------------------------------------------------------------------
1 | import { useMutation, useQuery } from "@tanstack/react-query";
2 | import axios from "axios";
3 |
4 | // Function to fetch search results
5 | const fetchSearchResults = async (value: string) => {
6 | const { data } = await axios.get(`/api/search?value=${value}`);
7 | return data;
8 | };
9 |
10 | // Hook to use the search query
11 | export const useSearch = (searchValue: string) => {
12 | return useQuery({
13 | queryKey: [ searchValue],
14 | queryFn: () => fetchSearchResults(searchValue),
15 | enabled: searchValue.length > 0, // More specific condition
16 | // Don't run query if searchValue is empty or too short
17 | initialData: [], // Provide initial empty array
18 | refetchOnWindowFocus: false,
19 | });
20 | };
21 |
--------------------------------------------------------------------------------
/src/lib/Firebase.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { initializeApp } from 'firebase/app';
3 | import { getStorage } from 'firebase/storage';
4 | const firebaseConfig = {
5 | apiKey: process.env.APIKEY,
6 | authDomain:"projectfriendz-45b49.firebaseapp.com",
7 | projectId: "projectfriendz-45b49",
8 | storageBucket: "projectfriendz-45b49.appspot.com",
9 | messagingSenderId: "186662584426",
10 | appId: "1:186662584426:web:b37a6002f57c2af7578a13",
11 | measurementId:"G-ZCMDL02FYD"
12 | };
13 | const app = initializeApp(firebaseConfig);
14 | const storage = getStorage(app);
15 | export{ storage };
--------------------------------------------------------------------------------
/src/lib/Functions.tsx:
--------------------------------------------------------------------------------
1 | import { storage } from "./Firebase";
2 | import { ref, uploadBytes, getDownloadURL } from "firebase/storage";
3 |
4 |
5 |
6 | // ****************** SIGN UP PROGRESS BAR ANIMATE AND STEPS FUNCTION ***********************
7 | export const HandleNext = (index: number) => {
8 | const ids = ["first", "second", "third", "fourth", "fifth"];
9 | const steps = ["stepOne", "stepTwo", "stepThree", "stepFour", "stepFive"];
10 | const title = [
11 | "Welcome, select an account type.",
12 | "Let's create a unique username",
13 | "Tell us more about you.",
14 | "Change a profile picture. Or go Next ",
15 | "You're all set. Ready?",
16 | ];
17 | const value = [0, 25, 50, 75, 100];
18 | // Set the clicked step as active and mark previous steps as completed
19 | const signupTitle = document.getElementById('signupTitle');
20 | var progressBar = document.getElementById('bar')
21 | if (signupTitle !== null) signupTitle.innerHTML = title[index]
22 | for (let i = 0; i <= index; i++) {
23 | document.getElementById(`${ids[i]}`)?.classList.add("activeDot");
24 | const pannelShow = document.getElementById(`${steps[i]}`);
25 | const pannelHide = document.getElementById(`${steps[i - 1]}`);
26 |
27 | if (pannelShow !== null) pannelShow.style.display = "block";
28 | if (pannelHide !== null) pannelHide.style.display = "none";
29 | if (progressBar !== null) progressBar.style.width = `${value[i]}%`
30 | }
31 |
32 | // Mark remaining steps as inactive and incomplete
33 | for (let i = index + 1; i < ids.length; i++) {
34 | const hidePannel = document.getElementById(`${steps[i]}`);
35 | if (hidePannel !== null) hidePannel.style.display = "none";
36 | document.getElementById(`${ids[i]}`)?.classList.remove("activeDot");
37 | }
38 | };
39 | // *********************** HANDLE Image Upload For Editor.js in firebase *************************
40 |
41 | export async function handleImageUpload (file:File) {
42 |
43 | const storageRef = ref(storage, `images/${file.name}`);
44 | await uploadBytes(storageRef, file);
45 | const res = await getDownloadURL(storageRef);
46 | return {
47 | success: 1,
48 | file: {
49 | url: res,
50 | },
51 | }
52 | };
--------------------------------------------------------------------------------
/src/lib/NewUserFormValidator.tsx:
--------------------------------------------------------------------------------
1 | import { z } from "zod"
2 |
3 | export const UsernameValidator = z.object({
4 | username: z.string()
5 | .min(3,{message: "Username must be at least 4 characters."})
6 | .max(20,{message: "Username not more than 20 characters.",})
7 | .regex(/^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9_]+$/,{message: "Username must include any number and character ",}),
8 | })
9 |
10 | export const LocationAndBioValidator = z.object({
11 | location: z.string().min(5,{message:"Location must at least 5 characters"})
12 | .max(45,{message:"Location not more than 15 characters."}),
13 | Bio: z.string().min(5,{message:"Bio must at least 5 characters"})
14 | .max(65,{message:"Bio not more than 25 characters."})
15 | })
16 | export const NewUserImageValidator = z.object({
17 | image: z.string()
18 | })
--------------------------------------------------------------------------------
/src/lib/Prisma.db.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from '@prisma/client'
2 | import "server-only"
3 |
4 | declare global {
5 | // eslint-disable-next-line no-var, no-unused-vars
6 | var cachedPrisma: PrismaClient
7 | }
8 |
9 | let prisma: PrismaClient
10 | if (process.env.NODE_ENV === 'production') {
11 | prisma = new PrismaClient()
12 | } else {
13 | if (!global.cachedPrisma) {
14 | global.cachedPrisma = new PrismaClient()
15 | }
16 | prisma = global.cachedPrisma
17 | }
18 |
19 | export const db = prisma
20 |
--------------------------------------------------------------------------------
/src/lib/auth.ts:
--------------------------------------------------------------------------------
1 | import { db } from "./Prisma.db";
2 |
3 | import { PrismaAdapter } from "@auth/prisma-adapter";
4 | import { nanoid } from "nanoid";
5 | import { NextAuthOptions, getServerSession } from "next-auth";
6 | import GoogleProvider from "next-auth/providers/google";
7 | export const authOptions: NextAuthOptions = {
8 | // @ts-ignore
9 | adapter: PrismaAdapter(db),
10 | session: {
11 | strategy: "jwt",
12 | },
13 | pages: {
14 | signIn: "/signin",
15 | newUser:'/newuser'
16 | },
17 | providers: [
18 | GoogleProvider({
19 | clientId: process.env.GOOGLE_CLIENT_ID!,
20 | clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
21 | }),
22 | ],
23 | callbacks: {
24 | async session({ token, session }) {
25 | if (token) {
26 | // @ts-ignore
27 | session.user.id = token.id;
28 | // @ts-ignore
29 | session.user.name = token.name;
30 | // @ts-ignore
31 | session.user.email = token.email;
32 | // @ts-ignore
33 | session.user.image = token.picture;
34 | // @ts-ignore
35 | session.user.username = token.username;
36 | }
37 |
38 | return session;
39 | },
40 |
41 | async jwt({ token, user }) {
42 | const dbUser = await db.user.findFirst({
43 | where: {
44 | email: token.email,
45 | },
46 | });
47 |
48 | if (!dbUser) {
49 | token.id = user!.id;
50 | return token;
51 | }
52 |
53 | if (!dbUser.username) {
54 | await db.user.update({
55 | where: {
56 | id: dbUser.id,
57 | },
58 | data: {
59 | username: nanoid(10),
60 | },
61 | });
62 | }
63 |
64 | return {
65 | id: dbUser.id,
66 | name: dbUser.name,
67 | email: dbUser.email,
68 | picture: dbUser.image,
69 | username: dbUser.username,
70 | onboardingCompleted: dbUser.onboardingCompleted,
71 | location: dbUser.location,
72 | Bio: dbUser.Bio,
73 | };
74 | },
75 | redirect() {
76 |
77 | return "/";
78 |
79 | },
80 | },
81 | };
82 |
83 | export const getAuthSession = () => getServerSession(authOptions);
84 |
--------------------------------------------------------------------------------
/src/lib/commentValidator.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 |
3 | export const CommentValidator = z.object({
4 | postId: z.string(),
5 | text: z.string(),
6 | replyToId: z.string().optional()
7 | })
8 |
9 | export type CommentRequest = z.infer
10 |
11 |
12 |
13 | export const CommentVoteValidator = z.object({
14 | commentId: z.string(),
15 | })
16 |
17 | export type CommentVoteRequest = z.infer
--------------------------------------------------------------------------------
/src/lib/redis.ts:
--------------------------------------------------------------------------------
1 | import { Redis } from '@upstash/redis'
2 |
3 | export const redis = new Redis({
4 | url: process.env.REDIS_URL!,
5 | token: process.env.REDIS_SECRET!,
6 | })
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/src/middleware.ts:
--------------------------------------------------------------------------------
1 |
2 | import { getToken } from 'next-auth/jwt'
3 | import { NextResponse } from 'next/server'
4 | import type { NextRequest } from 'next/server'
5 |
6 | export async function middleware(req: NextRequest) {
7 | const token = await getToken({ req })
8 |
9 | if (!token) {
10 | return NextResponse.redirect(new URL('/signin', req.nextUrl))
11 | }
12 | }
13 |
14 |
15 |
16 |
17 | // See "Matching Paths" below to learn more
18 | export const config = {
19 | matcher: '/',
20 | }
--------------------------------------------------------------------------------
/src/types/PostLikeValidator.tsx:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 |
3 | export const PostVoteValidator = z.object({
4 | postId: z.string(),
5 | })
6 |
7 | export type PostVoteRequest = z.infer
8 |
9 |
10 | export const FollowUserValidator = z.object({
11 | toFollowId: z.string(),
12 | })
13 |
14 | export type FollowUserRequest = z.infer
--------------------------------------------------------------------------------
/src/types/db.d.ts:
--------------------------------------------------------------------------------
1 | import { Post, User, Like, Comment } from "@prisma/client";
2 |
3 |
4 |
5 |
6 | export type ExtendedPost = Post & {
7 | author:User,
8 | like: Like[],
9 | comments: Comment[],
10 |
11 |
12 | }
--------------------------------------------------------------------------------
/src/types/next-auth.d.ts:
--------------------------------------------------------------------------------
1 | import type { Session, User } from 'next-auth'
2 | import type { JWT } from 'next-auth/jwt'
3 |
4 | type UserId = string
5 |
6 | declare module 'next-auth/jwt' {
7 | interface JWT {
8 | id: UserId
9 | username?: string | null
10 | }
11 | }
12 |
13 | declare module 'next-auth' {
14 | interface Session {
15 | user: User & {
16 | id: UserId
17 | username?: string | null
18 | onboardingCompleted?: boolean
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/src/types/redis.d.ts:
--------------------------------------------------------------------------------
1 |
2 | export type CachedPost = {
3 | id: string
4 | title: string
5 | authorUsername: string
6 | content: string
7 | // authorName : string
8 | // authorImage: string
9 | createdAt: Date
10 | }
--------------------------------------------------------------------------------
/src/types/types.ts:
--------------------------------------------------------------------------------
1 | export type user = {
2 | id:string;
3 | accountType:string;
4 | fullName:string;
5 | userName:string;
6 | email:string;
7 | password:any;
8 | Repassword:any;
9 | avatar:string;
10 |
11 |
12 | }
13 | export type sessionUserType = {
14 | user: {
15 | name: string;
16 | email: string;
17 | image: string;
18 | id: string;
19 | username: string;
20 | }
21 | }
22 |
23 |
24 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ["class"],
4 | content: [
5 | './pages/**/*.{ts,tsx}',
6 | './components/**/*.{ts,tsx}',
7 | './app/**/*.{ts,tsx}',
8 | './src/**/*.{ts,tsx}',
9 | ],
10 | theme: {
11 | container: {
12 | center: true,
13 | padding: "2rem",
14 | screens: {
15 | "2xl": "1400px",
16 | },
17 | },
18 | extend: {
19 | colors: {
20 | border: "hsl(var(--border))",
21 | borderE3:"#e3e3e3",
22 | colorF7:"#f7f7f7",
23 | input: "hsl(var(--input))",
24 | ring: "hsl(var(--ring))",
25 | background: "hsl(var(--background))",
26 | foreground: "hsl(var(--foreground))",
27 | primary: {
28 | DEFAULT: "hsl(var(--primary))",
29 | foreground: "hsl(var(--primary-foreground))",
30 | },
31 | secondary: {
32 | DEFAULT: "hsl(var(--secondary))",
33 | foreground: "hsl(var(--secondary-foreground))",
34 | },
35 | destructive: {
36 | DEFAULT: "hsl(var(--destructive))",
37 | foreground: "hsl(var(--destructive-foreground))",
38 | },
39 | muted: {
40 | DEFAULT: "hsl(var(--muted))",
41 | foreground: "hsl(var(--muted-foreground))",
42 | },
43 | accent: {
44 | DEFAULT: "hsl(var(--accent))",
45 | foreground: "hsl(var(--accent-foreground))",
46 | },
47 | popover: {
48 | DEFAULT: "hsl(var(--popover))",
49 | foreground: "hsl(var(--popover-foreground))",
50 | },
51 | card: {
52 | DEFAULT: "hsl(var(--card))",
53 | foreground: "hsl(var(--card-foreground))",
54 | },
55 | },
56 | borderRadius: {
57 | lg: "var(--radius)",
58 | md: "calc(var(--radius) - 2px)",
59 | sm: "calc(var(--radius) - 4px)",
60 | },
61 | keyframes: {
62 | "accordion-down": {
63 | from: { height: 0 },
64 | to: { height: "var(--radix-accordion-content-height)" },
65 | },
66 | "accordion-up": {
67 | from: { height: "var(--radix-accordion-content-height)" },
68 | to: { height: 0 },
69 | },
70 | shake: {
71 | '0%, 100%': { transform: 'rotate(0deg)' },
72 | '25%': { transform: 'rotate(-10deg)' },
73 | '75%': { transform: 'rotate(10deg)' }
74 | }
75 | },
76 | animation: {
77 | "accordion-down": "accordion-down 0.2s ease-out",
78 | "accordion-up": "accordion-up 0.2s ease-out",
79 | shake: 'shake 1s ease-in-out infinite'
80 | },
81 | },
82 | },
83 | plugins: [require("tailwindcss-animate")],
84 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "bundler",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "plugins": [
18 | {
19 | "name": "next"
20 | }
21 | ],
22 | "paths": {
23 | "@/*": ["./src/*"]
24 | }
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------