├── .env.example ├── .eslintignore ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── feature_request.yml │ └── improvement.yml └── workflows │ ├── cd.yaml │ ├── cd_prod.yaml │ └── ci.yaml ├── .gitignore ├── .husky └── pre-commit ├── .lintstagedrc.json ├── .prettierignore ├── .prettierrc ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.prod ├── README.md ├── components.json ├── docker-compose.yaml ├── next.config.js ├── package.json ├── postcss.config.mjs ├── prisma ├── migrations │ ├── 20240929090953_dev │ │ └── migration.sql │ ├── 20241007041046_added_deleted_field_job │ │ └── migration.sql │ ├── 20241014172510_job_upate │ │ └── migration.sql │ ├── 20241015192257_added_cascading_job │ │ └── migration.sql │ ├── 20241016110618_expiry_job │ │ └── migration.sql │ ├── 20241019104716_ │ │ └── migration.sql │ ├── 20241019174425_add_onboar_field │ │ └── migration.sql │ ├── 20241024174828_profileupdate │ │ └── migration.sql │ ├── 20241025095014_user_updated │ │ └── migration.sql │ ├── 20241025120951_resume_update_date │ │ └── migration.sql │ ├── 20241031043344_username_remove │ │ └── migration.sql │ ├── 20241031064849_company │ │ └── migration.sql │ └── migration_lock.toml ├── schema.prisma └── seed.ts ├── public ├── BG-Grid-Light.svg ├── BG-Grid.svg ├── adobe.svg ├── atlassian.svg ├── coinbase.svg ├── companies.png ├── fonts │ ├── font.woff2 │ └── satoshi.ttf ├── framer.svg ├── google.svg ├── main.png ├── main.svg ├── medium.svg ├── microsoft.svg ├── next.svg ├── robots.txt ├── solana.svg ├── spotify.png ├── spotify.svg └── vercel.svg ├── src ├── actions │ ├── auth.actions.ts │ ├── corn.ts │ ├── job.action.ts │ ├── skills.cron.ts │ ├── upload-to-cdn.ts │ └── user.profile.actions.ts ├── app │ ├── (auth) │ │ ├── forgot-password │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── reset-password │ │ │ └── [token] │ │ │ │ └── page.tsx │ │ ├── signin │ │ │ └── page.tsx │ │ ├── signup │ │ │ └── page.tsx │ │ ├── verify-email │ │ │ └── [token] │ │ │ │ ├── EmailVerificationLinkExpired.tsx │ │ │ │ └── page.tsx │ │ └── welcome │ │ │ └── page.tsx │ ├── [...404] │ │ └── page.tsx │ ├── admin │ │ └── page.tsx │ ├── api │ │ └── auth │ │ │ └── [...nextauth] │ │ │ └── route.ts │ ├── create-profile │ │ └── page.tsx │ ├── create │ │ └── page.tsx │ ├── editDetails │ │ └── page.tsx │ ├── favicon.ico │ ├── globals.css │ ├── jobs │ │ ├── [id] │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── loading.tsx │ │ └── page.tsx │ ├── layout.tsx │ ├── manage │ │ ├── jobs │ │ │ └── page.tsx │ │ └── recruiters │ │ │ └── page.tsx │ ├── page.tsx │ └── profile │ │ ├── [userId] │ │ ├── loading.tsx │ │ └── page.tsx │ │ └── layout.tsx ├── components │ ├── ApproveJobDialog.tsx │ ├── BackgroundSvg.tsx │ ├── BookmarkCardSkeletion.tsx │ ├── DeleteDialog.tsx │ ├── DescriptionEditor.tsx │ ├── Faqs.tsx │ ├── FaqsGetintouchCard.tsx │ ├── HalfCircleGradient.tsx │ ├── JobManagement.tsx │ ├── JobManagementHeader.tsx │ ├── JobManagementTable.tsx │ ├── Jobcard.tsx │ ├── JobcardSkeleton.tsx │ ├── ManageRecruiters.tsx │ ├── RecentJobs.tsx │ ├── ScrollToTop.tsx │ ├── ShareJobDialog.tsx │ ├── TestimonialCard.tsx │ ├── Testimonials.tsx │ ├── ToggleApproveJobButton.tsx │ ├── Toploader.tsx │ ├── UserCard.tsx │ ├── all-jobs.tsx │ ├── auth │ │ ├── forgot-password.tsx │ │ ├── reset-password.tsx │ │ ├── signin.tsx │ │ ├── signup.tsx │ │ ├── social-auth.tsx │ │ └── welcome.tsx │ ├── comboBox.tsx │ ├── gmaps-autosuggest.tsx │ ├── hero-section.tsx │ ├── infinitescroll.tsx │ ├── job-card-rec.tsx │ ├── job-creation-success.tsx │ ├── job-form.tsx │ ├── job-landing.tsx │ ├── job-skill.tsx │ ├── job-skills.tsx │ ├── job.tsx │ ├── loader.tsx │ ├── loading-spinner.tsx │ ├── navitem.tsx │ ├── pagination-client.tsx │ ├── password-input.tsx │ ├── profile-menu.tsx │ ├── profile │ │ ├── AboutMe.tsx │ │ ├── AccountSettings.tsx │ │ ├── ChangePassword.tsx │ │ ├── DeleteAccountDialog.tsx │ │ ├── EditProfile.tsx │ │ ├── EditProfilePicture.tsx │ │ ├── EducationDeleteDialog.tsx │ │ ├── ExperienceDeleteDialog.tsx │ │ ├── ProfileEducation.tsx │ │ ├── ProfileExperience.tsx │ │ ├── ProfileHeroSection.tsx │ │ ├── ProfileHireme.tsx │ │ ├── ProfileInfo.tsx │ │ ├── ProfileProject.tsx │ │ ├── ProfileProjects.tsx │ │ ├── ProfileResume.tsx │ │ ├── ProfileShare.tsx │ │ ├── ProfileSkills.tsx │ │ ├── ProfileSocials.tsx │ │ ├── UserExperience.tsx │ │ ├── UserProject.tsx │ │ ├── UserResume.tsx │ │ ├── UserSkills.tsx │ │ ├── emptycontainers │ │ │ └── ProfileEmptyContainers.tsx │ │ ├── forms │ │ │ ├── AccountSeetingForm.tsx │ │ │ ├── EditProfileForm.tsx │ │ │ ├── EducationForm.tsx │ │ │ ├── ExperienceForm.tsx │ │ │ ├── ProjectForm.tsx │ │ │ ├── ReadMeForm.tsx │ │ │ ├── SkillsForm.tsx │ │ │ └── UploadResumeForm.tsx │ │ ├── profile-skills-combobox.tsx │ │ ├── profileComboBox.tsx │ │ ├── projectDeleteDialog.tsx │ │ ├── resumeDeleteDialog.tsx │ │ ├── sheets │ │ │ └── SheetWrapper.tsx │ │ └── sidebar.tsx │ ├── skills-combobox.tsx │ ├── toggleJobButton.tsx │ ├── ui │ │ ├── Marquee.tsx │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── checkbox.tsx │ │ ├── command.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── icon.tsx │ │ ├── infinite-moving-cards.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── pagination.tsx │ │ ├── paginator.tsx │ │ ├── popover.tsx │ │ ├── radio-group.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── textarea.tsx │ │ ├── theme-toggle.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ └── use-toast.ts │ ├── user-multistep-form │ │ ├── add-project-form.tsx │ │ ├── add-resume-form.tsx │ │ ├── add-skills-form.tsx │ │ ├── addExperience-form.tsx │ │ └── user-multistep-form.tsx │ └── userDetails.tsx ├── config │ ├── app.config.ts │ ├── auth.config.ts │ ├── error.config.ts │ ├── path.config.ts │ ├── prisma.config.ts │ └── skillapi.auth.token.ts ├── env │ ├── client.ts │ └── server.ts ├── hooks │ ├── sample.ts │ ├── useFilterCheck.ts │ └── useSetQueryParams.ts ├── layouts │ ├── footer.tsx │ ├── form-container.tsx │ ├── header.tsx │ ├── job-filters.tsx │ ├── jobs-header.tsx │ └── mobile-nav.tsx ├── lib │ ├── admin.ts │ ├── async-catch.ts │ ├── auth.ts │ ├── authOptions.ts │ ├── constant │ │ ├── app.constant.ts │ │ ├── faqs.constants.ts │ │ ├── jobs.constant.ts │ │ ├── profile.constant.ts │ │ └── testimonials.constants.ts │ ├── error.ts │ ├── icons.ts │ ├── sendConfirmationEmail.ts │ ├── session.ts │ ├── success.ts │ ├── utils.ts │ └── validators │ │ ├── auth.validator.ts │ │ ├── jobs.validator.ts │ │ └── user.profile.validator.ts ├── middleware.ts ├── providers │ ├── auth-provider.tsx │ ├── providers.tsx │ └── theme-provider.tsx ├── services │ └── jobs.services.ts └── types │ ├── api.types.ts │ ├── faqs.types.ts │ ├── jobs.types.ts │ ├── next-auth.d.ts │ ├── recruiters.types.ts │ ├── testimonials.types.ts │ └── user.types.ts ├── tailwind.config.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | # 2 | # Database 3 | # 4 | DATABASE_URL="postgres://postgres:password@localhost:5432/postgres" 5 | # 6 | # AUTH 7 | # 8 | NEXTAUTH_SECRET="koXrQGB5TFD4KALDX4kAvnQ5RHHvAOIzB" 9 | NEXTAUTH_URL="http://localhost:3000" 10 | # 11 | # Bunny CDN 12 | # 13 | CDN_API_KEY=api-key 14 | CDN_BASE_UPLOAD_URL=https://sg.storage.bunnycdn.com/job-board/assets 15 | CDN_BASE_ACCESS_URL=https://job-board.b-cdn.net/assets 16 | 17 | NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=maps-api-key 18 | 19 | # 20 | # Email SMTP credentials 21 | # 22 | EMAIL_USER= # your email ex: vineetagarwal@gmail.com 23 | EMAIL_PASSWORD= 24 | EMAIL_SERVICE=gmail 25 | EMAIL_HOST=smtp.gmail.com 26 | EMAIL_PORT=587 27 | 28 | # 29 | # Google OAuth credentials 30 | # 31 | GOOGLE_CLIENT_ID= 32 | GOOGLE_CLIENT_SECRET= 33 | 34 | # go to https://lightcast.io/open-skills and signup to recieve your credentials 35 | LIGHTCAST_CLIENT_ID= 36 | LIGHTCAST_CLIENT_SECRET= 37 | 38 | # To run the application in production environment / check the envs 39 | # SKIP_ENV_CHECK=true npm run [replace with your script name] 40 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.d.ts 2 | .eslintrc 3 | .prettierrc 4 | .prettierignore 5 | .next -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint"], 5 | "rules": { 6 | "no-console": ["error", { "allow": ["error"] }], 7 | "eqeqeq": "error", 8 | "@typescript-eslint/no-unused-vars": [ 9 | "error", 10 | { 11 | "vars": "all", 12 | "args": "after-used", 13 | "caughtErrors": "all", 14 | "ignoreRestSiblings": true, 15 | "reportUsedIgnorePattern": false, 16 | "caughtErrorsIgnorePattern": "^_" 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Feature 2 | description: 'Submit a proposal for a new feature' 3 | title: '🚀 Feature: ' 4 | labels: [feature] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | We value your time and efforts to submit this Feature request form. 🙏 10 | - type: textarea 11 | id: feature-description 12 | validations: 13 | required: true 14 | attributes: 15 | label: '🔖 Feature description' 16 | description: 'A clear and concise description of what the feature is.' 17 | placeholder: 'You should add ...' 18 | - type: textarea 19 | id: pitch 20 | validations: 21 | required: true 22 | attributes: 23 | label: '🎤 Why is this feature needed ?' 24 | description: 'Please explain why this feature should be implemented and how it would be used. Add examples, if applicable.' 25 | placeholder: 'In my use-case, ...' 26 | - type: textarea 27 | id: solution 28 | validations: 29 | required: true 30 | attributes: 31 | label: '✌️ How do you aim to achieve this?' 32 | description: 'A clear and concise description of what you want to happen.' 33 | placeholder: 'I want this feature to, ...' 34 | - type: textarea 35 | id: alternative 36 | validations: 37 | required: false 38 | attributes: 39 | label: '🔄️ Additional Information' 40 | description: "A clear and concise description of any alternative solutions or additional solutions you've considered." 41 | placeholder: 'I tried, ...' 42 | - type: checkboxes 43 | id: no-duplicate-issues 44 | attributes: 45 | label: '👀 Have you spent some time to check if this feature request has been raised before?' 46 | options: 47 | - label: "I checked and didn't find similar issue" 48 | required: true 49 | - type: checkboxes 50 | id: read-code-of-conduct 51 | attributes: 52 | label: '🏢 Have you read the Code of Conduct?' 53 | options: 54 | - label: 'I have read the [Contributing Guidelines](https://github.com/code100x/job-board/blob/main/CONTRIBUTING.md)' 55 | required: true 56 | - type: dropdown 57 | id: willing-to-submit-pr 58 | attributes: 59 | label: Are you willing to submit PR? 60 | description: This is absolutely not required, but we are happy to guide you in the contribution process. 61 | options: 62 | - 'Yes I am willing to submit a PR!' 63 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/improvement.yml: -------------------------------------------------------------------------------- 1 | name: '✨ Improvement' 2 | description: 'Submit a improvement report to help us improve' 3 | title: '✨ Improvement: ' 4 | labels: ['improvement'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: We value your time and effort to submit this improvement report. 🙏 9 | - type: textarea 10 | id: description 11 | validations: 12 | required: true 13 | attributes: 14 | label: '📜 Description' 15 | description: 'A clear and concise description of what the improvement is.' 16 | placeholder: "It's a improvement when ..." 17 | - type: textarea 18 | id: steps-to-reproduce 19 | attributes: 20 | label: '👟 Reproduction steps' 21 | description: 'How do you encountered this behaviour? Please walk us through it step by step.' 22 | placeholder: "1. Go to '...' 23 | 2. Click on '....' 24 | 3. Scroll down to '....' 25 | 4. See error" 26 | - type: textarea 27 | id: expected-behavior 28 | attributes: 29 | label: '👍 Expected behavior' 30 | description: 'What did you think should happen?' 31 | placeholder: 'It should ...' 32 | - type: textarea 33 | id: additional-context 34 | validations: 35 | required: false 36 | attributes: 37 | label: '📃 Provide any additional context for the Bug.' 38 | description: 'Add any other context about the problem here.' 39 | placeholder: 'It actually ...' 40 | - type: checkboxes 41 | id: no-duplicate-issues 42 | attributes: 43 | label: '👀 Have you spent some time to check if this bug has been raised before?' 44 | options: 45 | - label: "I checked and didn't find similar issue" 46 | required: true 47 | - type: checkboxes 48 | id: read-code-of-conduct 49 | attributes: 50 | label: '🏢 Have you read the Contributing Guidelines?' 51 | options: 52 | - label: 'I have read the [Contributing Guidelines](https://github.com/code100x/job-board/blob/main/CONTRIBUTING.md)' 53 | required: true 54 | -------------------------------------------------------------------------------- /.github/workflows/cd.yaml: -------------------------------------------------------------------------------- 1 | name: Continuous Deployment 2 | on: 3 | push: 4 | branches: [ main ] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | with: 11 | fetch-depth: 0 12 | 13 | - name: Docker login 14 | uses: docker/login-action@v2 15 | with: 16 | username: ${{ secrets.DOCKERHUB_USERNAME }} 17 | password: ${{ secrets.DOCKERHUB_TOKEN }} 18 | 19 | - name: Set up Docker Buildx 20 | uses: docker/setup-buildx-action@v2 21 | 22 | - name: Build and push 23 | uses: docker/build-push-action@v4 24 | with: 25 | context: . 26 | file: ./Dockerfile.prod 27 | push: true 28 | tags: 100xdevs/job-board-staging:${{ github.sha }} 29 | 30 | - name: Clone staging-ops repo, update, and push 31 | env: 32 | PAT: ${{ secrets.PAT }} 33 | run: | 34 | git clone https://github.com/code100x/staging-ops.git 35 | cd staging-ops 36 | sed -i 's|image: 100xdevs/job-board-staging:.*|image: 100xdevs/job-board-staging:${{ github.sha }}|' staging/job-board/deployment.yml 37 | git config user.name "GitHub Actions Bot" 38 | git config user.email "actions@github.com" 39 | git add staging/job-board/deployment.yml 40 | git commit -m "Update job-board-staging image to ${{ github.sha }}" 41 | git push https://${PAT}@github.com/code100x/staging-ops.git main -------------------------------------------------------------------------------- /.github/workflows/cd_prod.yaml: -------------------------------------------------------------------------------- 1 | name: Continuous Deployment (Prod) 2 | on: 3 | push: 4 | branches: [ production ] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | with: 11 | fetch-depth: 0 12 | 13 | - name: Docker login 14 | uses: docker/login-action@v2 15 | with: 16 | username: ${{ secrets.DOCKERHUB_USERNAME }} 17 | password: ${{ secrets.DOCKERHUB_TOKEN }} 18 | 19 | - name: Set up Docker Buildx 20 | uses: docker/setup-buildx-action@v2 21 | 22 | - name: Build and push 23 | uses: docker/build-push-action@v4 24 | with: 25 | context: . 26 | file: ./Dockerfile.prod 27 | push: true 28 | tags: 100xdevs/job-board:${{ github.sha }} 29 | 30 | - name: Clone staging-ops repo, update, and push 31 | env: 32 | PAT: ${{ secrets.PAT }} 33 | run: | 34 | git clone https://github.com/code100x/staging-ops.git 35 | cd staging-ops 36 | sed -i 's|image: 100xdevs/job-board:.*|image: 100xdevs/job-board:${{ github.sha }}|' prod/job-board/deployment.yml 37 | git config user.name "GitHub Actions Bot" 38 | git config user.email "actions@github.com" 39 | git add prod/job-board/deployment.yml 40 | git commit -m "Update job-board image to ${{ github.sha }}" 41 | git push https://${PAT}@github.com/code100x/staging-ops.git main -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | env: 4 | SKIP_ENV_CHECK: 'true' 5 | 6 | on: 7 | push: 8 | branches: [main] 9 | pull_request: 10 | branches: [main] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Use Node.js 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: '20.9.0' 25 | 26 | - name: Install dependencies 27 | run: npm install 28 | 29 | - name: Run lint 30 | run: npm run lint 31 | 32 | - name: Run format 33 | run: npm run format 34 | 35 | - name: Run format check 36 | run: npm run check 37 | 38 | - name: Run build 39 | run: npm run build 40 | -------------------------------------------------------------------------------- /.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 | .yarn/install-state.gz 8 | package-lock.json 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | /out/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env*.local 31 | .env 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | pnpm-lock.yaml 40 | bun.lockb 41 | package-lock.json 42 | yarn.lock 43 | 44 | **/public/sw.js 45 | **/public/workbox-*.js 46 | **/public/worker-*.js 47 | **/public/sw.js.map 48 | **/public/workbox-*.js.map 49 | **/public/worker-*.js.map -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "src/**/*.{js,jsx,ts,tsx,json}": ["eslint --fix"], 3 | "src/**/*.{js,jsx,ts,tsx,json,css,md}": ["prettier --write"] 4 | } -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .next 4 | /package-lock.json 5 | *.yaml 6 | build 7 | .env 8 | out -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": true, 5 | "singleQuote": true, 6 | "trailingComma": "es5", 7 | "printWidth": 80, 8 | "bracketSpacing": true, 9 | "bracketSameLine": false, 10 | "arrowParens": "always", 11 | "endOfLine": "auto" 12 | } 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine AS deps 2 | WORKDIR /app 3 | COPY package*.json ./ 4 | RUN npm i 5 | 6 | FROM node:20-alpine AS builder 7 | WORKDIR /app 8 | COPY . . 9 | COPY --from=deps /app/node_modules ./node_modules 10 | RUN npx prisma generate 11 | RUN npm run build 12 | 13 | FROM node:20-alpine AS runner 14 | WORKDIR /app 15 | COPY --from=builder /app/.next/standalone ./ 16 | COPY --from=builder /app/.next/static ./.next/static 17 | COPY --from=builder /app/public ./public 18 | COPY --from=builder /app/package.json ./ 19 | 20 | EXPOSE 3000 21 | ENV PORT 3000 22 | CMD ["node", "server.js"] 23 | -------------------------------------------------------------------------------- /Dockerfile.prod: -------------------------------------------------------------------------------- 1 | 2 | FROM node:20-alpine AS build 3 | ARG DATABASE_URL 4 | WORKDIR /usr/src/app 5 | COPY package*.json ./ 6 | RUN npm install 7 | COPY . . 8 | RUN DATABASE_URL=$DATABASE_URL npx prisma generate 9 | RUN DATABASE_URL=$DATABASE_URL npm run build 10 | 11 | FROM node:20-alpine AS production 12 | WORKDIR /usr/src/app 13 | COPY --from=build /usr/src/app/.next ./.next 14 | COPY --from=build /usr/src/app/node_modules ./node_modules 15 | COPY --from=build /usr/src/app/public ./public 16 | COPY --from=build /usr/src/app/package.json ./package.json 17 | CMD ["npm", "run", "start"] 18 | 19 | EXPOSE 3000 -------------------------------------------------------------------------------- /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.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: 4 | dockerfile: Dockerfile 5 | target: development # Change this to production for prod builds (WIP) 6 | container_name: job-board-app 7 | environment: 8 | - DATABASE_URL=$DATABASE_URL 9 | - AUTH_SECRET=$AUTH_SECRET 10 | ports: 11 | - "3000:3000" 12 | volumes: 13 | - .:/usr/src/app 14 | - /usr/src/app/node_modules 15 | depends_on: 16 | db: 17 | condition: service_healthy 18 | 19 | db: 20 | image: postgres:16-alpine 21 | container_name: job-board-db 22 | restart: always 23 | environment: 24 | POSTGRES_USER: postgres 25 | POSTGRES_PASSWORD: postgres 26 | POSTGRES_DB: job-board-db 27 | ports: 28 | - 5432:5432 29 | volumes: 30 | - postgres-data:/var/lib/postgresql/data 31 | healthcheck: 32 | test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"] 33 | interval: 10s 34 | timeout: 5s 35 | retries: 5 36 | 37 | prisma-studio: 38 | container_name: prisma-studio 39 | image: timothyjmiller/prisma-studio:latest 40 | restart: unless-stopped 41 | env_file: 42 | - .env 43 | depends_on: 44 | - app 45 | ports: 46 | - 5555:5555 47 | 48 | volumes: 49 | postgres-data: -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | // next.config.js 2 | import { fileURLToPath } from 'node:url'; 3 | import createJiti from 'jiti'; 4 | 5 | if (process.env.SKIP_ENV_CHECK !== 'true') { 6 | const jiti = createJiti(fileURLToPath(import.meta.url)); 7 | jiti('./src/env/client'); 8 | jiti('./src/env/server'); 9 | } 10 | 11 | /** @type {import('next').NextConfig} */ 12 | const nextConfig = { 13 | output: 'standalone', 14 | reactStrictMode: true, 15 | logging: { 16 | fetches: { 17 | fullUrl: true, 18 | }, 19 | }, 20 | images: { 21 | remotePatterns: [ 22 | { 23 | protocol: 'https', 24 | hostname: 'job-board.b-cdn.net', 25 | }, 26 | { 27 | protocol: 'https', 28 | hostname: 'lh3.googleusercontent.com', 29 | }, 30 | { 31 | protocol: 'https', 32 | hostname: 'aakash2330.b-cdn.net', 33 | }, 34 | { 35 | protocol: 'https', 36 | hostname: 'www.example.com', 37 | } 38 | ], 39 | }, 40 | }; 41 | 42 | export default nextConfig; 43 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /prisma/migrations/20240929090953_dev/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "OauthProvider" AS ENUM ('GOOGLE'); 3 | 4 | -- CreateEnum 5 | CREATE TYPE "TokenType" AS ENUM ('EMAIL_VERIFICATION', 'RESET_PASSWORD'); 6 | 7 | -- CreateEnum 8 | CREATE TYPE "Currency" AS ENUM ('INR', 'USD'); 9 | 10 | -- CreateEnum 11 | CREATE TYPE "WorkMode" AS ENUM ('remote', 'hybrid', 'office'); 12 | 13 | -- CreateEnum 14 | CREATE TYPE "Role" AS ENUM ('USER', 'ADMIN'); 15 | 16 | -- CreateEnum 17 | CREATE TYPE "EmployementType" AS ENUM ('Full_time', 'Part_time', 'Internship', 'Contract'); 18 | 19 | -- CreateTable 20 | CREATE TABLE "User" ( 21 | "id" TEXT NOT NULL, 22 | "name" TEXT NOT NULL, 23 | "password" TEXT, 24 | "avatar" TEXT, 25 | "isVerified" BOOLEAN NOT NULL DEFAULT false, 26 | "role" "Role" NOT NULL DEFAULT 'USER', 27 | "email" TEXT NOT NULL, 28 | "emailVerified" TIMESTAMP(3), 29 | "oauthProvider" "OauthProvider", 30 | "oauthId" TEXT, 31 | "blockedByAdmin" TIMESTAMP(3), 32 | 33 | CONSTRAINT "User_pkey" PRIMARY KEY ("id") 34 | ); 35 | 36 | -- CreateTable 37 | CREATE TABLE "VerificationToken" ( 38 | "token" TEXT NOT NULL, 39 | "identifier" TEXT NOT NULL, 40 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 41 | "updatedAt" TIMESTAMP(3) NOT NULL, 42 | "type" "TokenType" NOT NULL 43 | ); 44 | 45 | -- CreateTable 46 | CREATE TABLE "Job" ( 47 | "id" TEXT NOT NULL, 48 | "userId" TEXT NOT NULL, 49 | "title" TEXT NOT NULL, 50 | "description" TEXT, 51 | "company_name" TEXT NOT NULL, 52 | "company_bio" TEXT NOT NULL, 53 | "company_email" TEXT NOT NULL, 54 | "category" TEXT NOT NULL, 55 | "type" "EmployementType" NOT NULL, 56 | "work_mode" "WorkMode" NOT NULL, 57 | "currency" "Currency" NOT NULL DEFAULT 'INR', 58 | "city" TEXT NOT NULL, 59 | "address" TEXT NOT NULL, 60 | "application" TEXT NOT NULL, 61 | "companyLogo" TEXT NOT NULL, 62 | "skills" TEXT[], 63 | "has_salary_range" BOOLEAN NOT NULL DEFAULT false, 64 | "minSalary" INTEGER, 65 | "maxSalary" INTEGER, 66 | "has_experience_range" BOOLEAN NOT NULL DEFAULT false, 67 | "minExperience" INTEGER, 68 | "maxExperience" INTEGER, 69 | "is_verified_job" BOOLEAN NOT NULL DEFAULT false, 70 | "postedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 71 | "updatedAt" TIMESTAMP(3) NOT NULL, 72 | 73 | CONSTRAINT "Job_pkey" PRIMARY KEY ("id") 74 | ); 75 | 76 | -- CreateIndex 77 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 78 | 79 | -- CreateIndex 80 | CREATE UNIQUE INDEX "VerificationToken_token_identifier_key" ON "VerificationToken"("token", "identifier"); 81 | 82 | -- AddForeignKey 83 | ALTER TABLE "Job" ADD CONSTRAINT "Job_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 84 | -------------------------------------------------------------------------------- /prisma/migrations/20241007041046_added_deleted_field_job/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Job" ADD COLUMN "deleted" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241014172510_job_upate/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "Role" ADD VALUE 'HR'; 3 | 4 | -- AlterTable 5 | ALTER TABLE "User" ADD COLUMN "onBoard" BOOLEAN NOT NULL DEFAULT false, 6 | ADD COLUMN "resume" TEXT, 7 | ADD COLUMN "skills" TEXT[]; 8 | 9 | -- CreateTable 10 | CREATE TABLE "Experience" ( 11 | "id" SERIAL NOT NULL, 12 | "companyName" TEXT NOT NULL, 13 | "designation" TEXT NOT NULL, 14 | "EmploymentType" "EmployementType" NOT NULL, 15 | "address" TEXT NOT NULL, 16 | "workMode" "WorkMode" NOT NULL, 17 | "currentWorkStatus" BOOLEAN NOT NULL, 18 | "startDate" TIMESTAMP(3) NOT NULL, 19 | "endDate" TIMESTAMP(3), 20 | "description" TEXT NOT NULL, 21 | "userId" TEXT NOT NULL, 22 | 23 | CONSTRAINT "Experience_pkey" PRIMARY KEY ("id") 24 | ); 25 | 26 | -- CreateTable 27 | CREATE TABLE "Project" ( 28 | "id" SERIAL NOT NULL, 29 | "projectName" TEXT NOT NULL, 30 | "projectSummary" TEXT NOT NULL, 31 | "projectLiveLink" TEXT, 32 | "projectGithub" TEXT NOT NULL, 33 | "userId" TEXT NOT NULL, 34 | 35 | CONSTRAINT "Project_pkey" PRIMARY KEY ("id") 36 | ); 37 | 38 | -- AddForeignKey 39 | ALTER TABLE "Experience" ADD CONSTRAINT "Experience_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 40 | 41 | -- AddForeignKey 42 | ALTER TABLE "Project" ADD CONSTRAINT "Project_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 43 | -------------------------------------------------------------------------------- /prisma/migrations/20241015192257_added_cascading_job/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "Job" DROP CONSTRAINT "Job_userId_fkey"; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "Job" ADD CONSTRAINT "Job_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 6 | -------------------------------------------------------------------------------- /prisma/migrations/20241016110618_expiry_job/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `onBoard` on the `User` table. All the data in the column will be lost. 5 | - You are about to drop the column `resume` on the `User` table. All the data in the column will be lost. 6 | - You are about to drop the column `skills` on the `User` table. All the data in the column will be lost. 7 | - You are about to drop the `Experience` table. If the table is not empty, all the data it contains will be lost. 8 | - You are about to drop the `Project` table. If the table is not empty, all the data it contains will be lost. 9 | 10 | */ 11 | -- DropForeignKey 12 | ALTER TABLE "Experience" DROP CONSTRAINT "Experience_userId_fkey"; 13 | 14 | -- DropForeignKey 15 | ALTER TABLE "Project" DROP CONSTRAINT "Project_userId_fkey"; 16 | 17 | -- AlterTable 18 | ALTER TABLE "Job" ADD COLUMN "expired" BOOLEAN NOT NULL DEFAULT false, 19 | ADD COLUMN "expiryDate" TIMESTAMP(3), 20 | ADD COLUMN "has_expiry_date" BOOLEAN NOT NULL DEFAULT false; 21 | 22 | -- AlterTable 23 | ALTER TABLE "User" DROP COLUMN "onBoard", 24 | DROP COLUMN "resume", 25 | DROP COLUMN "skills"; 26 | 27 | -- DropTable 28 | DROP TABLE "Experience"; 29 | 30 | -- DropTable 31 | DROP TABLE "Project"; 32 | -------------------------------------------------------------------------------- /prisma/migrations/20241019104716_/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Bookmark" ( 3 | "id" TEXT NOT NULL, 4 | "jobId" TEXT NOT NULL, 5 | "userId" TEXT NOT NULL, 6 | 7 | CONSTRAINT "Bookmark_pkey" PRIMARY KEY ("id") 8 | ); 9 | 10 | -- AddForeignKey 11 | ALTER TABLE "Bookmark" ADD CONSTRAINT "Bookmark_jobId_fkey" FOREIGN KEY ("jobId") REFERENCES "Job"("id") ON DELETE CASCADE ON UPDATE CASCADE; 12 | 13 | -- AddForeignKey 14 | ALTER TABLE "Bookmark" ADD CONSTRAINT "Bookmark_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 15 | -------------------------------------------------------------------------------- /prisma/migrations/20241019174425_add_onboar_field/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "User" ADD COLUMN "onBoard" BOOLEAN NOT NULL DEFAULT false, 3 | ADD COLUMN "resume" TEXT, 4 | ADD COLUMN "skills" TEXT[]; 5 | 6 | -- CreateTable 7 | CREATE TABLE "Experience" ( 8 | "id" SERIAL NOT NULL, 9 | "companyName" TEXT NOT NULL, 10 | "designation" TEXT NOT NULL, 11 | "EmploymentType" "EmployementType" NOT NULL, 12 | "address" TEXT NOT NULL, 13 | "workMode" "WorkMode" NOT NULL, 14 | "currentWorkStatus" BOOLEAN NOT NULL, 15 | "startDate" TIMESTAMP(3) NOT NULL, 16 | "endDate" TIMESTAMP(3), 17 | "description" TEXT NOT NULL, 18 | "userId" TEXT NOT NULL, 19 | 20 | CONSTRAINT "Experience_pkey" PRIMARY KEY ("id") 21 | ); 22 | 23 | -- CreateTable 24 | CREATE TABLE "Project" ( 25 | "id" SERIAL NOT NULL, 26 | "projectName" TEXT NOT NULL, 27 | "projectSummary" TEXT NOT NULL, 28 | "projectLiveLink" TEXT, 29 | "projectGithub" TEXT NOT NULL, 30 | "userId" TEXT NOT NULL, 31 | 32 | CONSTRAINT "Project_pkey" PRIMARY KEY ("id") 33 | ); 34 | 35 | -- AddForeignKey 36 | ALTER TABLE "Experience" ADD CONSTRAINT "Experience_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 37 | 38 | -- AddForeignKey 39 | ALTER TABLE "Project" ADD CONSTRAINT "Project_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 40 | -------------------------------------------------------------------------------- /prisma/migrations/20241024174828_profileupdate/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[username]` on the table `User` will be added. If there are existing duplicate values, this will fail. 5 | - Added the required column `aboutMe` to the `User` table without a default value. This is not possible if the table is not empty. 6 | - Added the required column `contactEmail` to the `User` table without a default value. This is not possible if the table is not empty. 7 | - Added the required column `username` to the `User` table without a default value. This is not possible if the table is not empty. 8 | 9 | */ 10 | -- CreateEnum 11 | CREATE TYPE "ProjectStack" AS ENUM ('GO', 'PYTHON', 'MERN', 'NEXTJS', 'AI_GPT_APIS', 'SPRINGBOOT', 'OTHERS'); 12 | 13 | -- CreateEnum 14 | CREATE TYPE "DegreeType" AS ENUM ('BTech', 'MTech', 'BCA', 'MCA'); 15 | 16 | -- CreateEnum 17 | CREATE TYPE "FieldOfStudyType" AS ENUM ('AI', 'Machine_Learning', 'CS', 'Mechanical'); 18 | 19 | -- DropForeignKey 20 | ALTER TABLE "Experience" DROP CONSTRAINT "Experience_userId_fkey"; 21 | 22 | -- DropForeignKey 23 | ALTER TABLE "Project" DROP CONSTRAINT "Project_userId_fkey"; 24 | 25 | -- AlterTable 26 | ALTER TABLE "Job" ADD COLUMN "deletedAt" TIMESTAMP(3); 27 | 28 | -- AlterTable 29 | ALTER TABLE "Project" ADD COLUMN "isFeature" BOOLEAN NOT NULL DEFAULT false, 30 | ADD COLUMN "projectThumbnail" TEXT, 31 | ADD COLUMN "stack" "ProjectStack" NOT NULL DEFAULT 'OTHERS'; 32 | 33 | -- AlterTable 34 | ALTER TABLE "User" ADD COLUMN "aboutMe" TEXT NOT NULL, 35 | ADD COLUMN "contactEmail" TEXT NOT NULL, 36 | ADD COLUMN "discordLink" TEXT, 37 | ADD COLUMN "githubLink" TEXT, 38 | ADD COLUMN "linkedinLink" TEXT, 39 | ADD COLUMN "portfolioLink" TEXT, 40 | ADD COLUMN "twitterLink" TEXT, 41 | ADD COLUMN "username" TEXT NOT NULL; 42 | 43 | -- CreateTable 44 | CREATE TABLE "Education" ( 45 | "id" SERIAL NOT NULL, 46 | "instituteName" TEXT NOT NULL, 47 | "degree" "DegreeType" NOT NULL, 48 | "fieldOfStudy" "FieldOfStudyType" NOT NULL, 49 | "startDate" TIMESTAMP(3) NOT NULL, 50 | "endDate" TIMESTAMP(3), 51 | "userId" TEXT NOT NULL, 52 | 53 | CONSTRAINT "Education_pkey" PRIMARY KEY ("id") 54 | ); 55 | 56 | -- CreateIndex 57 | CREATE UNIQUE INDEX "User_username_key" ON "User"("username"); 58 | 59 | -- AddForeignKey 60 | ALTER TABLE "Experience" ADD CONSTRAINT "Experience_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 61 | 62 | -- AddForeignKey 63 | ALTER TABLE "Education" ADD CONSTRAINT "Education_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 64 | 65 | -- AddForeignKey 66 | ALTER TABLE "Project" ADD CONSTRAINT "Project_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 67 | -------------------------------------------------------------------------------- /prisma/migrations/20241025095014_user_updated/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "User" ALTER COLUMN "aboutMe" DROP NOT NULL, 3 | ALTER COLUMN "contactEmail" DROP NOT NULL; 4 | -------------------------------------------------------------------------------- /prisma/migrations/20241025120951_resume_update_date/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "User" ADD COLUMN "resumeUpdateDate" TIMESTAMP(3); 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241031043344_username_remove/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `username` on the `User` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- DropIndex 8 | DROP INDEX "User_username_key"; 9 | 10 | -- AlterTable 11 | ALTER TABLE "User" DROP COLUMN "username"; 12 | -------------------------------------------------------------------------------- /prisma/migrations/20241031064849_company/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[companyId]` on the table `User` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "User" ADD COLUMN "companyId" TEXT, 9 | ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; 10 | 11 | -- CreateTable 12 | CREATE TABLE "Company" ( 13 | "id" TEXT NOT NULL, 14 | "companyName" TEXT NOT NULL, 15 | "companyLogo" TEXT, 16 | "companyEmail" TEXT NOT NULL, 17 | "companyBio" TEXT NOT NULL, 18 | 19 | CONSTRAINT "Company_pkey" PRIMARY KEY ("id") 20 | ); 21 | 22 | -- CreateIndex 23 | CREATE UNIQUE INDEX "User_companyId_key" ON "User"("companyId"); 24 | 25 | -- AddForeignKey 26 | ALTER TABLE "User" ADD CONSTRAINT "User_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE SET NULL ON UPDATE CASCADE; 27 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /public/companies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/job-board/2ed4b46a0d532fb8edab3d02a9fc416f53847f01/public/companies.png -------------------------------------------------------------------------------- /public/fonts/font.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/job-board/2ed4b46a0d532fb8edab3d02a9fc416f53847f01/public/fonts/font.woff2 -------------------------------------------------------------------------------- /public/fonts/satoshi.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/job-board/2ed4b46a0d532fb8edab3d02a9fc416f53847f01/public/fonts/satoshi.ttf -------------------------------------------------------------------------------- /public/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/job-board/2ed4b46a0d532fb8edab3d02a9fc416f53847f01/public/main.png -------------------------------------------------------------------------------- /public/main.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | Disallow: /admin/* 4 | Disallow: /manage/* 5 | 6 | Sitemap: https://job.vineet.tech/sitemap.xml -------------------------------------------------------------------------------- /public/spotify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/job-board/2ed4b46a0d532fb8edab3d02a9fc416f53847f01/public/spotify.png -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/actions/corn.ts: -------------------------------------------------------------------------------- 1 | // lib/cron.ts 2 | import cron from 'node-cron'; 3 | import { deleteOldDeltedJobs, updateExpiredJobs } from './job.action'; 4 | 5 | let cronJobInitialized = false; 6 | 7 | export const startCronJob = () => { 8 | if (!cronJobInitialized) { 9 | cronJobInitialized = true; 10 | 11 | // Schedule the job to run at midnight (12:00 AM) every day 12 | cron.schedule('0 0 * * *', async () => { 13 | try { 14 | await updateExpiredJobs(); 15 | await deleteOldDeltedJobs(); 16 | } catch (error) { 17 | console.error('Error updating expired jobs:', error); 18 | } 19 | }); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /src/actions/skills.cron.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | /* eslint-disable no-console */ 3 | import { NextResponse } from 'next/server'; 4 | import { bearerToken } from '@/config/skillapi.auth.token'; 5 | 6 | var cron = require('node-cron'); 7 | 8 | const url = 'https://auth.emsicloud.com/connect/token'; 9 | 10 | async function startAuthTokenCronJob() { 11 | // don't run the cron job if its already running 12 | await fetchAuthTokenCronJob(); 13 | // Schedule the cron to run every 45 minutes , the token resets after one hour 14 | cron.schedule('*/45 * * * *', async () => { 15 | await fetchAuthTokenCronJob(); 16 | }); 17 | return NextResponse.json({ data: 'Success', status: 200 }); 18 | } 19 | 20 | const options = { 21 | method: 'POST', 22 | headers: { 23 | 'Content-Type': 'application/x-www-form-urlencoded', 24 | }, 25 | body: new URLSearchParams({ 26 | client_id: process.env.LIGHTCAST_CLIENT_ID ?? '', 27 | client_secret: process.env.LIGHTCAST_CLIENT_SECRET ?? '', 28 | grant_type: 'client_credentials', 29 | scope: 'emsi_open', 30 | }), 31 | }; 32 | 33 | async function fetchAuthTokenCronJob() { 34 | try { 35 | const response = await fetch(url, options); 36 | if (!response.ok) { 37 | throw new Error(`HTTP error! Status: ${response.status}`); 38 | } 39 | const data = await response.json(); 40 | bearerToken.value = data.access_token; 41 | } catch (error) { 42 | console.error('Error:', error); 43 | } 44 | } 45 | 46 | export async function getBearerToken() { 47 | return bearerToken.value; 48 | } 49 | 50 | startAuthTokenCronJob(); 51 | -------------------------------------------------------------------------------- /src/actions/upload-to-cdn.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | 3 | import { v4 as uuidv4 } from 'uuid'; 4 | 5 | type FileType = 'webp' | 'pdf'; 6 | 7 | export async function uploadFileAction(formData: FormData, fileType: FileType) { 8 | const CDN_BASE_UPLOAD_URL = process.env.CDN_BASE_UPLOAD_URL!; 9 | const CDN_BASE_ACCESS_URL = process.env.CDN_BASE_ACCESS_URL!; 10 | const CDN_API_KEY = process.env.CDN_API_KEY!; 11 | 12 | try { 13 | const file = formData.get('file') as File; 14 | const uniqueFileName = formData.get('uniqueFileName') || uuidv4(); // Generate unique key if not provided 15 | 16 | if (!file) { 17 | return { error: 'File is required', status: 400 }; 18 | } 19 | const uploadUrl = `${CDN_BASE_UPLOAD_URL}/${uniqueFileName}.${fileType}`; 20 | const fileBuffer = Buffer.from(await file.arrayBuffer()); 21 | 22 | const response = await fetch(uploadUrl, { 23 | method: 'PUT', 24 | headers: { 25 | AccessKey: CDN_API_KEY, 26 | 'Content-Type': 'application/octet-stream', 27 | }, 28 | body: fileBuffer, 29 | }); 30 | if (response.ok) { 31 | return { 32 | message: 'File uploaded successfully', 33 | url: `${CDN_BASE_ACCESS_URL}/${uniqueFileName}.${fileType}`, 34 | }; 35 | } else { 36 | return { error: 'Failed to upload file', status: response.status }; 37 | } 38 | } catch (error) { 39 | console.error('Error uploading file:', error); 40 | return { error: 'Internal server error', status: 500 }; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/app/(auth)/forgot-password/page.tsx: -------------------------------------------------------------------------------- 1 | import { ForgotPassword } from '@/components/auth/forgot-password'; 2 | import { FormContainer } from '@/layouts/form-container'; 3 | import React from 'react'; 4 | 5 | const ForgootPasswordPage = async () => { 6 | return ( 7 |
8 | 12 | 13 | 14 |
15 | ); 16 | }; 17 | 18 | export default ForgootPasswordPage; 19 | -------------------------------------------------------------------------------- /src/app/(auth)/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { authOptions } from '@/lib/authOptions'; 3 | import { getServerSession } from 'next-auth'; 4 | import { redirect } from 'next/navigation'; 5 | 6 | export default async function AuthLayout({ 7 | children, 8 | }: { 9 | children: React.ReactNode; 10 | }) { 11 | const auth = await getServerSession(authOptions); 12 | 13 | if (auth) redirect(`/`); 14 | 15 | return children; 16 | } 17 | -------------------------------------------------------------------------------- /src/app/(auth)/reset-password/[token]/page.tsx: -------------------------------------------------------------------------------- 1 | import { ResetPassword } from '@/components/auth/reset-password'; 2 | import prisma from '@/config/prisma.config'; 3 | import { FormContainer } from '@/layouts/form-container'; 4 | import { isTokenExpiredUtil } from '@/lib/utils'; 5 | import { notFound } from 'next/navigation'; 6 | import React from 'react'; 7 | 8 | const ResetPasswordPage = async ({ 9 | params: { token }, 10 | }: { 11 | params: { token: string }; 12 | }) => { 13 | const verificatinToken = await prisma.verificationToken.findFirst({ 14 | where: { token }, 15 | }); 16 | 17 | if (!verificatinToken) notFound(); 18 | 19 | if (isTokenExpiredUtil(verificatinToken.createdAt)) 20 | return

link expried

; 21 | 22 | const user = await prisma.user.findFirst({ 23 | where: { id: verificatinToken.identifier }, 24 | }); 25 | 26 | if (!user) notFound(); 27 | 28 | return ( 29 |
30 | 34 | 35 | 36 |
37 | ); 38 | }; 39 | 40 | export default ResetPasswordPage; 41 | -------------------------------------------------------------------------------- /src/app/(auth)/signin/page.tsx: -------------------------------------------------------------------------------- 1 | import { Signin } from '@/components/auth/signin'; 2 | import { FormContainer } from '@/layouts/form-container'; 3 | 4 | const LoginPage = () => { 5 | return ( 6 |
7 | 11 | 12 | 13 |
14 | ); 15 | }; 16 | 17 | export default LoginPage; 18 | -------------------------------------------------------------------------------- /src/app/(auth)/signup/page.tsx: -------------------------------------------------------------------------------- 1 | import { Signup } from '@/components/auth/signup'; 2 | import { FormContainer } from '@/layouts/form-container'; 3 | 4 | const SignupPage = () => { 5 | return ( 6 |
7 | 11 | 12 | 13 |
14 | ); 15 | }; 16 | 17 | export default SignupPage; 18 | -------------------------------------------------------------------------------- /src/app/(auth)/verify-email/[token]/EmailVerificationLinkExpired.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useState } from 'react'; 3 | import { FormContainer } from '@/layouts/form-container'; 4 | import Link from 'next/link'; 5 | import APP_PATHS from '@/config/path.config'; 6 | import { Button } from '@/components/ui/button'; 7 | import { verifyEmail } from '@/actions/auth.actions'; 8 | import { useToast } from '@/components/ui/use-toast'; 9 | 10 | export const EmailVerificationLinkExpired = ({ token }: { token: string }) => { 11 | const [isEmailSent, setIsEmailSent] = useState(false); 12 | const [isLoading, setIsLoading] = useState(false); 13 | const { toast } = useToast(); 14 | 15 | const handleResendClick = async () => { 16 | setIsLoading(true); 17 | try { 18 | await verifyEmail({ token, resend: true }); 19 | setIsEmailSent(true); 20 | } catch { 21 | toast({ 22 | variant: 'destructive', 23 | title: 'Something went wrong, please try again!', 24 | }); 25 | } finally { 26 | setIsLoading(!true); 27 | } 28 | }; 29 | 30 | return ( 31 |
32 | 40 | {!isEmailSent ? ( 41 | 42 | 49 | 50 | ) : ( 51 | 52 | 55 | 56 | )} 57 | 58 |
59 | ); 60 | }; 61 | -------------------------------------------------------------------------------- /src/app/(auth)/verify-email/[token]/page.tsx: -------------------------------------------------------------------------------- 1 | import { verifyEmail } from '@/actions/auth.actions'; 2 | import { Button } from '@/components/ui/button'; 3 | import APP_PATHS from '@/config/path.config'; 4 | import { FormContainer } from '@/layouts/form-container'; 5 | import Link from 'next/link'; 6 | import { redirect } from 'next/navigation'; 7 | import React from 'react'; 8 | import { EmailVerificationLinkExpired } from './EmailVerificationLinkExpired'; 9 | 10 | const Page = async ({ params: { token } }: { params: { token: string } }) => { 11 | const res = await verifyEmail({ token }); 12 | 13 | if (res.status) return ; 14 | else if (res?.error?.notFound) return ; 15 | else if (res?.error?.linkExpired) 16 | return ; 17 | return redirect(APP_PATHS.SIGNIN); 18 | }; 19 | 20 | export default Page; 21 | 22 | const EmailVerifiedSuccess = () => { 23 | return ( 24 |
25 | 31 | 32 | 35 | 36 | 37 |
38 | ); 39 | }; 40 | 41 | const EmailVerificationLinkNotFound = () => { 42 | return ( 43 |
44 | 48 | 49 | 52 | 53 | 54 |
55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /src/app/(auth)/welcome/page.tsx: -------------------------------------------------------------------------------- 1 | import { Welcome } from '@/components/auth/welcome'; 2 | import APP_PATHS from '@/config/path.config'; 3 | import { FormContainer } from '@/layouts/form-container'; 4 | 5 | import { cookies } from 'next/headers'; 6 | import { redirect } from 'next/navigation'; 7 | import { PENDING_EMAIL_VERIFICATION_USER_ID } from '@/config/auth.config'; 8 | 9 | const WelcomePage = () => { 10 | const unverifiedUserId = cookies().get(PENDING_EMAIL_VERIFICATION_USER_ID); 11 | if (!unverifiedUserId) redirect(APP_PATHS.SIGNIN); 12 | 13 | return ( 14 |
15 | 21 | 22 | 23 |
24 | ); 25 | }; 26 | 27 | export default WelcomePage; 28 | -------------------------------------------------------------------------------- /src/app/[...404]/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import React from 'react'; 3 | import Link from 'next/link'; 4 | import { Poppins } from 'next/font/google'; 5 | import { Home, AlertTriangle } from 'lucide-react'; 6 | import { Button } from '@/components/ui/button'; 7 | import { motion } from 'framer-motion'; 8 | 9 | // Applying font from Google 10 | const fontOptions = Poppins({ 11 | subsets: ['latin'], 12 | variable: '--font-poppins', 13 | weight: ['400', '700'], 14 | }); 15 | 16 | const Custom404Page = () => { 17 | return ( 18 |
21 | 27 | 31 | 32 | 33 |

34 | 404 - Page Not Found 35 |

36 |

37 | We are sorry, but we could not find the page you are looking for. 38 |

39 |

40 | The page may have been moved, removed, renamed, or might never have 41 | existed. 42 |

43 | 55 |
56 |
57 | ); 58 | }; 59 | 60 | export default Custom404Page; 61 | -------------------------------------------------------------------------------- /src/app/admin/page.tsx: -------------------------------------------------------------------------------- 1 | import UserCard from '@/components/UserCard'; 2 | 3 | const page = async () => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | }; 10 | 11 | export default page; 12 | -------------------------------------------------------------------------------- /src/app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import { authOptions } from '@/lib/authOptions'; 2 | import NextAuth from 'next-auth/next'; 3 | 4 | const handler = NextAuth(authOptions); 5 | // export default handler; 6 | export { handler as GET, handler as POST }; 7 | -------------------------------------------------------------------------------- /src/app/create-profile/page.tsx: -------------------------------------------------------------------------------- 1 | import VerticalLinearStepper from '@/components/user-multistep-form/user-multistep-form'; 2 | import { authOptions } from '@/lib/authOptions'; 3 | import { getServerSession } from 'next-auth'; 4 | import { redirect } from 'next/navigation'; 5 | 6 | export default async function Home() { 7 | const session = await getServerSession(authOptions); 8 | if (!session || session.user.role !== 'USER') redirect('/'); 9 | if (session.user.onBoard === true) redirect('/jobs'); 10 | return ( 11 |
12 |

