From 86e1f6e9b113df589cef13cd6805810a86a6399f Mon Sep 17 00:00:00 2001 From: Kenton Duprey Date: Fri, 5 Jul 2024 16:29:22 -0400 Subject: [PATCH] feat: adding admin app with clerk authentication, linting, test provider, and basic layout --- apps/admin/.eslintignore | 3 + apps/admin/.eslintrc.js | 8 ++ apps/admin/.stylelintignore | 2 + apps/admin/.stylelintrc.json | 28 +++++++ apps/admin/LICENCE | 21 +++++ apps/admin/README.md | 37 +++++++++ apps/admin/barrelsby.config.json | 6 ++ apps/admin/jest.config.cjs | 17 ++++ apps/admin/jest.setup.cjs | 26 ++++++ apps/admin/next-env.d.ts | 5 ++ apps/admin/next.config.mjs | 26 ++++++ apps/admin/package.json | 75 ++++++++++++++++++ apps/admin/postcss.config.cjs | 14 ++++ apps/admin/src/app/apple-icon.png | Bin 0 -> 5637 bytes apps/admin/src/app/dashboard/layout.tsx | 8 ++ apps/admin/src/app/dashboard/page.tsx | 7 ++ apps/admin/src/app/favicon-16x16.png | Bin 0 -> 432 bytes apps/admin/src/app/favicon-32x32.png | Bin 0 -> 853 bytes apps/admin/src/app/favicon.ico | Bin 0 -> 15406 bytes apps/admin/src/app/global.css | 3 + apps/admin/src/app/layout.tsx | 46 +++++++++++ apps/admin/src/app/page.tsx | 12 +++ .../src/app/sign-in/[[...sign-in]]/page.tsx | 14 ++++ .../ColorSchemeToggle/ColorSchemeToggle.tsx | 33 ++++++++ .../DashboardLayout/DashboardLayout.tsx | 75 ++++++++++++++++++ apps/admin/src/components/index.ts | 2 + apps/admin/src/data/index.ts | 1 + apps/admin/src/data/links.ts | 6 ++ apps/admin/src/middleware.ts | 11 +++ apps/admin/src/theme.ts | 71 +++++++++++++++++ apps/admin/test-utils/index.ts | 5 ++ apps/admin/test-utils/render.tsx | 17 ++++ apps/admin/tsconfig.json | 25 ++++++ 33 files changed, 604 insertions(+) create mode 100644 apps/admin/.eslintignore create mode 100644 apps/admin/.eslintrc.js create mode 100644 apps/admin/.stylelintignore create mode 100644 apps/admin/.stylelintrc.json create mode 100644 apps/admin/LICENCE create mode 100644 apps/admin/README.md create mode 100644 apps/admin/barrelsby.config.json create mode 100644 apps/admin/jest.config.cjs create mode 100644 apps/admin/jest.setup.cjs create mode 100644 apps/admin/next-env.d.ts create mode 100644 apps/admin/next.config.mjs create mode 100644 apps/admin/package.json create mode 100644 apps/admin/postcss.config.cjs create mode 100644 apps/admin/src/app/apple-icon.png create mode 100644 apps/admin/src/app/dashboard/layout.tsx create mode 100644 apps/admin/src/app/dashboard/page.tsx create mode 100644 apps/admin/src/app/favicon-16x16.png create mode 100644 apps/admin/src/app/favicon-32x32.png create mode 100644 apps/admin/src/app/favicon.ico create mode 100644 apps/admin/src/app/global.css create mode 100644 apps/admin/src/app/layout.tsx create mode 100644 apps/admin/src/app/page.tsx create mode 100644 apps/admin/src/app/sign-in/[[...sign-in]]/page.tsx create mode 100644 apps/admin/src/components/ColorSchemeToggle/ColorSchemeToggle.tsx create mode 100644 apps/admin/src/components/DashboardLayout/DashboardLayout.tsx create mode 100644 apps/admin/src/components/index.ts create mode 100644 apps/admin/src/data/index.ts create mode 100644 apps/admin/src/data/links.ts create mode 100644 apps/admin/src/middleware.ts create mode 100644 apps/admin/src/theme.ts create mode 100644 apps/admin/test-utils/index.ts create mode 100644 apps/admin/test-utils/render.tsx create mode 100644 apps/admin/tsconfig.json diff --git a/apps/admin/.eslintignore b/apps/admin/.eslintignore new file mode 100644 index 000000000..590ba8759 --- /dev/null +++ b/apps/admin/.eslintignore @@ -0,0 +1,3 @@ +*.cjs +*.mjs +*.js \ No newline at end of file diff --git a/apps/admin/.eslintrc.js b/apps/admin/.eslintrc.js new file mode 100644 index 000000000..0c6ec010e --- /dev/null +++ b/apps/admin/.eslintrc.js @@ -0,0 +1,8 @@ +/** @type {import("eslint").Linter.Config} */ +module.exports = { + extends: ["@kduprey/eslint-config/next.js"], + parser: "@typescript-eslint/parser", + parserOptions: { + project: true, + }, +}; diff --git a/apps/admin/.stylelintignore b/apps/admin/.stylelintignore new file mode 100644 index 000000000..2b3533c7e --- /dev/null +++ b/apps/admin/.stylelintignore @@ -0,0 +1,2 @@ +.next +out diff --git a/apps/admin/.stylelintrc.json b/apps/admin/.stylelintrc.json new file mode 100644 index 000000000..4ea6506d9 --- /dev/null +++ b/apps/admin/.stylelintrc.json @@ -0,0 +1,28 @@ +{ + "extends": ["stylelint-config-standard-scss"], + "rules": { + "custom-property-pattern": null, + "selector-class-pattern": null, + "scss/no-duplicate-mixins": null, + "declaration-empty-line-before": null, + "declaration-block-no-redundant-longhand-properties": null, + "alpha-value-notation": null, + "custom-property-empty-line-before": null, + "property-no-vendor-prefix": null, + "color-function-notation": null, + "length-zero-no-unit": null, + "selector-not-notation": null, + "no-descending-specificity": null, + "comment-empty-line-before": null, + "scss/at-mixin-pattern": null, + "scss/at-rule-no-unknown": null, + "value-keyword-case": null, + "media-feature-range-notation": null, + "selector-pseudo-class-no-unknown": [ + true, + { + "ignorePseudoClasses": ["global"] + } + ] + } +} diff --git a/apps/admin/LICENCE b/apps/admin/LICENCE new file mode 100644 index 000000000..1a88111ae --- /dev/null +++ b/apps/admin/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Vitaly Rtischev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/admin/README.md b/apps/admin/README.md new file mode 100644 index 000000000..f25d60af1 --- /dev/null +++ b/apps/admin/README.md @@ -0,0 +1,37 @@ +# Mantine Next.js template + +This is a template for [Next.js](https://nextjs.org/) app router + [Mantine](https://mantine.dev/). +If you want to use pages router instead, see [next-pages-template](https://github.com/mantinedev/next-pages-template). + +## Features + +This template comes with the following features: + +- [PostCSS](https://postcss.org/) with [mantine-postcss-preset](https://mantine.dev/styles/postcss-preset) +- [TypeScript](https://www.typescriptlang.org/) +- [Storybook](https://storybook.js.org/) +- [Jest](https://jestjs.io/) setup with [React Testing Library](https://testing-library.com/docs/react-testing-library/intro) +- ESLint setup with [eslint-config-mantine](https://github.com/mantinedev/eslint-config-mantine) + +## npm scripts + +### Build and dev scripts + +- `dev` – start dev server +- `build` – bundle application for production +- `analyze` – analyzes application bundle with [@next/bundle-analyzer](https://www.npmjs.com/package/@next/bundle-analyzer) + +### Testing scripts + +- `typecheck` – checks TypeScript types +- `lint` – runs ESLint +- `prettier:check` – checks files with Prettier +- `jest` – runs jest tests +- `jest:watch` – starts jest watch +- `test` – runs `jest`, `prettier:check`, `lint` and `typecheck` scripts + +### Other scripts + +- `storybook` – starts storybook dev server +- `storybook:build` – build production storybook bundle to `storybook-static` +- `prettier:write` – formats all files with Prettier diff --git a/apps/admin/barrelsby.config.json b/apps/admin/barrelsby.config.json new file mode 100644 index 000000000..909928888 --- /dev/null +++ b/apps/admin/barrelsby.config.json @@ -0,0 +1,6 @@ +{ + "baseurl": "./src", + "delete": true, + "directory": ["./src/components"], + "noHeader": true +} diff --git a/apps/admin/jest.config.cjs b/apps/admin/jest.config.cjs new file mode 100644 index 000000000..a6a9e317c --- /dev/null +++ b/apps/admin/jest.config.cjs @@ -0,0 +1,17 @@ +const nextJest = require("next/jest"); + +const createJestConfig = nextJest({ + dir: "./", +}); + +const customJestConfig = { + setupFilesAfterEnv: ["/jest.setup.cjs"], + moduleNameMapper: { + "^@/components/(.*)$": "/components/$1", + "^@/pages/(.*)$": "/pages/$1", + }, + testEnvironment: "jest-environment-jsdom", + moduledirectories: ["node_modules", __dirname, "test-utils"], +}; + +module.exports = createJestConfig(customJestConfig); diff --git a/apps/admin/jest.setup.cjs b/apps/admin/jest.setup.cjs new file mode 100644 index 000000000..2f5c44dae --- /dev/null +++ b/apps/admin/jest.setup.cjs @@ -0,0 +1,26 @@ +require('@testing-library/jest-dom'); + +const { getComputedStyle } = window; +window.getComputedStyle = (elt) => getComputedStyle(elt); + +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), + removeListener: jest.fn(), + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), +}); + +class ResizeObserver { + observe() {} + unobserve() {} + disconnect() {} +} + +window.ResizeObserver = ResizeObserver; diff --git a/apps/admin/next-env.d.ts b/apps/admin/next-env.d.ts new file mode 100644 index 000000000..4f11a03dc --- /dev/null +++ b/apps/admin/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/admin/next.config.mjs b/apps/admin/next.config.mjs new file mode 100644 index 000000000..eaccc00fe --- /dev/null +++ b/apps/admin/next.config.mjs @@ -0,0 +1,26 @@ +import bundleAnalyzer from "@next/bundle-analyzer"; +import { PrismaPlugin } from "@prisma/nextjs-monorepo-workaround-plugin"; + +const withBundleAnalyzer = bundleAnalyzer({ + enabled: process.env.ANALYZE === "true", +}); + +export default withBundleAnalyzer({ + webpack: (config, { isServer }) => { + if (isServer) { + config.plugins.push(new PrismaPlugin()); + } else config.resolve.fallback.fs = false; + return config; + }, + reactStrictMode: false, + eslint: { + ignoreDuringBuilds: true, + }, + experimental: { + optimizePackageImports: [ + "@mantine/core", + "@mantine/hooks", + "@kduprey/ui", + ], + }, +}); diff --git a/apps/admin/package.json b/apps/admin/package.json new file mode 100644 index 000000000..b235188b5 --- /dev/null +++ b/apps/admin/package.json @@ -0,0 +1,75 @@ +{ + "name": "@kduprey/admin", + "version": "1.0.0", + "private": true, + "scripts": { + "analyze": "ANALYZE=true next build", + "build": "next build", + "dev": "next dev -p 3401", + "generate-barrels": "barrelsby -c barrelsby.config.json", + "jest": "jest", + "jest:watch": "jest --watch", + "lint": "next lint && npm run lint:stylelint", + "lint:stylelint": "stylelint '**/*.css' --cache", + "prettier:check": "prettier --check \"**/*.{ts,tsx}\"", + "prettier:write": "prettier --write \"**/*.{ts,tsx}\"", + "start": "next start", + "storybook": "storybook dev -p 6006", + "storybook:build": "storybook build", + "test": "npm run prettier:check && npm run lint && npm run typecheck && npm run jest", + "typecheck": "tsc --noEmit" + }, + "prettier": "@kduprey/eslint-config/prettier", + "dependencies": { + "@clerk/nextjs": "^5.2.1", + "@clerk/themes": "^2.1.10", + "@kduprey/config": "workspace:*", + "@kduprey/db": "workspace:*", + "@kduprey/ui": "workspace:*", + "@kduprey/utils": "workspace:*", + "@mantine/core": "7.11.1", + "@mantine/form": "^7.11.1", + "@mantine/hooks": "7.11.1", + "@mantine/notifications": "^7.11.1", + "@next/bundle-analyzer": "^14.2.4", + "@tabler/icons": "^3.8.0", + "@tabler/icons-react": "^3.8.0", + "clsx": "^2.1.1", + "mantine-datatable": "^7.11.1", + "next": "14.2.4", + "react": "18.3.1", + "react-dom": "18.3.1" + }, + "devDependencies": { + "@babel/core": "^7.24.7", + "@kduprey/eslint-config": "workspace:*", + "@kduprey/tsconfig": "workspace:*", + "@prisma/nextjs-monorepo-workaround-plugin": "^5.16.1", + "@storybook/addon-essentials": "^8.1.11", + "@storybook/addon-styling-webpack": "^1.0.0", + "@storybook/blocks": "^8.1.11", + "@storybook/nextjs": "^8.1.11", + "@storybook/preview-api": "^8.1.11", + "@storybook/react": "^8.1.11", + "@testing-library/dom": "^10.3.0", + "@testing-library/jest-dom": "^6.4.6", + "@testing-library/react": "^16.0.0", + "@testing-library/user-event": "^14.5.2", + "@types/jest": "^29.5.12", + "@types/node": "^20.14.9", + "@types/react": "18.3.3", + "@types/react-dom": "^18.3.0", + "babel-loader": "^9.1.3", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "postcss": "^8.4.39", + "postcss-preset-mantine": "1.15.0", + "postcss-simple-vars": "^7.0.1", + "storybook": "^8.1.11", + "storybook-dark-mode": "^4.0.2", + "stylelint": "^16.6.1", + "stylelint-config-standard-scss": "^13.1.0", + "ts-jest": "^29.1.5", + "typescript": "5.5.3" + } +} diff --git a/apps/admin/postcss.config.cjs b/apps/admin/postcss.config.cjs new file mode 100644 index 000000000..bfba0ddfa --- /dev/null +++ b/apps/admin/postcss.config.cjs @@ -0,0 +1,14 @@ +module.exports = { + plugins: { + 'postcss-preset-mantine': {}, + 'postcss-simple-vars': { + variables: { + 'mantine-breakpoint-xs': '36em', + 'mantine-breakpoint-sm': '48em', + 'mantine-breakpoint-md': '62em', + 'mantine-breakpoint-lg': '75em', + 'mantine-breakpoint-xl': '88em', + }, + }, + }, +}; diff --git a/apps/admin/src/app/apple-icon.png b/apps/admin/src/app/apple-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fa81c285e73e78aa1bbb731685dcf2559f89ef03 GIT binary patch literal 5637 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 X8}35*nEQd)*Cj!y>Znw}t;7EZ2uPO) literal 0 HcmV?d00001 diff --git a/apps/admin/src/app/dashboard/layout.tsx b/apps/admin/src/app/dashboard/layout.tsx new file mode 100644 index 000000000..cb602caf7 --- /dev/null +++ b/apps/admin/src/app/dashboard/layout.tsx @@ -0,0 +1,8 @@ +import { type PropsWithChildren } from "react"; +import { DashboardLayout } from "@/components"; + +const Layout = ({ children }: PropsWithChildren) => ( + {children} +); + +export default Layout; diff --git a/apps/admin/src/app/dashboard/page.tsx b/apps/admin/src/app/dashboard/page.tsx new file mode 100644 index 000000000..3203d0184 --- /dev/null +++ b/apps/admin/src/app/dashboard/page.tsx @@ -0,0 +1,7 @@ +import { Title } from "@mantine/core"; + +const Page = () => { + return Dashboard; +}; + +export default Page; diff --git a/apps/admin/src/app/favicon-16x16.png b/apps/admin/src/app/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..02d9c91221543a99f474a201e98da78e22879ddb GIT binary patch literal 432 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 literal 0 HcmV?d00001 diff --git a/apps/admin/src/app/global.css b/apps/admin/src/app/global.css new file mode 100644 index 000000000..fbbce3f10 --- /dev/null +++ b/apps/admin/src/app/global.css @@ -0,0 +1,3 @@ +@import "https://use.typekit.net/vik6drq.css"; +/* 👇 Make sure the styles are applied in the correct order */ +@layer mantine, mantine-datatable; diff --git a/apps/admin/src/app/layout.tsx b/apps/admin/src/app/layout.tsx new file mode 100644 index 000000000..868858d3f --- /dev/null +++ b/apps/admin/src/app/layout.tsx @@ -0,0 +1,46 @@ +import React, { type PropsWithChildren } from "react"; +import { ColorSchemeScript, MantineProvider } from "@mantine/core"; +import { ClerkProvider } from "@clerk/nextjs"; +import { dark } from "@clerk/themes"; +import "@mantine/core/styles.layer.css"; +import "mantine-datatable/styles.layer.css"; +import "./global.css"; +import { resolver, theme } from "../theme"; + +export const metadata = { + description: "Haus of Web - Admin", + keywords: "Haus of Web, Kenton Duprey, developer, software engineer, NYC", + title: "Haus of Web - Admin", +}; + +const RootLayout = ({ children }: Readonly) => { + return ( + + + + + + + + + + {children} + + + + + ); +}; + +export default RootLayout; diff --git a/apps/admin/src/app/page.tsx b/apps/admin/src/app/page.tsx new file mode 100644 index 000000000..52c0ca6de --- /dev/null +++ b/apps/admin/src/app/page.tsx @@ -0,0 +1,12 @@ +import { auth } from "@clerk/nextjs/server"; +import { redirect } from "next/navigation"; + +const HomePage = () => { + const { userId } = auth(); + + if (userId) redirect("/dashboard"); + + redirect("/sign-in"); +}; + +export default HomePage; diff --git a/apps/admin/src/app/sign-in/[[...sign-in]]/page.tsx b/apps/admin/src/app/sign-in/[[...sign-in]]/page.tsx new file mode 100644 index 000000000..86e82bf6e --- /dev/null +++ b/apps/admin/src/app/sign-in/[[...sign-in]]/page.tsx @@ -0,0 +1,14 @@ +import { SignIn } from "@clerk/nextjs"; +import { Center, Paper } from "@mantine/core"; + +const Page = () => { + return ( + +
+ +
+
+ ); +}; + +export default Page; diff --git a/apps/admin/src/components/ColorSchemeToggle/ColorSchemeToggle.tsx b/apps/admin/src/components/ColorSchemeToggle/ColorSchemeToggle.tsx new file mode 100644 index 000000000..682a17379 --- /dev/null +++ b/apps/admin/src/components/ColorSchemeToggle/ColorSchemeToggle.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { Button, Group, useMantineColorScheme } from "@mantine/core"; + +export const ColorSchemeToggle = () => { + const { setColorScheme } = useMantineColorScheme(); + + return ( + + + + + + ); +}; diff --git a/apps/admin/src/components/DashboardLayout/DashboardLayout.tsx b/apps/admin/src/components/DashboardLayout/DashboardLayout.tsx new file mode 100644 index 000000000..e7b5b03d4 --- /dev/null +++ b/apps/admin/src/components/DashboardLayout/DashboardLayout.tsx @@ -0,0 +1,75 @@ +"use client"; + +import { + Anchor, + AppShell, + AppShellHeader, + AppShellMain, + AppShellNavbar, + AppShellSection, + Burger, + Group, + NavLink, + Title, +} from "@mantine/core"; +import { useDisclosure } from "@mantine/hooks"; +import { type PropsWithChildren } from "react"; +import { SignedIn, UserButton } from "@clerk/nextjs"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { links } from "@/src/data"; + +export const DashboardLayout = ({ children }: PropsWithChildren) => { + const [opened, { toggle }] = useDisclosure(); + const path = usePathname(); + return ( + + + + + + + + Haus of Web, LLC. + + + + + + + {links.map((link) => ( + + ))} + + + + + + + + {children} + + ); +}; diff --git a/apps/admin/src/components/index.ts b/apps/admin/src/components/index.ts new file mode 100644 index 000000000..b21193ce4 --- /dev/null +++ b/apps/admin/src/components/index.ts @@ -0,0 +1,2 @@ +export * from "./ColorSchemeToggle/ColorSchemeToggle"; +export * from "./DashboardLayout/DashboardLayout"; diff --git a/apps/admin/src/data/index.ts b/apps/admin/src/data/index.ts new file mode 100644 index 000000000..130ebac08 --- /dev/null +++ b/apps/admin/src/data/index.ts @@ -0,0 +1 @@ +export * from "./links"; diff --git a/apps/admin/src/data/links.ts b/apps/admin/src/data/links.ts new file mode 100644 index 000000000..02d92d2ed --- /dev/null +++ b/apps/admin/src/data/links.ts @@ -0,0 +1,6 @@ +export const links = [ + { + href: "/dashboard", + label: "Dashboard", + }, +]; diff --git a/apps/admin/src/middleware.ts b/apps/admin/src/middleware.ts new file mode 100644 index 000000000..a6a10bd72 --- /dev/null +++ b/apps/admin/src/middleware.ts @@ -0,0 +1,11 @@ +import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server"; + +const isProtectedRoute = createRouteMatcher(["/dashboard(.*)"]); + +export default clerkMiddleware((auth, req) => { + if (isProtectedRoute(req)) auth().protect(); +}); + +export const config = { + matcher: ["/((?!.+.[w]+$|_next).*)", "/", "/(api|trpc)(.*)"], +}; diff --git a/apps/admin/src/theme.ts b/apps/admin/src/theme.ts new file mode 100644 index 000000000..52419184f --- /dev/null +++ b/apps/admin/src/theme.ts @@ -0,0 +1,71 @@ +"use client"; + +import { + type CSSVariablesResolver, + Card, + createTheme, + type MantineThemeOverride, + type VariantColorsResolver, + defaultVariantColorsResolver, + rem, + DEFAULT_THEME, + mergeMantineTheme, + rgba, +} from "@mantine/core"; +import { Raleway } from "next/font/google"; + +const raleway = Raleway({ + display: "swap", + subsets: ["latin"], +}); + +const variantColorResolver: VariantColorsResolver = (input) => { + const defaultResolvedColors = defaultVariantColorsResolver(input); + + if (input.variant === "danger") { + return { + ...defaultResolvedColors, + background: "none", + hover: rgba(theme.colors.red[6], 0.2), + color: "var(--mantine-color-red-6)", + border: `${rem(1)} solid var(--mantine-color-red-6)`, + }; + } + + return defaultResolvedColors; +}; + +const themeOverride: MantineThemeOverride = createTheme({ + black: "#000000", + components: { + Card: Card.extend({ + styles: { + root: { + border: "1px solid var(--mantine-color-default-border)", + }, + }, + }), + }, + fontFamily: `${raleway.style.fontFamily}, sans-serif`, + headings: { + fontFamily: `${raleway.style.fontFamily}, sans-serif`, + }, + primaryColor: "violet", + primaryShade: { dark: 4, light: 4 }, + variantColorResolver, +}); + +export const resolver: CSSVariablesResolver = () => ({ + variables: { + "--mantine-color-default-border": "#fff", + }, + light: { + "--mantine-color-default-border": "#fff", + }, + dark: { + "--mantine-color-default-border": "#fff", + "--mantine-color-text": "#fff", + }, +}); + +export const theme = mergeMantineTheme(DEFAULT_THEME, themeOverride); diff --git a/apps/admin/test-utils/index.ts b/apps/admin/test-utils/index.ts new file mode 100644 index 000000000..5736b5477 --- /dev/null +++ b/apps/admin/test-utils/index.ts @@ -0,0 +1,5 @@ +import userEvent from "@testing-library/user-event"; + +export { render } from "./render"; +export * from "@testing-library/react"; +export { userEvent }; diff --git a/apps/admin/test-utils/render.tsx b/apps/admin/test-utils/render.tsx new file mode 100644 index 000000000..577c46cfa --- /dev/null +++ b/apps/admin/test-utils/render.tsx @@ -0,0 +1,17 @@ +import { render, type RenderOptions } from "@testing-library/react"; +import { MantineProvider } from "@mantine/core"; +import type { ReactElement } from "react"; +import { theme } from "../src/theme"; + +const AllTheProviders = ({ children }: { children: React.ReactNode }) => { + return {children}; +}; + +export const customRender = ( + ui: ReactElement, + options?: Omit, +): ReturnType => + render(ui, { wrapper: AllTheProviders, ...options }); + +export * from "@testing-library/react"; +export { customRender as render }; diff --git a/apps/admin/tsconfig.json b/apps/admin/tsconfig.json new file mode 100644 index 000000000..f5376d6a0 --- /dev/null +++ b/apps/admin/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "@kduprey/tsconfig/nextjs.json", + "compilerOptions": { + "types": [ + "node", + "jest", + "@testing-library/jest-dom", + "@testing-library/react" + ], + "baseUrl": ".", + "paths": { + "@/*": ["./*", "./src/*"], + "test-utils": ["./src/test-utils"] + }, + "plugins": [{ "name": "next" }] + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "jest.setup.cjs" + ], + "exclude": ["node_modules"] +}