diff --git a/Dockerfile.dev b/Dockerfile.dev index 6f18b9414..ae1351395 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -2,14 +2,15 @@ FROM node:20-alpine WORKDIR /usr/src/app -COPY package.json package-lock.json ./ +COPY package.json pnpm-lock.yaml ./ COPY prisma ./prisma -RUN npm install +RUN npm i pnpm -g + +RUN pnpm install COPY . . EXPOSE 3000 -CMD ["npm", "run", "dev:docker"] - +CMD ["pnpm", "dev:docker"] \ No newline at end of file diff --git a/Dockerfile.prod b/Dockerfile.prod index 92e81176f..3f3e5efc5 100644 --- a/Dockerfile.prod +++ b/Dockerfile.prod @@ -1,14 +1,31 @@ -FROM node:20-alpine -ARG DATABASE_URL +FROM node:20-alpine AS build WORKDIR /usr/src/app +ARG DATABASE_URL COPY . . -RUN npm install -RUN DATABASE_URL=$DATABASE_URL npx prisma generate -RUN DATABASE_URL=$DATABASE_URL npm run build +RUN npm install -g pnpm && \ + pnpm install && \ + pnpm add sharp && \ + pnpm run build && \ + DATABASE_URL=$DATABASE_URL pnpm dlx prisma generate -EXPOSE 3000 -CMD ["npm", "run", "start"] + +FROM node:20-alpine AS run + +RUN mkdir /.npm && chown -R 1001:1001 /.npm + +USER 1001:1001 +WORKDIR /usr/src/app + +COPY --from=build --chown=1001:1001 usr/src/app/.next/standalone ./ +COPY --from=build --chown=1001:1001 usr/src/app/.next/static ./.next/static +COPY --from=build --chown=1001:1001 usr/src/app/public ./public + +ENV NODE_ENV production +ENV PORT 3000 +ENV HOSTNAME "0.0.0.0" + +CMD [ "node", "server.js" ] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 8c487ef75..61c2ba945 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,10 @@ -version: '3.5' services: app: - build: . + build: + context: . + dockerfile: Dockerfile.prod + args: + - DATABASE_URL=postgresql://postgres:postgres@db:5432/cms?schema=public container_name: cms-docker environment: - DATABASE_URL=postgresql://postgres:postgres@db:5432/cms?schema=public @@ -10,7 +13,7 @@ services: - '3000:3000' - '5555:5555' volumes: - - .:/usr/src/app + # - .:/usr/src/app - /usr/src/app/.next - /usr/src/app/node_modules depends_on: @@ -18,7 +21,7 @@ services: condition: service_healthy db: - image: postgres:9.6 + image: postgres:alpine container_name: db restart: always environment: @@ -36,4 +39,4 @@ services: retries: 5 volumes: - postgres-data: \ No newline at end of file + postgres-data: \ No newline at end of file diff --git a/next.config.js b/next.config.js index 82b78e87b..f0c5ba607 100644 --- a/next.config.js +++ b/next.config.js @@ -31,6 +31,7 @@ const nextConfig = { } return config; }, + output: 'standalone', }; module.exports = nextConfig; diff --git a/package.json b/package.json index f26aa8e56..21d1a0a46 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@tabler/icons-react": "^3.14.0", "@types/bcrypt": "^5.0.2", "@types/jsonwebtoken": "^9.0.5", + "@uiw/react-markdown-preview": "^5.1.3", "@uiw/react-md-editor": "^4.0.4", "axios": "^1.6.2", "bcrypt": "^5.1.1", @@ -71,6 +72,7 @@ "ioredis": "^5.4.1", "jose": "^5.2.2", "jsonwebtoken": "^9.0.2", + "katex": "^0.16.11", "lucide-react": "^0.321.0", "moment": "^2.30.1", "next": "14.0.2", @@ -80,6 +82,7 @@ "node-fetch": "^3.3.2", "notion-client": "^6.16.0", "pdf-lib": "^1.17.1", + "prismjs": "^1.29.0", "qs": "^6.13.0", "react": "^18", "react-big-calendar": "^1.13.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24f477ce4..0a2bb066e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,7 +24,7 @@ importers: specifier: ^9.4.0 version: 9.7.0(react@18.3.1) '@prisma/client': - specifier: ^5.6.0 + specifier: ^5.18.0 version: 5.18.0(prisma@5.18.0) '@radix-ui/react-accordion': specifier: ^1.1.2 @@ -47,6 +47,9 @@ importers: '@radix-ui/react-popover': specifier: ^1.1.1 version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-radio-group': + specifier: ^1.2.0 + version: 1.2.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-scroll-area': specifier: ^1.1.0 version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -54,7 +57,7 @@ importers: specifier: ^1.1.0 version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': - specifier: ^1.0.2 + specifier: ^1.1.0 version: 1.1.0(@types/react@18.3.3)(react@18.3.1) '@radix-ui/react-switch': specifier: ^1.1.0 @@ -62,12 +65,18 @@ importers: '@radix-ui/react-tooltip': specifier: ^1.0.7 version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tabler/icons-react': + specifier: ^3.14.0 + version: 3.19.0(react@18.3.1) '@types/bcrypt': specifier: ^5.0.2 version: 5.0.2 '@types/jsonwebtoken': specifier: ^9.0.5 version: 9.0.6 + '@uiw/react-markdown-preview': + specifier: ^5.1.3 + version: 5.1.3(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@uiw/react-md-editor': specifier: ^4.0.4 version: 4.0.4(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -113,6 +122,9 @@ importers: jsonwebtoken: specifier: ^9.0.2 version: 9.0.2 + katex: + specifier: ^0.16.11 + version: 0.16.11 lucide-react: specifier: ^0.321.0 version: 0.321.0(react@18.3.1) @@ -140,6 +152,9 @@ importers: pdf-lib: specifier: ^1.17.1 version: 1.17.1 + prismjs: + specifier: ^1.29.0 + version: 1.29.0 qs: specifier: ^6.13.0 version: 6.13.0 @@ -289,7 +304,7 @@ importers: specifier: ^0.6.1 version: 0.6.6(prettier@3.3.3) prisma: - specifier: ^5.17.0 + specifier: ^5.18.0 version: 5.18.0 tailwindcss: specifier: ^3.3.0 @@ -1675,6 +1690,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.1.1': + resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-dialog@1.1.1': resolution: {integrity: sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==} peerDependencies: @@ -1845,6 +1869,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-presence@1.1.1': + resolution: {integrity: sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@2.0.0': resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} peerDependencies: @@ -1858,6 +1895,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-radio-group@1.2.1': + resolution: {integrity: sha512-kdbv54g4vfRjja9DNWPMxKvXblzqbpEC8kspEkZ6dVP7kQksGCn+iZHkcCz2nb00+lPdRvxrqy4WrvvV1cNqrQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-roving-focus@1.1.0': resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==} peerDependencies: @@ -2331,6 +2381,14 @@ packages: resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} + '@tabler/icons-react@3.19.0': + resolution: {integrity: sha512-AqEWGI0tQWgqo6ZjMO5yJ9sYT8oXLuAM/up0hN9iENS6IdtNZryKrkNSiMgpwweNTpl8wFFG/dAZ959S91A/uQ==} + peerDependencies: + react: '>= 16' + + '@tabler/icons@3.19.0': + resolution: {integrity: sha512-A4WEWqpdbTfnpFEtwXqwAe9qf9sp1yRPvzppqAuwcoF0q5YInqB+JkJtSFToCyBpPVeLxJUxxkapLvt2qQgnag==} + '@testing-library/dom@10.1.0': resolution: {integrity: sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==} engines: {node: '>=18'} @@ -2655,8 +2713,8 @@ packages: '@uiw/copy-to-clipboard@1.0.17': resolution: {integrity: sha512-O2GUHV90Iw2VrSLVLK0OmNIMdZ5fgEg4NhvtwINsX+eZ/Wf6DWD0TdsK9xwV7dNRnK/UI2mQtl0a2/kRgm1m1A==} - '@uiw/react-markdown-preview@5.1.2': - resolution: {integrity: sha512-IheBzajVKZ3NUhYAfrnnereJmKVNQ6vRZDjQhKEQy7IMarJXrUKC1eG8OzTgy1J/3m67MBTs6d52kkSB699efA==} + '@uiw/react-markdown-preview@5.1.3': + resolution: {integrity: sha512-jV02wO4XHWFk54kz7sLqOkdPgJLttSfKLyen47XgjcyGgQXU2I4WJBygmdpV2AT9m/MiQ8qrN1Y+E5Syv9ZDpw==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' @@ -4610,6 +4668,10 @@ packages: resolution: {integrity: sha512-UpzJy4yrnqnhXvRPhjEuLA4lcPn6eRngixW7Q3TJErjg3Aw2PuLFBzTkdUb89UtumxjhHTqL3a5GDGETMSwgJA==} hasBin: true + katex@0.16.11: + resolution: {integrity: sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -8725,6 +8787,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-context@1.1.1(@types/react@18.3.3)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-dialog@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -8923,6 +8991,16 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 + '@radix-ui/react-presence@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1) @@ -8932,6 +9010,24 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 + '@radix-ui/react-radio-group@1.2.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/react-roving-focus@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -9580,6 +9676,13 @@ snapshots: dependencies: defer-to-connect: 2.0.1 + '@tabler/icons-react@3.19.0(react@18.3.1)': + dependencies: + '@tabler/icons': 3.19.0 + react: 18.3.1 + + '@tabler/icons@3.19.0': {} + '@testing-library/dom@10.1.0': dependencies: '@babel/code-frame': 7.24.7 @@ -9974,7 +10077,7 @@ snapshots: '@uiw/copy-to-clipboard@1.0.17': {} - '@uiw/react-markdown-preview@5.1.2(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@uiw/react-markdown-preview@5.1.3(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.25.0 '@uiw/copy-to-clipboard': 1.0.17 @@ -9998,7 +10101,7 @@ snapshots: '@uiw/react-md-editor@4.0.4(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.25.0 - '@uiw/react-markdown-preview': 5.1.2(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@uiw/react-markdown-preview': 5.1.3(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) rehype: 13.0.1 @@ -12280,6 +12383,10 @@ snapshots: dependencies: commander: 8.3.0 + katex@0.16.11: + dependencies: + commander: 8.3.0 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 diff --git a/src/app/admin/add-course/page.tsx b/src/app/admin/add-course/page.tsx index dace4c5a8..d4b6d5609 100644 --- a/src/app/admin/add-course/page.tsx +++ b/src/app/admin/add-course/page.tsx @@ -1,12 +1,7 @@ 'use client'; import { Button } from '@/components/ui/button'; -import { - Card, - CardContent, - CardHeader, - CardTitle, -} from '@/components/ui/card'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Form, FormControl, @@ -29,7 +24,7 @@ import { AccordionContent, AccordionItem, AccordionTrigger, -} from "@/components/ui/accordion"; +} from '@/components/ui/accordion'; import { Cuboid, PackagePlus } from 'lucide-react'; import { FaDiscord } from 'react-icons/fa'; @@ -86,24 +81,32 @@ export default function Courses() { return (
- -
+
-

View Content

+

View Content

- + - -
- New course + +
+ + New course
-
-
Create new course for 100xdevs community and let user explore new courses
-
- +
+
+ Create new course for 100xdevs community and let user explore + new courses +
+
+ {/* Create a new course */} Fill in the course details below @@ -121,7 +124,11 @@ export default function Courses() { Title - + @@ -134,7 +141,11 @@ export default function Courses() { Image url - + @@ -144,11 +155,12 @@ export default function Courses() { control={form.control} name="description" render={({ field }: { field: any }) => ( - + Description