13 | Hey {session?.user.name} let's get you set up and started! 14 |

15 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/app/create/page.tsx: -------------------------------------------------------------------------------- 1 | import PostJobForm from '@/components/job-form'; 2 | import React from 'react'; 3 | 4 | const page = () => { 5 | return ( 6 |
7 |
8 |

Post a job

9 |

10 | 100xJobs is trusted by leading companies 11 |

12 |
13 | 14 | 15 |
16 | ); 17 | }; 18 | 19 | export default page; 20 | -------------------------------------------------------------------------------- /src/app/editDetails/page.tsx: -------------------------------------------------------------------------------- 1 | import BackgroundSvg from '@/components/BackgroundSvg'; 2 | import HalfCircleGradient from '@/components/HalfCircleGradient'; 3 | import UserDetails from '@/components/userDetails'; 4 | import React from 'react'; 5 | 6 | const editDetails = () => { 7 | return ( 8 |
9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 | ); 17 | }; 18 | 19 | export default editDetails; 20 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/job-board/2ed4b46a0d532fb8edab3d02a9fc416f53847f01/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/jobs/[id]/loading.tsx: -------------------------------------------------------------------------------- 1 | import JobCardSkeleton from '@/components/JobcardSkeleton'; 2 | 3 | const Loading = () => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | }; 10 | 11 | export default Loading; 12 | -------------------------------------------------------------------------------- /src/app/jobs/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import { getJobById, getRecommendedJobs } from '@/actions/job.action'; 2 | import { Job } from '@/components/job'; 3 | import JobCard from '@/components/job-card-rec'; 4 | import { JobByIdSchemaType } from '@/lib/validators/jobs.validator'; 5 | import { ArrowLeft } from 'lucide-react'; 6 | import Link from 'next/link'; 7 | import { redirect } from 'next/navigation'; 8 | 9 | const page = async ({ params }: { params: JobByIdSchemaType }) => { 10 | const job = await getJobById(params); 11 | if (!job.status) { 12 | return; 13 | } 14 | 15 | const jobDetail = job.additional?.job; 16 | if (!jobDetail) { 17 | return redirect('/jobs'); 18 | } 19 | 20 | const curatedJobs = await getRecommendedJobs({ 21 | id: jobDetail.id, 22 | category: jobDetail.category, 23 | }); 24 | 25 | if (!curatedJobs.status) { 26 | return; 27 | } 28 | 29 | const recommendedJobs = curatedJobs.additional?.jobs; 30 | 31 | return ( 32 |
33 |
34 | 38 | 39 |

Back to All Jobs

40 | 41 |
42 |
43 | {/* the particular job details */} 44 | 45 | 46 | {/* job recommendations */} 47 | 58 |
59 |
60 | ); 61 | }; 62 | 63 | export default page; 64 | -------------------------------------------------------------------------------- /src/app/jobs/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Card, CardContent } from '@/components/ui/card'; 2 | import { Skeleton } from '@/components/ui/skeleton'; 3 | 4 | const Loading = () => { 5 | return ( 6 |
7 |
8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 |
17 | 18 |
19 | 20 |
21 | {[1, 2, 3, 4, 5].map((job) => ( 22 | 23 | 24 |
25 | 26 |
27 | 28 | 29 |
30 | 31 | 32 | 33 |
34 |
35 | {[1, 2, 3, 4, 5, 6].map((skill) => ( 36 | 37 | ))} 38 |
39 |
40 |
41 |
42 |
43 | ))} 44 |
45 |
46 |
47 | ); 48 | }; 49 | 50 | export default Loading; 51 | -------------------------------------------------------------------------------- /src/app/jobs/page.tsx: -------------------------------------------------------------------------------- 1 | import AllJobs from '@/components/all-jobs'; 2 | import Loader from '@/components/loader'; 3 | import JobFilters from '@/layouts/job-filters'; 4 | import JobsHeader from '@/layouts/jobs-header'; 5 | import { 6 | JobQuerySchema, 7 | JobQuerySchemaType, 8 | } from '@/lib/validators/jobs.validator'; 9 | import { redirect } from 'next/navigation'; 10 | import { Suspense } from 'react'; 11 | 12 | const page = async ({ searchParams }: { searchParams: JobQuerySchemaType }) => { 13 | const parsedData = JobQuerySchema.safeParse(searchParams); 14 | if (!(parsedData.success && parsedData.data)) { 15 | console.error(parsedData.error); 16 | redirect('/jobs'); 17 | } 18 | const parsedSearchParams = parsedData.data; 19 | return ( 20 |
21 |
22 |

Explore Jobs

23 |

24 | Explore thousands of remote and onsite jobs that match your skills and 25 | aspirations. 26 |

27 |
28 | 29 |
30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 | 41 |
42 | 46 | 47 |
48 | } 49 | > 50 | 51 | 52 |
53 |
54 |
55 | 56 | ); 57 | }; 58 | 59 | export default page; 60 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import Footer from '@/layouts/footer'; 2 | import Header from '@/layouts/header'; 3 | import { cn } from '@/lib/utils'; 4 | import Providers from '@/providers/providers'; 5 | import type { Metadata } from 'next'; 6 | import NextTopLoader from 'nextjs-toploader'; 7 | import './globals.css'; 8 | import localFont from 'next/font/local'; 9 | 10 | const satoshi = localFont({ 11 | display: 'swap', 12 | src: [ 13 | { 14 | path: '../../public/fonts/satoshi.ttf', 15 | }, 16 | ], 17 | variable: '--font-satoshi', 18 | }); 19 | export const metadata: Metadata = { 20 | title: '100xJobs', 21 | description: 'Get your dream job', 22 | // icons: '/main.png', 23 | }; 24 | 25 | export default async function RootLayout({ 26 | children, 27 | }: Readonly<{ 28 | children: React.ReactNode; 29 | }>) { 30 | return ( 31 | 32 | 38 | {' '} 39 | 40 | 41 |
42 |
{children}
43 |