├── .env.example ├── .eslintrc.json ├── .gitignore ├── .idea ├── .gitignore ├── modules.xml ├── twitter-clone-new.iml └── vcs.xml ├── .prettierrc ├── .vscode └── settings.json ├── README.md ├── next-env.d.ts ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── prisma ├── data │ ├── categories.ts │ └── faker-data.ts ├── populate.ts ├── schema.prisma └── seed.ts ├── public ├── favicon.ico ├── favicon1.ico ├── icons │ ├── bookmark-empty.png │ ├── bookmark-filled.png │ ├── camera-white.png │ ├── camera.png │ ├── close-white.png │ ├── close.png │ ├── emoji.png │ ├── hart.png │ ├── log-out.png │ ├── photo.png │ ├── profile.png │ ├── replay.png │ ├── search.png │ └── test.svg └── images │ ├── 404-image.svg │ ├── avatar-fallback.svg │ ├── avatar-fallback1.svg │ ├── blob-animation.svg │ ├── community-fallback.svg │ ├── community-fallback1.svg │ ├── default-banner.jpg │ ├── fallback.svg │ ├── fallback1.svg │ ├── fallback3.svg │ └── login-image.svg ├── src ├── components │ ├── bookmarks │ │ └── use-bookmarks.ts │ ├── comments-list │ │ ├── comment-content.tsx │ │ ├── comment-footer.tsx │ │ ├── comment-input.tsx │ │ ├── comment-item.tsx │ │ ├── comments-list.tsx │ │ ├── deleted-comment-fallback.tsx │ │ └── use-comment.tsx │ ├── common │ │ ├── back-button.tsx │ │ ├── button-follow.tsx │ │ ├── button.tsx │ │ ├── error-fallback.tsx │ │ ├── fallback-card.tsx │ │ ├── icons │ │ │ ├── arrow-left.tsx │ │ │ ├── blob.tsx │ │ │ ├── bookmark-empty.tsx │ │ │ ├── bookmark.tsx │ │ │ ├── caret-down.tsx │ │ │ ├── chevron-left.tsx │ │ │ ├── chevron-right.tsx │ │ │ ├── close.tsx │ │ │ ├── comment.tsx │ │ │ ├── emoji.tsx │ │ │ ├── exclamation.tsx │ │ │ ├── github.tsx │ │ │ ├── google.tsx │ │ │ ├── graph.tsx │ │ │ ├── hash.tsx │ │ │ ├── heart-empty.tsx │ │ │ ├── heart.tsx │ │ │ ├── link.tsx │ │ │ ├── log-out.tsx │ │ │ ├── photo.tsx │ │ │ ├── profile.tsx │ │ │ ├── search.tsx │ │ │ ├── share.tsx │ │ │ ├── star-empty.tsx │ │ │ └── star.tsx │ │ ├── letter-counter.tsx │ │ ├── loading.tsx │ │ ├── modal-wrapper.tsx │ │ ├── posts-sort-panel.tsx │ │ ├── spinner.tsx │ │ ├── text-header.tsx │ │ ├── theme-switch.tsx │ │ ├── theme-switcher-button.tsx │ │ ├── trending-tags-list.tsx │ │ ├── user-card.tsx │ │ └── user-profile-image.tsx │ ├── community │ │ ├── categories-list-item.tsx │ │ ├── categories-list.tsx │ │ ├── community-card.tsx │ │ ├── community-creator.tsx │ │ ├── community-favourite-icon.tsx │ │ ├── community-filter.tsx │ │ ├── community-list.tsx │ │ ├── community-post-input.tsx │ │ ├── community-profile-hero.tsx │ │ ├── community-settings-button.tsx │ │ ├── community-settings.tsx │ │ ├── desktop-category-panel.tsx │ │ ├── join-community-button.tsx │ │ ├── members-list.tsx │ │ ├── mobile-category-panel.tsx │ │ ├── popular-communities-list.tsx │ │ ├── suggestion-list.tsx │ │ ├── use-category-list.tsx │ │ └── use-community.ts │ ├── explore │ │ ├── suggested-communities-list.tsx │ │ ├── suggested-users-list.tsx │ │ └── use-explore.ts │ ├── form │ │ ├── form-error-message.tsx │ │ ├── form-images.tsx │ │ ├── form-input.tsx │ │ ├── form-select.tsx │ │ └── form-textarea.tsx │ ├── header │ │ ├── desktop-navigation.tsx │ │ ├── dropdown-menu.tsx │ │ ├── header.tsx │ │ ├── menu.tsx │ │ ├── search-bar.tsx │ │ ├── search-card.tsx │ │ └── use-search-history.ts │ ├── home │ │ ├── home-fallback-card.tsx │ │ ├── home-post-input.tsx │ │ └── use-home.ts │ ├── layouts │ │ ├── community-layout.tsx │ │ ├── explore-layout.tsx │ │ └── main-layout.tsx │ ├── notifications │ │ ├── notification-card-wrapper.tsx │ │ ├── notification-comment-reply-card.tsx │ │ ├── notification-community-new-member-card.tsx │ │ ├── notification-mentions-card.tsx │ │ ├── notification-post-comment-card.tsx │ │ ├── notification-start-follow-card.tsx │ │ ├── notifications-list.tsx │ │ ├── use-notification-list.ts │ │ └── use-notifications.ts │ ├── post-card │ │ ├── author.tsx │ │ ├── community-badge.tsx │ │ ├── images-grid.tsx │ │ ├── index.ts │ │ ├── mentions-list.tsx │ │ ├── post-card-footer.tsx │ │ ├── post-card-link.tsx │ │ ├── post-card.tsx │ │ ├── post-sharing-modal.tsx │ │ ├── repost-badge.tsx │ │ └── tags-list.tsx │ ├── post-input │ │ ├── emoji-picker.tsx │ │ ├── index.ts │ │ ├── post-content.tsx │ │ ├── post-file-input.tsx │ │ ├── post-input.tsx │ │ ├── post-link-input.tsx │ │ ├── post-mentions-input.tsx │ │ ├── post-mentions-picker.tsx │ │ ├── post-tags-input.tsx │ │ ├── post-tags-picker.tsx │ │ ├── selected-mentions-list.tsx │ │ ├── selected-tags-list.tsx │ │ ├── types.ts │ │ ├── upload-image-thumbnail.tsx │ │ └── use-post-input.tsx │ ├── post │ │ ├── image-gallery.tsx │ │ ├── post-details.tsx │ │ ├── post-list.tsx │ │ ├── post-thumbnail.tsx │ │ └── use-post.ts │ ├── tags │ │ └── use-tags.ts │ ├── user-profile │ │ ├── profile-filter-item.tsx │ │ ├── profile-filters.tsx │ │ ├── types.ts │ │ ├── user-profile-button.tsx │ │ └── user-profile-hero.tsx │ └── user │ │ ├── followers-list.tsx │ │ ├── following-list.tsx │ │ ├── profile-settings.tsx │ │ ├── use-user.ts │ │ └── user-follows.tsx ├── env │ ├── client.mjs │ ├── schema.mjs │ └── server.mjs ├── hooks │ ├── mutation.ts │ ├── query.ts │ ├── use-lock-body-scroll.tsx │ ├── use-settings-dropzone.tsx │ └── use-suggestion-popup.tsx ├── pages │ ├── 404.tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── api │ │ ├── auth │ │ │ └── [...nextauth].ts │ │ ├── restricted.ts │ │ └── trpc │ │ │ └── [trpc].ts │ ├── auth │ │ ├── signin.tsx │ │ └── verify-request.tsx │ ├── bookmarks.tsx │ ├── community │ │ ├── [communityId].tsx │ │ └── index.tsx │ ├── explore │ │ └── index.tsx │ ├── index.tsx │ ├── notifications.tsx │ ├── post │ │ └── [postId].tsx │ ├── tag │ │ └── [tagName].tsx │ └── user │ │ └── [userId].tsx ├── server │ ├── db │ │ └── client.ts │ └── router │ │ ├── bookmark.ts │ │ ├── comment.ts │ │ ├── community.ts │ │ ├── context.ts │ │ ├── explore.ts │ │ ├── index.ts │ │ ├── notification.ts │ │ ├── posts.ts │ │ ├── protected-router.ts │ │ ├── search.ts │ │ ├── tag.ts │ │ ├── types.ts │ │ ├── user.ts │ │ └── utils.ts ├── styles │ └── globals.css ├── types │ ├── db.ts │ └── next-auth.d.ts └── utils │ ├── auth.ts │ ├── cloudinary.ts │ └── trpc.ts ├── tailwind.config.cjs └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | 2 | # Prisma (mysql db) 3 | DATABASE_URL=mysql://xxx 4 | 5 | # Next Auth 6 | NEXTAUTH_SECRET=xxx 7 | NEXTAUTH_URL=http://localhost:3000 8 | 9 | # Next Auth Discord Provider 10 | GOOGLE_CLIENT_ID=xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com 11 | GOOGLE_CLIENT_SECRET=xxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxx 12 | 13 | GITHUB_CLIENT_ID=xxxxxxxxxxxxxxxxxxxx 14 | GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 15 | 16 | # Email Auth provider 17 | EMAIL_SERVER=smtp://username:serwer-password@host-name:port 18 | EMAIL_FROM=noreply@example.com 19 | 20 | EMAIL_SERVER_USER=xxx@gmail.com 21 | EMAIL_SERVER_PASSWORD=xxxxxx 22 | EMAIL_SERVER_HOST=smtp-relay.sendinblue.com 23 | EMAIL_SERVER_PORT=587 24 | EMAIL_FROM=noreply@example.com 25 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "airbnb", 5 | "airbnb-typescript", 6 | "prettier" 7 | ], 8 | "parserOptions": { 9 | "project": "./tsconfig.json" 10 | }, 11 | "rules": { 12 | "react/function-component-definition": [ 13 | "error", 14 | { 15 | "namedComponents": ["function-declaration", "arrow-function"], 16 | "unnamedComponents": "arrow-function" 17 | } 18 | ], 19 | "react/react-in-jsx-scope": "off", 20 | "react/jsx-props-no-spreading": "off", 21 | "react/require-default-props": "off", 22 | "jsx-a11y/anchor-is-valid": "off" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.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 | # database 12 | /prisma/db.sqlite 13 | /prisma/db.sqlite-journal 14 | 15 | # next.js 16 | /.next/ 17 | /out/ 18 | 19 | # production 20 | /build 21 | 22 | # misc 23 | .DS_Store 24 | *.pem 25 | 26 | # debug 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | .pnpm-debug.log* 31 | 32 | # local env files 33 | .env 34 | .env*.local 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/twitter-clone-new.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "scss.lint.unknownAtRules": "ignore" 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Social Network 2 | 3 | ## Overview 4 | 5 | Social Media App allows users to share their moments with others and find people with the same passions 6 | 7 | Implemented functionalities: 8 | - Login through magic links or other social media accounts 9 | - Ability to share posts with photos and links 10 | - Ability to add tags and mention other users in posts 11 | - Ability to Like, comment and share posts 12 | - Ability to follow other users 13 | - Infinite feed with posts of people you follow 14 | - Groups that allow you to find users with the same passions 15 | - Explore section, where you can find other users or groups that may be of interest to you. 16 | - Notification system, which will let you know when someone e.g. starts following you or add comment on your post 17 | - Dark mode 18 | 19 | ## Build with 20 | 21 | - Next.js 22 | - TypeScript 23 | - TailwindCSS 24 | - Prisma 25 | - NextAuth 26 | - TRPC 27 | - PlanetScale 28 | - Cloudinary 29 | - Vercel 30 | 31 | ## Links 32 | 33 | - [live demo](https://social-network.mlatka9.vercel.app/) 34 | - [github repo](https://github.com/mlatka9/twitter-clone) 35 | 36 | ## Screenshots 37 | 38 | ![twitter-clone-mlatka9 vercel app_](https://user-images.githubusercontent.com/72691985/195369325-65c7985c-0222-4411-9312-ff12a7a0492f.png) 39 | ![s2](https://user-images.githubusercontent.com/72691985/195369341-bd726523-88ff-4869-a677-2ec07b3aae74.png) 40 | ![s3](https://user-images.githubusercontent.com/72691985/195369352-04d7f832-329c-4e2c-9f3b-0fdf2a82608e.png) 41 | 42 | 43 | ## Install 44 | 45 | 1. Clone repository 46 | ``` 47 | git clone https://github.com/mlatka9/twitter-clone.git 48 | ``` 49 | 50 | 2. Go to project directory 51 | ``` 52 | cd twitter-clone 53 | ``` 54 | 55 | 3. Install dependencies 56 | ``` 57 | npm install 58 | ``` 59 | 60 | 4. Create .env file (see .env.example) 61 | 62 | ``` 63 | echo "" > .env (for windows) touch .env (for unix) 64 | ``` 65 | 66 | 5. Run server 67 | ``` 68 | npm run dev 69 | ``` 70 | 71 | ## Acknowledgement 72 | 73 | The app design was inspired by the [devchallenges.io task](https://devchallenges.io/challenges/rleoQc34THclWx1cFFKH) 74 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | import { env } from './src/env/server.mjs'; 2 | 3 | import asd from '@next/bundle-analyzer'; 4 | /** 5 | * Don't be scared of the generics here. 6 | * All they do is to give us autocompletion when using this. 7 | * 8 | * @template {import('next').NextConfig} T 9 | * @param {T} config - A generic parameter that flows through to the return type 10 | * @constraint {{import('next').NextConfig}} 11 | */ 12 | function defineNextConfig(config) { 13 | return withBundleAnalyzer(config); 14 | } 15 | 16 | const withBundleAnalyzer = asd({ 17 | enabled: process.env.ANALYZE === 'true', 18 | }); 19 | 20 | export default defineNextConfig({ 21 | // experimental: { 22 | // images: { 23 | // unoptimized: true, 24 | // } 25 | // }, 26 | reactStrictMode: true, 27 | swcMinify: true, 28 | images: { 29 | domains: [ 30 | 'lh3.googleusercontent.com', 31 | 'res.cloudinary.com', 32 | 'cloudflare-ipfs.com', 33 | 'loremflickr.com', 34 | 'avatars.githubusercontent.com' 35 | ], 36 | }, 37 | }); 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twitter-clone-new", 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 | "format": "prettier --write .", 11 | "postinstall": "prisma generate" 12 | }, 13 | "prisma": { 14 | "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts" 15 | }, 16 | "dependencies": { 17 | "@emoji-mart/data": "^1.0.5", 18 | "@emoji-mart/react": "^1.0.1", 19 | "@faker-js/faker": "^7.5.0", 20 | "@next-auth/prisma-adapter": "^1.0.4", 21 | "@next/bundle-analyzer": "^12.2.5", 22 | "@prisma/client": "^4.1.1", 23 | "@tanstack/react-query": "^4.0.10", 24 | "@tanstack/react-query-devtools": "^4.0.10", 25 | "@trpc/client": "^9.26.2", 26 | "@trpc/next": "^9.26.2", 27 | "@trpc/react": "^9.26.2", 28 | "@trpc/server": "^9.26.2", 29 | "axios": "^0.27.2", 30 | "clsx": "^1.2.1", 31 | "emoji-mart": "^5.2.1", 32 | "javascript-time-ago": "^2.5.7", 33 | "lodash.get": "^4.4.2", 34 | "next": "12.2.3", 35 | "next-auth": "^4.10.3", 36 | "next-themes": "^0.2.0", 37 | "nodemailer": "^6.7.8", 38 | "react": "18.2.0", 39 | "react-dom": "18.2.0", 40 | "react-dropzone": "^14.2.2", 41 | "react-hook-form": "^7.34.0", 42 | "react-intersection-observer": "^9.4.0", 43 | "react-masonry-css": "^1.0.16", 44 | "react-query": "^3.39.2", 45 | "react-time-ago": "^7.2.1", 46 | "react-toastify": "^9.0.8", 47 | "superjson": "^1.9.1", 48 | "tailwind-scrollbar": "^2.0.1", 49 | "usehooks-ts": "^2.6.0", 50 | "uuid": "^8.3.2", 51 | "zod": "^3.17.3" 52 | }, 53 | "devDependencies": { 54 | "@types/lodash.get": "^4.4.7", 55 | "@types/node": "^18.0.0", 56 | "@types/react": "18.0.14", 57 | "@types/react-dom": "18.0.5", 58 | "@types/react-mentions": "^4.1.6", 59 | "@types/uuid": "^8.3.4", 60 | "@typescript-eslint/eslint-plugin": "^5.13.0", 61 | "@typescript-eslint/parser": "^5.0.0", 62 | "autoprefixer": "^10.4.8", 63 | "eslint": "^8.2.0", 64 | "eslint-config-airbnb": "^19.0.4", 65 | "eslint-config-airbnb-typescript": "^17.0.0", 66 | "eslint-config-next": "12.2.3", 67 | "eslint-config-prettier": "^8.5.0", 68 | "eslint-plugin-import": "^2.25.3", 69 | "eslint-plugin-jsx-a11y": "^6.5.1", 70 | "eslint-plugin-prettier": "^4.2.1", 71 | "eslint-plugin-react": "^7.28.0", 72 | "eslint-plugin-react-hooks": "^4.3.0", 73 | "postcss": "^8.4.16", 74 | "prettier": "2.7.1", 75 | "prisma": "^4.1.1", 76 | "tailwindcss": "^3.1.8", 77 | "ts-node": "^10.9.1", 78 | "typescript": "^4.7.4" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /prisma/data/categories.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | 'fashion & beauty', 3 | 'arts & culture', 4 | 'animation & comics', 5 | 'business & finance', 6 | 'food', 7 | 'travel', 8 | 'entertainment', 9 | 'music', 10 | 'gaming', 11 | 'sport', 12 | 'technology', 13 | 'science', 14 | 'other', 15 | ]; 16 | -------------------------------------------------------------------------------- /prisma/data/faker-data.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | 3 | export function createRandomUser() { 4 | return { 5 | id: faker.datatype.uuid(), 6 | name: faker.internet.userName(), 7 | email: faker.internet.email(), 8 | image: `${faker.image.avatar()}?random=${Math.round(Math.random() * 100)}`, 9 | bannerImage: `${faker.image.nature()}?random=${Math.round( 10 | Math.random() * 500 11 | )}`, 12 | bio: faker.lorem.paragraph(), 13 | birthdate: faker.date.birthdate(), 14 | }; 15 | } 16 | 17 | export function createRandomPost(authorId: string) { 18 | return { 19 | id: faker.datatype.uuid(), 20 | content: faker.lorem.paragraph(), 21 | user: authorId, 22 | createdAt: faker.date.birthdate({ min: 0, max: 1, mode: 'age' }), 23 | }; 24 | } 25 | 26 | export function createRandomImage(postId: string) { 27 | return { 28 | id: faker.datatype.uuid(), 29 | url: `${faker.image.imageUrl()}?random=${Math.round(Math.random() * 500)}`, 30 | postId, 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /prisma/populate.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client'; 2 | import { 3 | createRandomImage, 4 | createRandomPost, 5 | createRandomUser, 6 | } from './data/faker-data'; 7 | 8 | const prisma = new PrismaClient(); 9 | 10 | async function main() { 11 | const USERS = [] as ReturnType[]; 12 | const POSTS = [] as ReturnType[]; 13 | const IMAGES = [] as ReturnType[]; 14 | 15 | Array.from({ length: 20 }).forEach(() => { 16 | USERS.push(createRandomUser()); 17 | }); 18 | 19 | const usersIds = USERS.map((user) => user.id); 20 | 21 | Array.from({ length: 200 }).forEach(() => { 22 | const randomAuthorId = 23 | usersIds[Math.floor(Math.random() * usersIds.length)]!; 24 | POSTS.push(createRandomPost(randomAuthorId)); 25 | }); 26 | 27 | const postIds = POSTS.map((post) => post.id); 28 | 29 | Array.from({ length: 200 }).forEach(() => { 30 | const randomPostId = postIds[Math.floor(Math.random() * postIds.length)]!; 31 | IMAGES.push(createRandomImage(randomPostId)); 32 | }); 33 | 34 | await prisma.user.createMany({ 35 | data: USERS.map((user) => ({ 36 | id: user.id, 37 | bannerImage: user.bannerImage, 38 | bio: user.bio, 39 | email: user.email, 40 | image: user.image, 41 | name: user.name, 42 | })), 43 | }); 44 | 45 | await prisma.post.createMany({ 46 | data: POSTS.map((post) => ({ 47 | id: post.id, 48 | content: post.content.slice(0, 200), 49 | userId: post.user, 50 | createdAt: post.createdAt, 51 | })), 52 | }); 53 | 54 | await prisma.image.createMany({ 55 | data: IMAGES.map((image) => ({ 56 | fallbackUrl: "", 57 | url: image.url, 58 | postId: image.postId, 59 | alt: '', 60 | width: 100, 61 | height: 100, 62 | })), 63 | }); 64 | } 65 | 66 | main() 67 | .then(async () => { 68 | await prisma.$disconnect(); 69 | }) 70 | .catch(async () => { 71 | await prisma.$disconnect(); 72 | process.exit(1); 73 | }); 74 | -------------------------------------------------------------------------------- /prisma/seed.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client'; 2 | import categories from './data/categories'; 3 | 4 | const prisma = new PrismaClient(); 5 | 6 | async function main() { 7 | await prisma.category.createMany({ 8 | data: categories.map((category) => ({ name: category })), 9 | }); 10 | } 11 | 12 | main() 13 | .then(async () => { 14 | await prisma.$disconnect(); 15 | }) 16 | .catch(async () => { 17 | await prisma.$disconnect(); 18 | process.exit(1); 19 | }); 20 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatka9/social-network/3cd77cbce4efc60c4c841ea88cd8abed4f0405e4/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon1.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatka9/social-network/3cd77cbce4efc60c4c841ea88cd8abed4f0405e4/public/favicon1.ico -------------------------------------------------------------------------------- /public/icons/bookmark-empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatka9/social-network/3cd77cbce4efc60c4c841ea88cd8abed4f0405e4/public/icons/bookmark-empty.png -------------------------------------------------------------------------------- /public/icons/bookmark-filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatka9/social-network/3cd77cbce4efc60c4c841ea88cd8abed4f0405e4/public/icons/bookmark-filled.png -------------------------------------------------------------------------------- /public/icons/camera-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatka9/social-network/3cd77cbce4efc60c4c841ea88cd8abed4f0405e4/public/icons/camera-white.png -------------------------------------------------------------------------------- /public/icons/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatka9/social-network/3cd77cbce4efc60c4c841ea88cd8abed4f0405e4/public/icons/camera.png -------------------------------------------------------------------------------- /public/icons/close-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatka9/social-network/3cd77cbce4efc60c4c841ea88cd8abed4f0405e4/public/icons/close-white.png -------------------------------------------------------------------------------- /public/icons/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatka9/social-network/3cd77cbce4efc60c4c841ea88cd8abed4f0405e4/public/icons/close.png -------------------------------------------------------------------------------- /public/icons/emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatka9/social-network/3cd77cbce4efc60c4c841ea88cd8abed4f0405e4/public/icons/emoji.png -------------------------------------------------------------------------------- /public/icons/hart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatka9/social-network/3cd77cbce4efc60c4c841ea88cd8abed4f0405e4/public/icons/hart.png -------------------------------------------------------------------------------- /public/icons/log-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatka9/social-network/3cd77cbce4efc60c4c841ea88cd8abed4f0405e4/public/icons/log-out.png -------------------------------------------------------------------------------- /public/icons/photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatka9/social-network/3cd77cbce4efc60c4c841ea88cd8abed4f0405e4/public/icons/photo.png -------------------------------------------------------------------------------- /public/icons/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatka9/social-network/3cd77cbce4efc60c4c841ea88cd8abed4f0405e4/public/icons/profile.png -------------------------------------------------------------------------------- /public/icons/replay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatka9/social-network/3cd77cbce4efc60c4c841ea88cd8abed4f0405e4/public/icons/replay.png -------------------------------------------------------------------------------- /public/icons/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatka9/social-network/3cd77cbce4efc60c4c841ea88cd8abed4f0405e4/public/icons/search.png -------------------------------------------------------------------------------- /public/icons/test.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/avatar-fallback.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/images/blob-animation.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /public/images/default-banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatka9/social-network/3cd77cbce4efc60c4c841ea88cd8abed4f0405e4/public/images/default-banner.jpg -------------------------------------------------------------------------------- /public/images/fallback.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /public/images/fallback1.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | Grey checkers minimum size remix+264463 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /public/images/fallback3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/bookmarks/use-bookmarks.ts: -------------------------------------------------------------------------------- 1 | import { useUserBookmarkedPostsQuery } from '@/hooks/query'; 2 | 3 | const useBookmarks = () => { 4 | const { data, fetchNextPage, hasNextPage, isSuccess } = 5 | useUserBookmarkedPostsQuery(); 6 | 7 | const isBookmarksNotExists = isSuccess && !data?.pages[0]?.posts.length; 8 | 9 | return { 10 | isBookmarksNotExists, 11 | data, 12 | fetchNextPage, 13 | hasNextPage, 14 | }; 15 | }; 16 | 17 | export default useBookmarks; 18 | -------------------------------------------------------------------------------- /src/components/comments-list/comment-content.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from "clsx"; 3 | 4 | interface CommentContentProps { 5 | isEditing: boolean; 6 | draftContent: string; 7 | onChangeDraftContent: (e: React.ChangeEvent) => void; 8 | handleUpdateComment: () => void; 9 | commentMessage: string; 10 | parentUserName: string | null; 11 | } 12 | 13 | const CommentContent = ({ 14 | draftContent, 15 | handleUpdateComment, 16 | isEditing, 17 | onChangeDraftContent, 18 | commentMessage, 19 | parentUserName, 20 | }: CommentContentProps) => ( 21 |
22 | {isEditing ? ( 23 |
24 |