├── .node-version ├── jest.setup.js ├── .husky ├── post-merge ├── pre-commit └── commit-msg ├── public ├── images │ ├── new-tab.png │ └── split-discount.png ├── favicon │ ├── favicon.ico │ ├── large-og.jpg │ ├── apple-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── ms-icon-70x70.png │ ├── apple-icon-57x57.png │ ├── apple-icon-60x60.png │ ├── apple-icon-72x72.png │ ├── apple-icon-76x76.png │ ├── ms-icon-144x144.png │ ├── ms-icon-150x150.png │ ├── ms-icon-310x310.png │ ├── android-icon-36x36.png │ ├── android-icon-48x48.png │ ├── android-icon-72x72.png │ ├── android-icon-96x96.png │ ├── apple-icon-114x114.png │ ├── apple-icon-120x120.png │ ├── apple-icon-144x144.png │ ├── apple-icon-152x152.png │ ├── apple-icon-180x180.png │ ├── android-icon-144x144.png │ ├── android-icon-192x192.png │ ├── apple-icon-precomposed.png │ ├── browserconfig.xml │ └── manifest.json ├── fonts │ └── inter-var-latin.woff2 └── svg │ └── Vercel.svg ├── postcss.config.js ├── prisma ├── migrations │ ├── 20220308143014_add_phone_number_to_user │ │ └── migration.sql │ ├── migration_lock.toml │ ├── 20220305032109_add_date_to_debt_table │ │ └── migration.sql │ ├── 20220305034059_change_title_to_description_for_debt │ │ └── migration.sql │ ├── 20220304172341_add_debt_model │ │ └── migration.sql │ ├── 20220305093746_change_debt_to_transaction │ │ └── migration.sql │ └── 20220304162448_user_with_google │ │ └── migration.sql └── schema.prisma ├── src ├── types │ ├── dropzone.ts │ ├── next-auth.d.ts │ └── index.d.ts ├── lib │ ├── date.ts │ ├── clsxm.ts │ ├── prisma.ts │ ├── umami.ts │ ├── axios.ts │ ├── logger.ts │ ├── __tests__ │ │ └── helper.test.ts │ ├── require-session.server.ts │ ├── email.server.ts │ └── helper.ts ├── hooks │ ├── useDialog.tsx │ └── toast │ │ ├── useLoadingToast.tsx │ │ └── useSWRWithToast.tsx ├── constant │ ├── env.ts │ ├── toast.ts │ ├── regex.ts │ ├── food-lists.ts │ └── email-whitelist.ts ├── pages │ ├── api │ │ ├── user │ │ │ ├── index.ts │ │ │ ├── [id].ts │ │ │ └── edit.ts │ │ ├── auth │ │ │ └── [...nextauth].ts │ │ ├── delete-trx │ │ │ └── [trxId].ts │ │ ├── remind.ts │ │ └── trx │ │ │ ├── create-many-by-id.ts │ │ │ ├── create-many.ts │ │ │ ├── summary.ts │ │ │ ├── create.ts │ │ │ └── [id].ts │ ├── 404.tsx │ ├── _document.tsx │ ├── _app.tsx │ ├── sandbox │ │ ├── toast-swr.tsx │ │ ├── dialog-zustand.tsx │ │ └── rhf.tsx │ ├── list.tsx │ ├── profile.tsx │ ├── index.tsx │ ├── debt │ │ ├── bayar │ │ │ └── [id].tsx │ │ ├── request.tsx │ │ └── split.tsx │ └── trx │ │ └── [id].tsx ├── components │ ├── Skeleton.tsx │ ├── buttons │ │ ├── TextButton.tsx │ │ └── Button.tsx │ ├── links │ │ ├── PrimaryLink.tsx │ │ ├── UnderlineLink.tsx │ │ ├── UnstyledLink.tsx │ │ ├── ArrowLink.tsx │ │ └── ButtonLink.tsx │ ├── layout │ │ ├── Layout.tsx │ │ └── Header.tsx │ ├── UserImage.tsx │ ├── DismissableToast.tsx │ ├── NextImage.tsx │ ├── UserListItem.tsx │ ├── forms │ │ ├── TextArea.tsx │ │ ├── UserCheckboxes.tsx │ │ ├── Input.tsx │ │ ├── SelectInput.tsx │ │ ├── PasswordInput.tsx │ │ ├── DatePicker.tsx │ │ ├── FilePreview.tsx │ │ └── DropzoneInput.tsx │ ├── Seo.tsx │ ├── UserSelect.tsx │ └── dialog │ │ └── BaseDialog.tsx ├── styles │ ├── nprogress.css │ └── globals.css ├── container │ └── FullScreenLoading.tsx └── store │ └── useDialogStore.tsx ├── .prettierrc.js ├── next-env.d.ts ├── .vscode ├── settings.json ├── extensions.json ├── css.code-snippets └── typescriptreact.code-snippets ├── vercel.json ├── .github ├── issue-branch.yml └── workflows │ ├── create-branch.yml │ ├── issue-autolink.yml │ └── lint.yml ├── README.md ├── next-sitemap.js ├── commitlint.config.js ├── .prettierignore ├── .gitignore ├── next.config.js ├── tsconfig.json ├── .env.example ├── jest.config.js ├── CONTRIBUTING.md ├── tailwind.config.js ├── .eslintrc.js └── package.json /.node-version: -------------------------------------------------------------------------------- 1 | v16.14.0 -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/extend-expect'; 2 | -------------------------------------------------------------------------------- /.husky/post-merge: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged -------------------------------------------------------------------------------- /public/images/new-tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/images/new-tab.png -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /public/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/favicon.ico -------------------------------------------------------------------------------- /public/favicon/large-og.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/large-og.jpg -------------------------------------------------------------------------------- /public/favicon/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/apple-icon.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/favicon-96x96.png -------------------------------------------------------------------------------- /public/favicon/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/ms-icon-70x70.png -------------------------------------------------------------------------------- /public/images/split-discount.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/images/split-discount.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/apple-icon-57x57.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/apple-icon-60x60.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/apple-icon-72x72.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/apple-icon-76x76.png -------------------------------------------------------------------------------- /public/favicon/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/ms-icon-144x144.png -------------------------------------------------------------------------------- /public/favicon/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/ms-icon-150x150.png -------------------------------------------------------------------------------- /public/favicon/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/ms-icon-310x310.png -------------------------------------------------------------------------------- /public/fonts/inter-var-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/fonts/inter-var-latin.woff2 -------------------------------------------------------------------------------- /public/favicon/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/android-icon-36x36.png -------------------------------------------------------------------------------- /public/favicon/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/android-icon-48x48.png -------------------------------------------------------------------------------- /public/favicon/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/android-icon-72x72.png -------------------------------------------------------------------------------- /public/favicon/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/android-icon-96x96.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/apple-icon-114x114.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/apple-icon-120x120.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/apple-icon-144x144.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/apple-icon-152x152.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/apple-icon-180x180.png -------------------------------------------------------------------------------- /public/favicon/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/android-icon-144x144.png -------------------------------------------------------------------------------- /public/favicon/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/android-icon-192x192.png -------------------------------------------------------------------------------- /prisma/migrations/20220308143014_add_phone_number_to_user/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "User" ADD COLUMN "phoneNumber" TEXT; 3 | -------------------------------------------------------------------------------- /public/favicon/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/lhokutang/HEAD/public/favicon/apple-icon-precomposed.png -------------------------------------------------------------------------------- /src/types/dropzone.ts: -------------------------------------------------------------------------------- 1 | import { FileWithPath } from 'react-dropzone'; 2 | 3 | export type FileWithPreview = FileWithPath & { preview: string }; 4 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'always', 3 | singleQuote: true, 4 | jsxSingleQuote: true, 5 | tabWidth: 2, 6 | semi: true, 7 | }; 8 | -------------------------------------------------------------------------------- /public/svg/Vercel.svg: -------------------------------------------------------------------------------- 1 | Vercel -------------------------------------------------------------------------------- /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" -------------------------------------------------------------------------------- /src/lib/date.ts: -------------------------------------------------------------------------------- 1 | export const DATE_FORMAT = { 2 | /** 15 Agustus 2021 */ 3 | FULL: 'dd MMMM yyyy', 4 | /** 15 Agu 2021 */ 5 | FULL_DATE_HOUR_MINUTE: 'd MMMM yyyy HH:mm', 6 | }; 7 | -------------------------------------------------------------------------------- /src/hooks/useDialog.tsx: -------------------------------------------------------------------------------- 1 | import useDialogStore from '@/store/useDialogStore'; 2 | 3 | /** Hook to use dialog cleanly */ 4 | export default function useDialog() { 5 | return useDialogStore.useDialog(); 6 | } 7 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "css.validate": false, 3 | "editor.formatOnSave": true, 4 | "editor.tabSize": 2, 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll": true 7 | }, 8 | "headwind.runOnSave": false 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | // Tailwind CSS Intellisense 4 | "bradlc.vscode-tailwindcss", 5 | "esbenp.prettier-vscode", 6 | "dbaeumer.vscode-eslint", 7 | "aaron-bond.better-comments" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/css.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Region CSS": { 3 | "prefix": "regc", 4 | "body": [ 5 | "/* #region /**=========== ${1} =========== */", 6 | "$0", 7 | "/* #endregion /**======== ${1} =========== */" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/constant/env.ts: -------------------------------------------------------------------------------- 1 | export const isProd = process.env.NODE_ENV === 'production'; 2 | export const isLocal = process.env.NODE_ENV === 'development'; 3 | 4 | export const showLogger = isLocal 5 | ? true 6 | : process.env.NEXT_PUBLIC_SHOW_LOGGER === 'true' ?? false; 7 | -------------------------------------------------------------------------------- /src/lib/clsxm.ts: -------------------------------------------------------------------------------- 1 | import clsx, { ClassValue } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | /** Merge classes with tailwind-merge with clsx full feature */ 5 | export default function clsxm(...classes: ClassValue[]) { 6 | return twMerge(clsx(...classes)); 7 | } 8 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": [ 3 | { 4 | "source": "/fonts/inter-var-latin.woff2", 5 | "headers": [ 6 | { 7 | "key": "Cache-Control", 8 | "value": "public, max-age=31536000, immutable" 9 | } 10 | ] 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/constant/toast.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_TOAST_MESSAGE = { 2 | loading: 'Loading...', 3 | success: 'Success', 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | error: (err: any) => 6 | err?.response?.data?.message ?? 'Something is wrong, please try again', 7 | }; 8 | -------------------------------------------------------------------------------- /.github/issue-branch.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/robvanderleek/create-issue-branch#option-2-configure-github-action 2 | 3 | # ex: i4-lower_camel_upper 4 | branchName: 'i${issue.number}-${issue.title,}' 5 | branches: 6 | - label: epic 7 | skip: true 8 | - label: debt 9 | skip: true 10 | -------------------------------------------------------------------------------- /public/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /prisma/migrations/20220305032109_add_date_to_debt_table/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `date` to the `Debt` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Debt" ADD COLUMN "date" TIMESTAMP(3) NOT NULL; 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

