├── client ├── public │ ├── audio │ │ ├── sending.mp3 │ │ ├── sending2.mp3 │ │ ├── notification.mp3 │ │ └── notification2.mp3 │ └── logo.svg ├── postcss.config.mjs ├── .eslintrc.json ├── app │ ├── api │ │ ├── auth │ │ │ └── [...nextauth] │ │ │ │ └── route.ts │ │ └── uploadthing │ │ │ ├── route.ts │ │ │ └── core.ts │ ├── auth │ │ ├── _component │ │ │ ├── state.tsx │ │ │ ├── social.tsx │ │ │ ├── sign-in.tsx │ │ │ └── verify.tsx │ │ └── page.tsx │ ├── (chat) │ │ ├── layout.tsx │ │ ├── _components │ │ │ ├── add-contact.tsx │ │ │ ├── contact-list.tsx │ │ │ ├── top-chat.tsx │ │ │ ├── chat.tsx │ │ │ └── settings.tsx │ │ └── page.tsx │ ├── layout.tsx │ └── globals.css ├── http │ └── axios.ts ├── types │ ├── next-auth.d.ts │ └── index.ts ├── lib │ ├── generate-token.ts │ ├── uploadthing.ts │ ├── constants.ts │ ├── mongoose.ts │ ├── utils.ts │ ├── validation.ts │ └── auth-options.ts ├── next.config.ts ├── components │ ├── providers │ │ ├── session.provider.tsx │ │ ├── theme.provider.tsx │ │ └── query.provider.tsx │ ├── ui │ │ ├── skeleton.tsx │ │ ├── textarea.tsx │ │ ├── label.tsx │ │ ├── separator.tsx │ │ ├── input.tsx │ │ ├── toaster.tsx │ │ ├── switch.tsx │ │ ├── popover.tsx │ │ ├── avatar.tsx │ │ ├── button.tsx │ │ ├── accordion.tsx │ │ ├── input-otp.tsx │ │ ├── dialog.tsx │ │ ├── sheet.tsx │ │ ├── form.tsx │ │ ├── toast.tsx │ │ └── context-menu.tsx │ ├── loadings │ │ ├── chat.loading.tsx │ │ └── message.loading.tsx │ ├── shared │ │ └── mode-toggle.tsx │ ├── forms │ │ ├── danger-zone.form.tsx │ │ ├── information.form.tsx │ │ ├── email.form.tsx │ │ └── notification.form.tsx │ └── cards │ │ └── message.card.tsx ├── components.json ├── hooks │ ├── use-current.ts │ ├── use-auth.ts │ ├── use-audio.ts │ ├── use-loading.ts │ └── use-toast.ts ├── .gitignore ├── tsconfig.json ├── models │ └── user.model.ts ├── README.md ├── package.json └── tailwind.config.ts ├── server ├── lib │ └── constants.js ├── models │ ├── otp.model.js │ ├── message.model.js │ └── user.model.js ├── middlewares │ ├── error.middleware.js │ └── auth.middleware.js ├── errors │ └── base.error.js ├── .gitignore ├── package.json ├── app.js ├── controllers │ ├── auth.controller.js │ └── user.controller.js ├── routes │ └── index.js └── service │ └── mail.service.js ├── socket ├── package.json ├── .gitignore ├── socket.js └── package-lock.json └── README.md /client/public/audio/sending.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samarbadriddin0v/telegram-clone/HEAD/client/public/audio/sending.mp3 -------------------------------------------------------------------------------- /client/public/audio/sending2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samarbadriddin0v/telegram-clone/HEAD/client/public/audio/sending2.mp3 -------------------------------------------------------------------------------- /client/public/audio/notification.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samarbadriddin0v/telegram-clone/HEAD/client/public/audio/notification.mp3 -------------------------------------------------------------------------------- /client/public/audio/notification2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samarbadriddin0v/telegram-clone/HEAD/client/public/audio/notification2.mp3 -------------------------------------------------------------------------------- /server/lib/constants.js: -------------------------------------------------------------------------------- 1 | const CONST = { 2 | READ: 'read', 3 | DELIVERED: 'delivered', 4 | SENT: 'sent', 5 | } 6 | 7 | module.exports = { CONST } 8 | -------------------------------------------------------------------------------- /client/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"], 3 | "rules": { 4 | "react-hooks/exhaustive-deps": "off", 5 | "@typescript-eslint/no-explicit-any": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /client/app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import { authOptions } from '@/lib/auth-options' 2 | import NextAuth from 'next-auth' 3 | 4 | const handler = NextAuth(authOptions) 5 | export { handler as GET, handler as POST } 6 | -------------------------------------------------------------------------------- /client/app/api/uploadthing/route.ts: -------------------------------------------------------------------------------- 1 | import { createRouteHandler } from 'uploadthing/next' 2 | 3 | import { ourFileRouter } from './core' 4 | 5 | export const { GET, POST } = createRouteHandler({ router: ourFileRouter }) 6 | -------------------------------------------------------------------------------- /client/http/axios.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export const SERVER_URL = process.env.NEXT_PUBLIC_SERVER_URL 4 | 5 | export const axiosClient = axios.create({ 6 | baseURL: SERVER_URL, 7 | headers: { 'Content-Type': 'application/json' }, 8 | }) 9 | -------------------------------------------------------------------------------- /client/types/next-auth.d.ts: -------------------------------------------------------------------------------- 1 | import { DefaultSession } from 'next-auth' 2 | import { IUser } from '.' 3 | 4 | declare module 'next-auth' { 5 | interface Session { 6 | currentUser?: IUser 7 | user: {} & DefaultSession['user'] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /client/lib/generate-token.ts: -------------------------------------------------------------------------------- 1 | 'use server' 2 | 3 | import jwt from 'jsonwebtoken' 4 | 5 | export const generateToken = async (userId?: string) => { 6 | const token = jwt.sign({ userId }, process.env.NEXT_PUBLIC_JWT_SECRET!, { expiresIn: '1m' }) 7 | return token 8 | } 9 | -------------------------------------------------------------------------------- /server/models/otp.model.js: -------------------------------------------------------------------------------- 1 | const { Schema, model } = require('mongoose') 2 | 3 | const otpSchema = new Schema({ 4 | email: { type: String, required: true }, 5 | otp: { type: String, required: true }, 6 | expireAt: { type: Date }, 7 | }) 8 | 9 | module.exports = model('Otp', otpSchema) 10 | -------------------------------------------------------------------------------- /client/lib/uploadthing.ts: -------------------------------------------------------------------------------- 1 | import { OurFileRouter } from '@/app/api/uploadthing/core' 2 | import { generateUploadButton, generateUploadDropzone } from '@uploadthing/react' 3 | 4 | export const UploadButton = generateUploadButton() 5 | export const UploadDropzone = generateUploadDropzone() 6 | -------------------------------------------------------------------------------- /client/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from 'next' 2 | 3 | const nextConfig: NextConfig = { 4 | images: { 5 | remotePatterns: [ 6 | { protocol: 'https', hostname: 'github.com', pathname: '**' }, 7 | { protocol: 'https', hostname: 'utfs.io', pathname: '**' }, 8 | ], 9 | }, 10 | } 11 | 12 | export default nextConfig 13 | -------------------------------------------------------------------------------- /server/middlewares/error.middleware.js: -------------------------------------------------------------------------------- 1 | const BaseError = require('../errors/base.error') 2 | 3 | module.exports = function (err, req, res, next) { 4 | if (err instanceof BaseError) { 5 | return res.status(err.status).json({ message: err.message, errors: err.errors }) 6 | } 7 | 8 | return res.status(500).json({ message: err.message }) 9 | } 10 | -------------------------------------------------------------------------------- /client/components/providers/session.provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { ChildProps } from '@/types' 4 | import { SessionProvider as Session } from 'next-auth/react' 5 | import { FC } from 'react' 6 | 7 | const SessionProvider: FC = ({ children }) => { 8 | return {children} 9 | } 10 | 11 | export default SessionProvider 12 | -------------------------------------------------------------------------------- /client/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /client/lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const SOUNDS = [ 2 | { value: 'notification.mp3', label: 'Apple' }, 3 | { value: 'notification2.mp3', label: 'Sammish' }, 4 | { value: 'sending.mp3', label: 'Belli' }, 5 | { value: 'sending2.mp3', label: 'Oranger' }, 6 | ] 7 | 8 | export const CONST = { 9 | READ: 'read', 10 | DELIVERED: 'delivered', 11 | SENT: 'sent', 12 | } 13 | -------------------------------------------------------------------------------- /socket/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket", 3 | "version": "1.0.0", 4 | "main": "socket.js", 5 | "scripts": { 6 | "socket": "nodemon socket.js" 7 | }, 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "description": "", 12 | "devDependencies": { 13 | "nodemon": "^3.1.7" 14 | }, 15 | "dependencies": { 16 | "socket.io": "^4.8.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/app/auth/_component/state.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useAuth } from '@/hooks/use-auth' 4 | import SignIn from './sign-in' 5 | import Verify from './verify' 6 | 7 | const StateAuth = () => { 8 | const { step } = useAuth() 9 | 10 | return ( 11 | <> 12 | {step === 'login' && } 13 | {step === 'verify' && } 14 | 15 | ) 16 | } 17 | 18 | export default StateAuth 19 | -------------------------------------------------------------------------------- /server/errors/base.error.js: -------------------------------------------------------------------------------- 1 | module.exports = class BaseError extends Error { 2 | status 3 | errors 4 | 5 | constructor(status, message, errors = []) { 6 | super(message) 7 | this.status = status 8 | this.errors = errors 9 | } 10 | 11 | static BadRequest(message, errors = []) { 12 | return new BaseError(400, message, errors) 13 | } 14 | 15 | static Unauthorized() { 16 | return new BaseError(401, 'Unauthorized') 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/components/providers/theme.provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import { ThemeProvider as NextThemesProvider } from 'next-themes' 5 | import NoSSR from 'react-no-ssr' 6 | 7 | export function ThemeProvider({ children, ...props }: React.ComponentProps) { 8 | return ( 9 | 10 | {children} 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /client/app/(chat)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { authOptions } from '@/lib/auth-options' 2 | import { ChildProps } from '@/types' 3 | import { getServerSession } from 'next-auth' 4 | import { redirect } from 'next/navigation' 5 | import { FC } from 'react' 6 | 7 | const Layout: FC = async ({ children }) => { 8 | const session = await getServerSession(authOptions) 9 | if (!session) return redirect('/auth') 10 | 11 | return
{children}
12 | } 13 | 14 | export default Layout 15 | -------------------------------------------------------------------------------- /client/lib/mongoose.ts: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | 3 | let isConnected: boolean = false 4 | 5 | export const connectToDatabase = async () => { 6 | mongoose.set('strictQuery', true) 7 | 8 | if (!process.env.MONGO_URI) { 9 | return console.error('MONGO_URI is not defined') 10 | } 11 | 12 | if (isConnected) { 13 | return 14 | } 15 | 16 | try { 17 | await mongoose.connect(process.env.MONGO_URI, { autoCreate: true }) 18 | isConnected = true 19 | } catch { 20 | console.log('Error connecting to database') 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /client/hooks/use-current.ts: -------------------------------------------------------------------------------- 1 | import { IMessage, IUser } from '@/types' 2 | import { create } from 'zustand' 3 | 4 | type Store = { 5 | currentContact: IUser | null 6 | setCurrentContact: (contact: IUser | null) => void 7 | editedMessage: IMessage | null 8 | setEditedMessage: (message: IMessage | null) => void 9 | } 10 | 11 | export const useCurrentContact = create()(set => ({ 12 | currentContact: null, 13 | setCurrentContact: contact => set({ currentContact: contact }), 14 | editedMessage: null, 15 | setEditedMessage: message => set({ editedMessage: message }), 16 | })) 17 | -------------------------------------------------------------------------------- /client/hooks/use-auth.ts: -------------------------------------------------------------------------------- 1 | import { IUser } from '@/types' 2 | import { create } from 'zustand' 3 | 4 | type Store = { 5 | step: 'login' | 'verify' 6 | setStep: (step: 'login' | 'verify') => void 7 | email: string 8 | setEmail: (email: string) => void 9 | onlineUsers: IUser[] 10 | setOnlineUsers: (users: IUser[]) => void 11 | } 12 | 13 | export const useAuth = create()(set => ({ 14 | step: 'login', 15 | setStep: step => set({ step }), 16 | email: '', 17 | setEmail: email => set({ email }), 18 | onlineUsers: [], 19 | setOnlineUsers: users => set({ onlineUsers: users }), 20 | })) 21 | -------------------------------------------------------------------------------- /server/models/message.model.js: -------------------------------------------------------------------------------- 1 | const { Schema, model } = require('mongoose') 2 | const { CONST } = require('../lib/constants') 3 | 4 | const messageSchema = new Schema( 5 | { 6 | sender: { type: Schema.Types.ObjectId, ref: 'User', required: true }, 7 | receiver: { type: Schema.Types.ObjectId, ref: 'User', required: true }, 8 | text: { type: String }, 9 | image: { type: String }, 10 | status: { type: String, enum: [CONST.DELIVERED, CONST.READ, CONST.SENT], default: CONST.SENT }, 11 | reaction: { type: String }, 12 | }, 13 | { timestamps: true } 14 | ) 15 | 16 | module.exports = model('Message', messageSchema) 17 | -------------------------------------------------------------------------------- /client/components/loadings/chat.loading.tsx: -------------------------------------------------------------------------------- 1 | import MessageLoading from './message.loading' 2 | 3 | const ChatLoading = () => { 4 | return ( 5 | <> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ) 21 | } 22 | 23 | export default ChatLoading 24 | -------------------------------------------------------------------------------- /client/components/shared/mode-toggle.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import { Moon, Sun } from 'lucide-react' 5 | import { useTheme } from 'next-themes' 6 | 7 | import { Button } from '@/components/ui/button' 8 | 9 | export function ModeToggle() { 10 | const { setTheme, resolvedTheme } = useTheme() 11 | 12 | return resolvedTheme === 'dark' ? ( 13 | 16 | ) : ( 17 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /client/.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.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # env files (can opt-in for committing if needed) 33 | .env* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | -------------------------------------------------------------------------------- /server/.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.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # env files (can opt-in for committing if needed) 33 | .env* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | -------------------------------------------------------------------------------- /socket/.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.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # env files (can opt-in for committing if needed) 33 | .env* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "main": "app.js", 5 | "scripts": { 6 | "server": "nodemon app.js" 7 | }, 8 | "keywords": [], 9 | "author": "Samar Badriddinov", 10 | "license": "ISC", 11 | "description": "", 12 | "dependencies": { 13 | "bcrypt": "^5.1.1", 14 | "cookie-parser": "^1.4.7", 15 | "cors": "^2.8.5", 16 | "dotenv": "^16.4.5", 17 | "express": "^4.21.1", 18 | "express-group-routes": "^1.1.0", 19 | "http": "^0.0.1-security", 20 | "jsonwebtoken": "^9.0.2", 21 | "mongoose": "^8.8.1", 22 | "nodemailer": "^6.9.16" 23 | }, 24 | "devDependencies": { 25 | "nodemon": "^3.1.7" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /client/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from 'clsx' 2 | import { twMerge } from 'tailwind-merge' 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | 8 | export const getSoundLabel = (value?: string) => { 9 | switch (value) { 10 | case 'notification.mp3': 11 | return 'Apple' 12 | case 'notification2.mp3': 13 | return 'Sammish' 14 | case 'sending.mp3': 15 | return 'Belli' 16 | case 'sending2.mp3': 17 | return 'Oranger' 18 | default: 19 | return '' 20 | } 21 | } 22 | 23 | export const sliceText = (text: string, length: number) => { 24 | return text.length > length ? `${text.slice(0, length)}...` : text 25 | } 26 | -------------------------------------------------------------------------------- /server/models/user.model.js: -------------------------------------------------------------------------------- 1 | const { Schema, model } = require('mongoose') 2 | 3 | const userSchema = new Schema( 4 | { 5 | email: { type: String, required: true, unique: true }, 6 | isVerified: { type: Boolean, default: false }, 7 | firstName: { type: String }, 8 | lastName: { type: String }, 9 | bio: { type: String }, 10 | avatar: { type: String }, 11 | muted: { type: Boolean, default: false }, 12 | notificationSound: { type: String, default: 'notification.mp3' }, 13 | sendingSound: { type: String, default: 'sending.mp3' }, 14 | contacts: [{ type: Schema.Types.ObjectId, ref: 'User' }], 15 | }, 16 | { timestamps: true } 17 | ) 18 | 19 | module.exports = model('User', userSchema) 20 | -------------------------------------------------------------------------------- /client/components/loadings/message.loading.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils' 2 | import { FC } from 'react' 3 | import { Skeleton } from '../ui/skeleton' 4 | 5 | interface Props { 6 | isReceived?: boolean 7 | } 8 | const MessageLoading: FC = ({ isReceived }) => { 9 | return ( 10 |
11 | 12 | 13 | 14 | 15 |
16 | ) 17 | } 18 | 19 | export default MessageLoading 20 | -------------------------------------------------------------------------------- /client/hooks/use-audio.ts: -------------------------------------------------------------------------------- 1 | import useSound from 'use-sound' 2 | 3 | const useAudio = () => { 4 | const [play1] = useSound('/audio/notification.mp3') 5 | const [play2] = useSound('/audio/notification2.mp3') 6 | const [play3] = useSound('/audio/sending.mp3') 7 | const [play4] = useSound('/audio/sending2.mp3') 8 | 9 | const playSound = (sound: string) => { 10 | switch (sound) { 11 | case 'notification.mp3': 12 | play1() 13 | break 14 | case 'notification2.mp3': 15 | play2() 16 | break 17 | case 'sending.mp3': 18 | play3() 19 | break 20 | case 'sending2.mp3': 21 | play4() 22 | break 23 | default: 24 | } 25 | } 26 | 27 | return { playSound } 28 | } 29 | 30 | export default useAudio 31 | -------------------------------------------------------------------------------- /client/models/user.model.ts: -------------------------------------------------------------------------------- 1 | import { IUser } from '@/types' 2 | import { Schema, models, model } from 'mongoose' 3 | 4 | const userSchema = new Schema({ 5 | email: { type: String, required: true, unique: true }, 6 | isVerified: { type: Boolean, default: false }, 7 | firstName: { type: String }, 8 | lastName: { type: String }, 9 | bio: { type: String }, 10 | avatar: { type: String }, 11 | muted: { type: Boolean, default: false }, 12 | notificationSound: { type: String, default: 'notification.mp3' }, 13 | sendingSound: { type: String, default: 'sending.mp3' }, 14 | contacts: [{ type: Schema.Types.ObjectId, ref: 'User' }], 15 | }) 16 | 17 | const User = models.User || model('User', userSchema) 18 | export default User 19 | -------------------------------------------------------------------------------- /client/app/api/uploadthing/core.ts: -------------------------------------------------------------------------------- 1 | import { authOptions } from '@/lib/auth-options' 2 | import { getServerSession } from 'next-auth' 3 | import { createUploadthing, type FileRouter } from 'uploadthing/next' 4 | import { UploadThingError } from 'uploadthing/server' 5 | 6 | const f = createUploadthing() 7 | 8 | export const ourFileRouter = { 9 | imageUploader: f({ image: { maxFileSize: '4MB' } }) 10 | .middleware(async () => { 11 | const token = await getServerSession(authOptions) 12 | if (!token) throw new UploadThingError('Unauthorized') 13 | return { token } 14 | }) 15 | .onUploadComplete(async ({ file }) => { 16 | return file 17 | }), 18 | } satisfies FileRouter 19 | 20 | export type OurFileRouter = typeof ourFileRouter 21 | -------------------------------------------------------------------------------- /client/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface ChildProps { 2 | children: React.ReactNode 3 | } 4 | 5 | export interface IError extends Error { 6 | response: { data: { message: string } } 7 | } 8 | 9 | export interface IUser { 10 | email: string 11 | _id: string 12 | avatar: string 13 | firstName: string 14 | lastName: string 15 | bio: string 16 | isVerified: boolean 17 | muted: boolean 18 | notificationSound: string 19 | sendingSound: string 20 | contacts: IUser[] 21 | lastMessage: IMessage | null 22 | } 23 | 24 | export interface IMessage { 25 | _id: string 26 | text: string 27 | image: string 28 | reaction: string 29 | sender: IUser 30 | receiver: IUser 31 | createdAt: string 32 | updatedAt: string 33 | status: string 34 | } 35 | -------------------------------------------------------------------------------- /client/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Textarea = React.forwardRef< 6 | HTMLTextAreaElement, 7 | React.ComponentProps<"textarea"> 8 | >(({ className, ...props }, ref) => { 9 | return ( 10 |