LhokUtang

3 |

Aplikasi perutangan 4.0 milik kos lhoktuan, sukolilo, surabaya.

4 |
5 | 6 | # Contributing & Project Setup 7 | 8 | If you are interested in contributing or running this project on your local machine, please check the [contributing guide](CONTRIBUTING.md) 9 | -------------------------------------------------------------------------------- /src/types/next-auth.d.ts: -------------------------------------------------------------------------------- 1 | import { DefaultUser } from 'next-auth'; 2 | 3 | declare module 'next-auth' { 4 | /** 5 | * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context 6 | */ 7 | interface Session { 8 | user: DefaultUser & { 9 | id: string; 10 | }; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/create-branch.yml: -------------------------------------------------------------------------------- 1 | name: Create Branch from Issue 2 | 3 | on: 4 | issues: 5 | types: [assigned] 6 | 7 | jobs: 8 | create_issue_branch_job: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Create Issue Branch 12 | uses: robvanderleek/create-issue-branch@main 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /.github/workflows/issue-autolink.yml: -------------------------------------------------------------------------------- 1 | name: 'Issue Autolink' 2 | on: 3 | pull_request: 4 | types: [opened] 5 | 6 | jobs: 7 | issue-links: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: tkt-actions/add-issue-links@v1.6.0 11 | with: 12 | repo-token: '${{ secrets.GITHUB_TOKEN }}' 13 | branch-prefix: 'i' 14 | resolve: 'true' 15 | -------------------------------------------------------------------------------- /next-sitemap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('next-sitemap').IConfig} 3 | * @see https://github.com/iamvishnusankar/next-sitemap#readme 4 | */ 5 | module.exports = { 6 | /** Without additional '/' on the end, e.g. https://theodorusclarence.com */ 7 | siteUrl: 'https://lhoks.thcl.dev', 8 | generateRobotsTxt: true, 9 | robotsTxtOptions: { 10 | policies: [{ userAgent: '*', allow: '/' }], 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | // @see https://www.prisma.io/docs/support/help-articles/nextjs-prisma-client-dev-practices 2 | // @see https://github.com/nextauthjs/next-auth/issues/824#issuecomment-860266512 3 | import { PrismaClient } from '@prisma/client'; 4 | 5 | declare global { 6 | namespace NodeJS { 7 | interface Global { 8 | prisma: PrismaClient | undefined; 9 | } 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /src/lib/prisma.ts: -------------------------------------------------------------------------------- 1 | // @see https://www.prisma.io/docs/support/help-articles/nextjs-prisma-client-dev-practices 2 | // @see https://github.com/nextauthjs/next-auth/issues/824#issuecomment-860266512 3 | import { PrismaClient } from '@prisma/client'; 4 | 5 | export const prisma = 6 | global.prisma || 7 | new PrismaClient({ 8 | // log: ['query'], 9 | }); 10 | 11 | if (process.env.NODE_ENV !== 'production') global.prisma = prisma; 12 | -------------------------------------------------------------------------------- /src/constant/regex.ts: -------------------------------------------------------------------------------- 1 | export const REGEX = { 2 | EMAIL: { 3 | value: 4 | /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, 5 | message: 'Email tidak valid', 6 | }, 7 | PHONE_NUMBER: { 8 | value: /^\+628[1-9][0-9]{7,11}$/, 9 | message: 10 | 'Nomor Telepon harus diawali +62 dan memiliki panjang 13-15 karakter', 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/lib/umami.ts: -------------------------------------------------------------------------------- 1 | const EVENT_TYPE = ['link', 'click', 'navigate', 'recommend'] as const; 2 | type EventType = typeof EVENT_TYPE[number]; 3 | 4 | type TrackEvent = ( 5 | event_name: string, 6 | event_data?: { type?: EventType } & { [key: string]: string | number } 7 | ) => void; 8 | 9 | export const trackEvent: TrackEvent = (...args) => { 10 | if (window.umami && typeof window.umami.track === 'function') { 11 | window.umami.track(...args); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/hooks/toast/useLoadingToast.tsx: -------------------------------------------------------------------------------- 1 | import { useToasterStore } from 'react-hot-toast'; 2 | 3 | /** 4 | * Hook to get information whether something is loading 5 | * @returns true if there is a loading toast 6 | * @example const isLoading = useLoadingToast(); 7 | */ 8 | export default function useLoadingToast(): boolean { 9 | const { toasts } = useToasterStore(); 10 | const isLoading = toasts.some((toast) => toast.type === 'loading'); 11 | return isLoading; 12 | } 13 | -------------------------------------------------------------------------------- /prisma/migrations/20220305034059_change_title_to_description_for_debt/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `title` on the `Debt` table. All the data in the column will be lost. 5 | - Added the required column `description` to the `Debt` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "Debt" DROP COLUMN "title", 10 | ADD COLUMN "description" TEXT NOT NULL; 11 | -------------------------------------------------------------------------------- /src/pages/api/user/index.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from 'next'; 2 | 3 | import { prisma } from '@/lib/prisma'; 4 | import requireSession from '@/lib/require-session.server'; 5 | 6 | export default requireSession(users); 7 | 8 | async function users(req: NextApiRequest, res: NextApiResponse) { 9 | if (req.method === 'GET') { 10 | res.status(200).json({ users: await prisma.user.findMany() }); 11 | } else { 12 | res.status(405).json({ message: 'Method Not Allowed' }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/lib/axios.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const axiosClient = axios.create({ 4 | headers: { 5 | Authorization: '', 6 | 'Content-Type': 'application/json', 7 | }, 8 | }); 9 | 10 | axiosClient.defaults.withCredentials = false; 11 | 12 | axiosClient.interceptors.request.use(function (config) { 13 | const token = localStorage.getItem('token'); 14 | if (config.headers) { 15 | config.headers.Authorization = token ? `Bearer ${token}` : ''; 16 | } 17 | return config; 18 | }); 19 | 20 | export default axiosClient; 21 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | // TODO Add Scope Enum Here 5 | // 'scope-enum': [2, 'always', ['yourscope', 'yourscope']], 6 | 'type-enum': [ 7 | 2, 8 | 'always', 9 | [ 10 | 'feat', 11 | 'fix', 12 | 'docs', 13 | 'chore', 14 | 'style', 15 | 'refactor', 16 | 'ci', 17 | 'test', 18 | 'perf', 19 | 'revert', 20 | 'vercel', 21 | ], 22 | ], 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /prisma/migrations/20220304172341_add_debt_model/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Debt" ( 3 | "id" TEXT NOT NULL, 4 | "title" TEXT NOT NULL, 5 | "amount" INTEGER NOT NULL, 6 | "creditorId" TEXT NOT NULL, 7 | "debtorId" TEXT NOT NULL, 8 | "type" TEXT, 9 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 10 | 11 | CONSTRAINT "Debt_pkey" PRIMARY KEY ("id") 12 | ); 13 | 14 | -- AddForeignKey 15 | ALTER TABLE "Debt" ADD CONSTRAINT "Debt_creditorId_fkey" FOREIGN KEY ("creditorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 16 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | .next 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # changelog 38 | CHANGELOG.md 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # next-sitemap 38 | robots.txt 39 | sitemap.xml -------------------------------------------------------------------------------- /src/lib/logger.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { showLogger } from '@/constant/env'; 3 | 4 | /** 5 | * A logger function that will only logs on development 6 | * @param object - The object to log 7 | * @param comment - Autogenerated with `lg` snippet 8 | */ 9 | export default function logger(object: unknown, comment?: string): void { 10 | if (!showLogger) return; 11 | 12 | console.log( 13 | '%c ============== INFO LOG \n', 14 | 'color: #22D3EE', 15 | `${typeof window !== 'undefined' && window?.location.pathname}\n`, 16 | `=== ${comment ?? ''}\n`, 17 | object 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Skeleton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import clsxm from '@/lib/clsxm'; 4 | 5 | type SkeletonProps = React.ComponentPropsWithoutRef<'div'>; 6 | 7 | export default function Skeleton({ className, ...rest }: SkeletonProps) { 8 | return ( 9 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/constant/food-lists.ts: -------------------------------------------------------------------------------- 1 | export const FOOD_LISTS = [ 2 | 'Bu Lala', 3 | 'Kane', 4 | 'Mbak Lis', 5 | 'Jus', 6 | 'Urban Latte', 7 | 'Suprek', 8 | 'Bedjo', 9 | 'Xenteur', 10 | 'Joder', 11 | 'Teh Poci', 12 | 'Soto Abas', 13 | 'Laundry', 14 | 'Es Teh Surabaya', 15 | 'Mixue', 16 | 'Sego Ndog', 17 | 'Kopi Kenangan', 18 | 'Uduk Gebang', 19 | 'Bebek Ombrenk', 20 | 'Gobar', 21 | 'Uni', 22 | 'Bagong', 23 | 'Cak Kabul', 24 | 'Butong', 25 | 'Cak Rozi', 26 | 'Djayaraya', 27 | 'Ngelalap', 28 | 'Bangjo', 29 | 'Pak Ndut', 30 | 'Warjo', 31 | 'Ayam Ibu', 32 | 'Warung Jakarta', 33 | 'Pawon Chef Rudy', 34 | ]; 35 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | eslint: { 4 | dirs: ['src'], 5 | }, 6 | 7 | reactStrictMode: true, 8 | 9 | // Uncoment to add domain whitelist 10 | images: { 11 | domains: ['lh3.googleusercontent.com'], 12 | }, 13 | 14 | // SVGR 15 | webpack(config) { 16 | config.module.rules.push({ 17 | test: /\.svg$/i, 18 | issuer: /\.[jt]sx?$/, 19 | use: [ 20 | { 21 | loader: '@svgr/webpack', 22 | options: { 23 | typescript: true, 24 | icon: true, 25 | }, 26 | }, 27 | ], 28 | }); 29 | 30 | return config; 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /src/lib/__tests__/helper.test.ts: -------------------------------------------------------------------------------- 1 | import { openGraph } from '@/lib/helper'; 2 | 3 | describe('Open Graph function should work correctly', () => { 4 | it('should not return templateTitle when not specified', () => { 5 | const result = openGraph({ 6 | description: 'Test description', 7 | siteName: 'Test site name', 8 | }); 9 | expect(result).not.toContain('&templateTitle='); 10 | }); 11 | 12 | it('should return templateTitle when specified', () => { 13 | const result = openGraph({ 14 | templateTitle: 'Test Template Title', 15 | description: 'Test description', 16 | siteName: 'Test site name', 17 | }); 18 | expect(result).toContain('&templateTitle=Test%20Template%20Title'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/lib/require-session.server.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from 'next'; 2 | import { User } from 'next-auth'; 3 | import { getSession } from 'next-auth/react'; 4 | 5 | export type ApiRoute = ( 6 | req: NextApiRequest, 7 | res: NextApiResponse, 8 | user: User 9 | ) => Promise; 10 | 11 | export default function requireSession(apiRoute: ApiRoute) { 12 | return async function handler(req: NextApiRequest, res: NextApiResponse) { 13 | const session = await getSession({ req }); 14 | if (!session?.user?.email) { 15 | res.status(401).json({ 16 | message: 'Unauthorized', 17 | }); 18 | return null; 19 | } else { 20 | return apiRoute(req, res, session.user as User); 21 | } 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/styles/nprogress.css: -------------------------------------------------------------------------------- 1 | /* Make clicks pass-through */ 2 | #nprogress { 3 | pointer-events: none; 4 | } 5 | 6 | #nprogress .bar { 7 | background: var(--color-primary-700); 8 | 9 | position: fixed; 10 | z-index: 1031; 11 | top: 0; 12 | left: 0; 13 | 14 | width: 100%; 15 | height: 2px; 16 | } 17 | 18 | /* Fancy blur effect */ 19 | #nprogress .peg { 20 | display: block; 21 | position: absolute; 22 | right: 0px; 23 | width: 100px; 24 | height: 100%; 25 | box-shadow: 0 0 10px var(--clr-primary-700), 0 0 5px var(--clr-primary-700); 26 | opacity: 1; 27 | 28 | -webkit-transform: rotate(3deg) translate(0px, -4px); 29 | -ms-transform: rotate(3deg) translate(0px, -4px); 30 | transform: rotate(3deg) translate(0px, -4px); 31 | } 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "baseUrl": ".", 17 | "paths": { 18 | "@/*": ["./src/*"], 19 | "~/*": ["./public/*"] 20 | }, 21 | "incremental": true 22 | }, 23 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 24 | "exclude": ["node_modules"], 25 | "moduleResolution": ["node_modules", ".next", "node"] 26 | } 27 | -------------------------------------------------------------------------------- /src/components/buttons/TextButton.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | 3 | type TextButtonProps = { 4 | /** Button children element */ 5 | children: React.ReactNode; 6 | /** Will be merged with button tag */ 7 | className?: string; 8 | } & React.ComponentPropsWithoutRef<'button'>; 9 | 10 | export default function TextButton({ 11 | children, 12 | className = '', 13 | type = 'button', 14 | ...rest 15 | }: TextButtonProps) { 16 | return ( 17 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /prisma/migrations/20220305093746_change_debt_to_transaction/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the `Debt` table. If the table is not empty, all the data it contains will be lost. 5 | 6 | */ 7 | -- DropForeignKey 8 | ALTER TABLE "Debt" DROP CONSTRAINT "Debt_creditorId_fkey"; 9 | 10 | -- DropTable 11 | DROP TABLE "Debt"; 12 | 13 | -- CreateTable 14 | CREATE TABLE "Transaction" ( 15 | "id" TEXT NOT NULL, 16 | "userId" TEXT NOT NULL, 17 | "amount" INTEGER NOT NULL, 18 | "description" TEXT NOT NULL, 19 | "date" TIMESTAMP(3) NOT NULL, 20 | "destinationUserId" TEXT NOT NULL, 21 | 22 | CONSTRAINT "Transaction_pkey" PRIMARY KEY ("id") 23 | ); 24 | 25 | -- AddForeignKey 26 | ALTER TABLE "Transaction" ADD CONSTRAINT "Transaction_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 27 | -------------------------------------------------------------------------------- /src/components/links/PrimaryLink.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import clsxm from '@/lib/clsxm'; 4 | 5 | import UnstyledLink, { 6 | UnstyledLinkProps, 7 | } from '@/components/links/UnstyledLink'; 8 | 9 | const PrimaryLink = React.forwardRef( 10 | ({ className, children, ...rest }, ref) => { 11 | return ( 12 | 22 | {children} 23 | 24 | ); 25 | } 26 | ); 27 | 28 | export default PrimaryLink; 29 | -------------------------------------------------------------------------------- /src/components/layout/Layout.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import BaseDialog from '@/components/dialog/BaseDialog'; 4 | import Header from '@/components/layout/Header'; 5 | 6 | import useDialogStore from '@/store/useDialogStore'; 7 | 8 | export default function Layout({ children }: { children: React.ReactNode }) { 9 | //#region //*=========== Store =========== 10 | const open = useDialogStore.useOpen(); 11 | const state = useDialogStore.useState(); 12 | const handleClose = useDialogStore.useHandleClose(); 13 | const handleSubmit = useDialogStore.useHandleSubmit(); 14 | //#endregion //*======== Store =========== 15 | 16 | return ( 17 |
18 |
19 | 20 | {children} 21 | 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/UserImage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import clsxm from '@/lib/clsxm'; 4 | 5 | import NextImage from '@/components/NextImage'; 6 | 7 | export default function UserImage({ 8 | image, 9 | className, 10 | }: { 11 | image?: string | null; 12 | className?: string; 13 | }) { 14 | return ( 15 | <> 16 | {image ? ( 17 | 27 | ) : ( 28 |
34 | )} 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/components/links/UnderlineLink.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import clsxm from '@/lib/clsxm'; 4 | 5 | import UnstyledLink, { 6 | UnstyledLinkProps, 7 | } from '@/components/links/UnstyledLink'; 8 | 9 | const UnderlineLink = React.forwardRef( 10 | ({ children, className, ...rest }, ref) => { 11 | return ( 12 | 22 | {children} 23 | 24 | ); 25 | } 26 | ); 27 | 28 | export default UnderlineLink; 29 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Environment variables declared in this file are automatically made available to Prisma. 2 | # See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema 3 | 4 | # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB (Preview) and CockroachDB (Preview). 5 | # See the documentation for all the connection string options: https://pris.ly/d/connection-strings 6 | 7 | DATABASE_URL="postgres://[username]:[password]@localhost:5432/lhokutang" 8 | 9 | 10 | # Run `openssl rand -hex 32` or go to https://generate-secret.now.sh/32 11 | NEXTAUTH_SECRET="" 12 | 13 | # Google OAuth2 Credentials 14 | # @see https://developers.google.com/adwords/api/docs/guides/authentication 15 | # Add http://localhost:3000 for the origin, and http://localhost:3000/api/auth/callback/google for the redirect 16 | GOOGLE_CLIENT_ID="" 17 | GOOGLE_CLIENT_SECRET="" 18 | -------------------------------------------------------------------------------- /public/favicon/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /src/container/FullScreenLoading.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | import { useSession } from 'next-auth/react'; 3 | import toast from 'react-hot-toast'; 4 | import { ImSpinner6 } from 'react-icons/im'; 5 | 6 | export default function FullScreenLoading({ 7 | children, 8 | }: { 9 | children: React.ReactNode; 10 | }) { 11 | const { push } = useRouter(); 12 | 13 | const { data: session } = useSession({ 14 | required: true, 15 | onUnauthenticated: () => { 16 | toast.error('Please login first'); 17 | push('/'); 18 | }, 19 | }); 20 | const isUser = !!session?.user; 21 | 22 | return ( 23 | <> 24 | {isUser ? ( 25 | children 26 | ) : ( 27 |
28 |

LhokUtang

29 | Loading... 30 | 31 |
32 | )} 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RiAlarmWarningFill } from 'react-icons/ri'; 3 | 4 | import Layout from '@/components/layout/Layout'; 5 | import ArrowLink from '@/components/links/ArrowLink'; 6 | import Seo from '@/components/Seo'; 7 | 8 | export default function NotFoundPage() { 9 | return ( 10 | 11 | 12 | 13 |
14 |
15 |
16 | 20 |

Page Not Found

21 | 22 | Back to Home 23 | 24 |
25 |
26 |
27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/lib/email.server.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import mailjet from 'node-mailjet'; 3 | export const sendMail = ({ 4 | to, 5 | toName, 6 | subject, 7 | text, 8 | html, 9 | }: { 10 | to: string; 11 | toName: string; 12 | subject: string; 13 | text: string; 14 | html?: string; 15 | }) => { 16 | const mailjetClient = mailjet.apiConnect( 17 | process.env.MAILJET_API_KEY ?? '', 18 | process.env.MAILJET_SECRET_KEY ?? '' 19 | ); 20 | 21 | const req = mailjetClient.post('send', { version: 'v3.1' }).request({ 22 | Messages: [ 23 | { 24 | From: { 25 | Email: 'givemefeedbackplease@gmail.com', 26 | Name: 'Lhokutang', 27 | }, 28 | To: [{ Email: to, Name: toName }], 29 | Subject: subject, 30 | TextPart: text, 31 | HTMLPart: html, 32 | }, 33 | ], 34 | }); 35 | 36 | req 37 | .then((res) => { 38 | console.log(res.body); 39 | }) 40 | .catch((err) => console.error(err)); 41 | 42 | return; 43 | }; 44 | -------------------------------------------------------------------------------- /src/constant/email-whitelist.ts: -------------------------------------------------------------------------------- 1 | export const emailWhitelist = [ 2 | 'ppdbultimate@gmail.com', 3 | 'theodorusclarence@gmail.com', 4 | 'jeremiakeloko@gmail.com', 5 | 'ivan.19400016@gmail.com', 6 | 'billharit@gmail.com', 7 | 'rayhanalifa@googlemail.com', 8 | 'ryan.ardhana27@gmail.com', 9 | 'williamhadiwijaya111@gmail.com', 10 | 'raynardbudiman7@gmail.com', 11 | 'tobiasega70@gmail.com', 12 | 'ariwwijyaa@gmail.com', 13 | 'gardasudarmanto@gmail.com', 14 | ]; 15 | 16 | export const alumnusEmail = [ 17 | 'ppdbultimate@gmail.com', 18 | 'kanda.wibisanan@gmail.com', 19 | 'stevearmando@gmail.com', 20 | 'adhwamaharika@gmail.com', 21 | 'satriaadam04@gmail.com', 22 | 'adhwamaharika@gmail.com', 23 | 'carlonugroho01@gmail.com', 24 | 'radityarobert@gmail.com', 25 | 'kanda.wibisanan@gmail.com', 26 | 'ferdinand.parulian@gmail.com', 27 | 'stevearmando@gmail.com', 28 | 'dimbum14@gmail.com', 29 | 'dega.ad@gmail.com', 30 | 'sihombingcharminuel@gmail.com', 31 | 'shrallvierdo@gmail.com', 32 | 'philippurba.pp@gmail.com', 33 | ]; 34 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const nextJest = require('next/jest'); 3 | 4 | const createJestConfig = nextJest({ 5 | // Provide the path to your Next.js app to load next.config.js and .env files in your test environment 6 | dir: './', 7 | }); 8 | 9 | // Add any custom config to be passed to Jest 10 | const customJestConfig = { 11 | // Add more setup options before each test is run 12 | setupFilesAfterEnv: ['/jest.setup.js'], 13 | 14 | // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work 15 | moduleDirectories: ['node_modules', '/'], 16 | 17 | testEnvironment: 'jest-environment-jsdom', 18 | 19 | /** 20 | * Absolute imports and Module Path Aliases 21 | */ 22 | moduleNameMapper: { 23 | '^@/(.*)$': '/src/$1', 24 | '^~/(.*)$': '/public/$1', 25 | }, 26 | }; 27 | 28 | // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async 29 | module.exports = createJestConfig(customJestConfig); 30 | -------------------------------------------------------------------------------- /src/pages/api/user/[id].ts: -------------------------------------------------------------------------------- 1 | import { Prisma } from '@prisma/client'; 2 | import { NextApiRequest, NextApiResponse } from 'next'; 3 | 4 | import { prisma } from '@/lib/prisma'; 5 | import requireSession from '@/lib/require-session.server'; 6 | 7 | export default requireSession(GetSingleUser); 8 | 9 | async function GetSingleUser(req: NextApiRequest, res: NextApiResponse) { 10 | if (req.method === 'GET') { 11 | const userId = req.query.id; 12 | 13 | if (typeof userId !== 'string') { 14 | return res.status(400).json({ 15 | message: 'Invalid ID', 16 | }); 17 | } 18 | 19 | try { 20 | const user = await prisma.user.findUnique({ 21 | where: { 22 | id: userId, 23 | }, 24 | }); 25 | return res.status(200).json(user); 26 | } catch (error) { 27 | if (error instanceof Prisma.PrismaClientUnknownRequestError) 28 | return res.status(500).send(error.message); 29 | else { 30 | throw error; 31 | } 32 | } 33 | } else { 34 | res.status(405).json({ message: 'Method Not Allowed' }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { 2 | DocumentContext, 3 | Head, 4 | Html, 5 | Main, 6 | NextScript, 7 | } from 'next/document'; 8 | 9 | class MyDocument extends Document { 10 | static async getInitialProps(ctx: DocumentContext) { 11 | const initialProps = await Document.getInitialProps(ctx); 12 | return { ...initialProps }; 13 | } 14 | 15 | render() { 16 | return ( 17 | 18 | 19 | 26 |