diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..39cb3268 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +node_modules +coverage +html +dist +!.prettierrc.cjs \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 4f6f59ee..4727f0ce 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,83 +1,68 @@ +// @ts-check + /** - * This is intended to be a basic starting point for linting in your app. - * It relies on recommended configs out of the box for simplicity, but you can - * and should modify this configuration to best suit your team's needs. + * To use this configuration, you need to install the following dependencies: + * + * eslint@^8.57.0 @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-refresh eslint-plugin-unicorn eslint-plugin-unused-imports@^3.2.0 prettier eslint-config-prettier eslint-plugin-prettier @ianvs/prettier-plugin-sort-imports prettier-plugin-tailwindcss + * + * + * To Replicate the vitest configuration, you need to install the following dependencies: + * + * eslint-plugin-vitest@^0.4.1 eslint-testing-library + * + * Also replicate the .prettierrc.cjs file in the root of your project. */ /** @type {import('eslint').Linter.Config} */ module.exports = { root: true, - parserOptions: { - ecmaVersion: "latest", - sourceType: "module", - ecmaFeatures: { - jsx: true, - }, - }, + reportUnusedDisableDirectives: true, env: { browser: true, - commonjs: true, - es6: true, + es2020: true, }, - ignorePatterns: ["!**/.server", "!**/.client"], - - // Base config - extends: ["eslint:recommended"], - - overrides: [ - // React - { - files: ["**/*.{js,jsx,ts,tsx}"], - plugins: ["react", "jsx-a11y"], - extends: [ - "plugin:react/recommended", - "plugin:react/jsx-runtime", - "plugin:react-hooks/recommended", - "plugin:jsx-a11y/recommended", - ], - settings: { - react: { - version: "detect", - }, - formComponents: ["Form"], - linkComponents: [ - { name: "Link", linkAttribute: "to" }, - { name: "NavLink", linkAttribute: "to" }, - ], - "import/resolver": { - typescript: {}, - }, + extends: [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:@typescript-eslint/recommended", + "plugin:react-hooks/recommended", + "plugin:unicorn/recommended", + "plugin:prettier/recommended", + ], + ignorePatterns: ["dist", "node_modules"], + parser: "@typescript-eslint/parser", + plugins: ["@typescript-eslint", "react", "unicorn", "unused-imports"], + rules: { + "react/prop-types": "off", + "unused-imports/no-unused-imports": "error", + "unused-imports/no-unused-vars": [ + "warn", + { + vars: "all", + varsIgnorePattern: "^_", + args: "after-used", + argsIgnorePattern: "^_", }, - }, - - // Typescript + ], + "unicorn/filename-case": [ + "error", + { cases: { kebabCase: true, pascalCase: true, camelCase: true } }, + ], + }, + settings: { + react: { version: "detect" }, + vitest: { typecheck: true }, + }, + overrides: [ { - files: ["**/*.{ts,tsx}"], - plugins: ["@typescript-eslint", "import"], - parser: "@typescript-eslint/parser", - settings: { - "import/internal-regex": "^~/", - "import/resolver": { - node: { - extensions: [".ts", ".tsx"], - }, - typescript: { - alwaysTryTypes: true, - }, - }, - }, - extends: [ - "plugin:@typescript-eslint/recommended", - "plugin:import/recommended", - "plugin:import/typescript", - ], + files: [".eslintrc.*js", ".vite(|st).(js|ts)"], + env: { node: true }, }, - - // Node { - files: [".eslintrc.cjs"], - env: { - node: true, + files: ["*.d.ts"], + rules: { + "unicorn/prevent-abbreviations": "off", }, }, ], diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 00000000..d7afe15e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,38 @@ +name: Feature +description: An enhancement or feature +title: "[Feature]: " +labels: ["triage", "feature"] +body: + - type: textarea + id: description + attributes: + label: Description + description: A step-by-step description of the suggested feature/enhancement. + placeholder: On the user page, you should be able to... + validations: + required: true + - type: textarea + id: acceptanceCriteria + attributes: + label: Acceptance Criteria + description: What are the things that must be achieved for your ticket to be considered complete. + validations: + required: true + - type: markdown + attributes: + value: > + | Please include any screenshots which would help demonstrate the steps + and point out which parts the feature is related to + - type: textarea + id: links + attributes: + label: Links + description: Place links to supporting docs here. e.g. Figma + value: > + | [`FIGMA LINK`](LINK_HERE) + - type: textarea + id: images + attributes: + label: Images + description: Paste images or image urls + value: "![image](URL_HERE)" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..905cdc0f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,47 @@ + + +# Description + + + + + +**Closes #issue_number_here** + +# Changes proposed + +## What were you told to do? + + + +## What did you do? + + + +# Check List (Check all the applicable boxes) + +🚨Please review the [contribution guideline](CONTRIBUTING.md) for this repository. + + + + + +- [ ] My code follows the code style of this project. +- [ ] This PR does not contain plagiarized content. +- [ ] The title and description of the PR is clear and explains the approach. +- [ ] I am making a pull request against the **dev branch** (left side). +- [ ] My commit messages styles matches our requested structure. +- [ ] My code additions will fail neither code linting checks nor unit test. +- [ ] I am only making changes to files I was requested to. + +# Screenshots/Videos + + + + diff --git a/.github/workflows/dev-cicd.yml b/.github/workflows/dev-cicd.yml index aa75d6d2..f2e2fc9c 100644 --- a/.github/workflows/dev-cicd.yml +++ b/.github/workflows/dev-cicd.yml @@ -24,25 +24,27 @@ jobs: with: node-version: '20' - - name: Cache npm modules + - name: Cache pnpm modules uses: actions/cache@v3 with: - path: ~/.npm - key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} + path: ~/.pnpm-store + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | - ${{ runner.os }}-npm- + ${{ runner.os }}-pnpm- - - name: Install npm - run: npm install -g npm@9 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 - name: Install dependencies - run: npm install + run: pnpm install - name: Lint code - run: npm run lint + run: pnpm lint - name: Build project - run: npm run build + run: pnpm build deploy: runs-on: ubuntu-latest diff --git a/.github/workflows/initial-label.yml b/.github/workflows/initial-label.yml new file mode 100644 index 00000000..a091548e --- /dev/null +++ b/.github/workflows/initial-label.yml @@ -0,0 +1,18 @@ +name: Label new issues +on: + issues: + types: + - reopened + - opened +jobs: + label_issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - run: gh issue edit "$NUMBER" --add-label "$LABELS" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} + LABELS: triage diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..8bb63d2a --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,21 @@ +name: Lint + +on: + pull_request: + +jobs: + eslint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + - uses: pnpm/action-setup@v4 + with: + version: 9 + - run: pnpm install + - uses: reviewdog/action-eslint@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + eslint_flags: ". --ext js,jsx,ts,tsx --ignore-path=.gitignore" diff --git a/.github/workflows/prod-cicd.yml b/.github/workflows/prod-cicd.yml index 64dd3d30..291e8cbd 100644 --- a/.github/workflows/prod-cicd.yml +++ b/.github/workflows/prod-cicd.yml @@ -1,4 +1,3 @@ - name: Production CI/CD Pipeline on: @@ -25,25 +24,27 @@ jobs: with: node-version: '20' - - name: Cache npm modules + - name: Cache pnpm modules uses: actions/cache@v3 with: - path: ~/.npm - key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} + path: ~/.pnpm-store + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | - ${{ runner.os }}-npm- + ${{ runner.os }}-pnpm- - - name: Install npm - run: npm install -g npm@9 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 - name: Install dependencies - run: npm install + run: pnpm install - name: Lint code - run: npm run lint + run: pnpm run lint - name: Build project - run: npm run build + run: pnpm run build deploy: runs-on: ubuntu-latest diff --git a/.github/workflows/staging-cicd.yml b/.github/workflows/staging-cicd.yml new file mode 100644 index 00000000..2bbb9b80 --- /dev/null +++ b/.github/workflows/staging-cicd.yml @@ -0,0 +1,61 @@ +name: Staging CI/CD Pipeline + +on: + push: + branches: + - staging + paths-ignore: + - .github/workflows/** + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Cache pnpm modules + uses: actions/cache@v3 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm- + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Install dependencies + run: pnpm install + + - name: Lint code + run: pnpm run lint + + - name: Build project + run: pnpm run build + + deploy: + runs-on: ubuntu-latest + needs: build + + steps: + - name: Deploy to staging environment + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + password: ${{ secrets.PASSWORD }} + script: | + ./deploy_staging.sh remix diff --git a/.gitignore b/.gitignore index 80ec311f..5b531e0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules +/.react-email /.cache /build diff --git a/.prettierrc.cjs b/.prettierrc.cjs new file mode 100644 index 00000000..a9a84453 --- /dev/null +++ b/.prettierrc.cjs @@ -0,0 +1,22 @@ +/** @type {import('prettier').Config & import("@ianvs/prettier-plugin-sort-imports").PluginConfig} */ +module.exports = { + tabWidth: 2, + printWidth: 80, + jsxSingleQuote: false, + singleQuote: false, + semi: true, + trailingComma: "all", + arrowParens: "always", + endOfLine: "auto", + + plugins: [ + "@ianvs/prettier-plugin-sort-imports", + "prettier-plugin-tailwindcss", + ], + + // #region @ianvs/prettier-plugin-sort-imports + importOrder: ["", "", "^~/", "^[.][.]/", "^[.]/"], + importOrderParserPlugins: ["typescript", "jsx", "decorators-legacy"], + importOrderTypeScriptVersion: "5.0.0", + // #endregion @ianvs/prettier-plugin-sort-imports +}; diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..897af65d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..a62dbfe3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "[javascript][javascriptreact][typescript][typescriptreact]": { + "editor.formatOnSave": false, + "editor.defaultFormatter": "vscode.typescript-language-features" + }, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "always" + }, +} \ No newline at end of file diff --git a/app/components/index.ts b/app/components/index.ts index e69de29b..cb0ff5c3 100644 --- a/app/components/index.ts +++ b/app/components/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/app/components/ui/DataCard.tsx b/app/components/ui/DataCard.tsx new file mode 100644 index 00000000..9d15af17 --- /dev/null +++ b/app/components/ui/DataCard.tsx @@ -0,0 +1,25 @@ +import { ReactNode } from "react"; + +type DataCardType = { + title: string; + amount: string; + subText: string; + icon: ReactNode; +}; + +const DataCard = ({ title, amount, subText, icon }: DataCardType) => { + return ( +
+
+

{title}

+ {icon} +
+
+

${amount}

+

{subText}

+
+
+ ); +}; + +export default DataCard; diff --git a/app/components/ui/alert-dialog.tsx b/app/components/ui/alert-dialog.tsx new file mode 100644 index 00000000..7044fe66 --- /dev/null +++ b/app/components/ui/alert-dialog.tsx @@ -0,0 +1,145 @@ +"use client"; + +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; +import { buttonVariants } from "app/components/ui/button"; +import { cn } from "app/lib/utils/cn"; +import { + ComponentPropsWithoutRef, + ElementRef, + forwardRef, + HTMLAttributes, +} from "react"; + +const AlertDialog = AlertDialogPrimitive.Root; + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger; + +const AlertDialogPortal = AlertDialogPrimitive.Portal; + +const AlertDialogOverlay = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...properties }, reference) => ( + +)); +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; + +const AlertDialogContent = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...properties }, reference) => ( + + + + +)); +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; + +const AlertDialogHeader = ({ + className, + ...properties +}: HTMLAttributes) => ( +
+); +AlertDialogHeader.displayName = "AlertDialogHeader"; + +const AlertDialogFooter = ({ + className, + ...properties +}: HTMLAttributes) => ( +
+); +AlertDialogFooter.displayName = "AlertDialogFooter"; + +const AlertDialogTitle = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...properties }, reference) => ( + +)); +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; + +const AlertDialogDescription = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...properties }, reference) => ( + +)); +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName; + +const AlertDialogAction = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...properties }, reference) => ( + +)); +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; + +const AlertDialogCancel = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...properties }, reference) => ( + +)); +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; diff --git a/app/components/ui/button.tsx b/app/components/ui/button.tsx index c535a7db..1e269149 100644 --- a/app/components/ui/button.tsx +++ b/app/components/ui/button.tsx @@ -1,8 +1,7 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "app/lib/utils/cn" +import { Slot } from "@radix-ui/react-slot"; +import { cn } from "app/lib/utils/cn"; +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; const buttonVariants = cva( "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", @@ -30,27 +29,27 @@ const buttonVariants = cva( variant: "default", size: "default", }, - } -) + }, +); -export interface ButtonProps +export interface ButtonProperties extends React.ButtonHTMLAttributes, VariantProps { - asChild?: boolean + asChild?: boolean; } -const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...properties }, reference) => { + const Comp = asChild ? Slot : "button"; return ( - ) - } -) -Button.displayName = "Button" + ); + }, +); +Button.displayName = "Button"; -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/app/components/ui/card.tsx b/app/components/ui/card.tsx new file mode 100644 index 00000000..77b80c1b --- /dev/null +++ b/app/components/ui/card.tsx @@ -0,0 +1,85 @@ +import { cn } from "app/lib/utils/cn"; +import { forwardRef, HTMLAttributes } from "react"; + +const Card = forwardRef>( + ({ className, ...properties }, reference) => ( +
+ ), +); +Card.displayName = "Card"; + +const CardHeader = forwardRef>( + ({ className, ...properties }, reference) => ( +
+ ), +); +CardHeader.displayName = "CardHeader"; + +const CardTitle = forwardRef< + HTMLParagraphElement, + HTMLAttributes +>(({ className, ...properties }, reference) => ( +

+)); +CardTitle.displayName = "CardTitle"; + +const CardDescription = forwardRef< + HTMLParagraphElement, + HTMLAttributes +>(({ className, ...properties }, reference) => ( +

+)); +CardDescription.displayName = "CardDescription"; + +const CardContent = forwardRef>( + ({ className, ...properties }, reference) => ( +

+ ), +); +CardContent.displayName = "CardContent"; + +const CardFooter = forwardRef>( + ({ className, ...properties }, reference) => ( +
+ ), +); +CardFooter.displayName = "CardFooter"; + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/app/components/ui/card/card-platform.tsx b/app/components/ui/card/card-platform.tsx index d89c28d6..cf20fa37 100644 --- a/app/components/ui/card/card-platform.tsx +++ b/app/components/ui/card/card-platform.tsx @@ -1,55 +1,79 @@ import { useState } from "react"; + import { cn } from "~/lib/utils/cn"; -type PlatformCardProps = { - /** - * Displaying the platform name or title. - */ - heading: string; +type PlatformCardProperties = { + /** + * Displaying the platform name or title. + */ + heading: string; - /** - * Displaying the platform logo or icon. - */ - logo: string; - /** - * Providing a brief overview of the platform. - */ - description: string; - /** - * Additional class names for customizing the container style. - */ - containerClassName?: string; + /** + * Displaying the platform logo or icon. + */ + logo: string; + /** + * Providing a brief overview of the platform. + */ + description: string; + /** + * Additional class names for customizing the container style. + */ + containerClassName?: string; }; /** * A card component that displays platform details including a heading, logo, description, and a toggle switch. * The toggle switch allows admins to activate or deactivate the platform. - * + * * @param {PlatformCardProps} props - The properties for the component. * @returns The CardPlatform component. */ -export default function CardPlatform({ heading, logo, description, containerClassName }: PlatformCardProps) { - - const [isActivated, setIsActivated] = useState(false) +export default function CardPlatform({ + heading, + logo, + description, + containerClassName, +}: PlatformCardProperties) { + const [isActivated, setIsActivated] = useState(false); - const toggleSwitchHandler = () => setIsActivated(prev => !prev) + const toggleSwitchHandler = () => setIsActivated((previous) => !previous); - return ( -
-
-
- {heading} -
- -
-
-

{heading}

-

{description}

-
-
- ); -} \ No newline at end of file + return ( +
+
+
+ {heading} +
+ +
+
+

+ {heading} +

+

{description}

+
+
+ ); +} diff --git a/app/components/ui/input.tsx b/app/components/ui/input.tsx new file mode 100644 index 00000000..1a8ff0d3 --- /dev/null +++ b/app/components/ui/input.tsx @@ -0,0 +1,24 @@ +import { cn } from "app/lib/utils/cn"; +import * as React from "react"; + +export interface InputProperties + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...properties }, reference) => { + return ( + + ); + }, +); +Input.displayName = "Input"; + +export { Input }; diff --git a/app/components/ui/label.tsx b/app/components/ui/label.tsx new file mode 100644 index 00000000..8764b186 --- /dev/null +++ b/app/components/ui/label.tsx @@ -0,0 +1,25 @@ +"use client"; + +import * as LabelPrimitive from "@radix-ui/react-label"; +import { cn } from "app/lib/utils/cn"; +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", +); + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...properties }, reference) => ( + +)); +Label.displayName = LabelPrimitive.Root.displayName; + +export { Label }; diff --git a/app/components/ui/tabs.tsx b/app/components/ui/tabs.tsx new file mode 100644 index 00000000..61abb7b0 --- /dev/null +++ b/app/components/ui/tabs.tsx @@ -0,0 +1,54 @@ +"use client"; + +import * as TabsPrimitive from "@radix-ui/react-tabs"; +import { cn } from "app/lib/utils/cn"; +import { ComponentPropsWithoutRef, ElementRef, forwardRef } from "react"; + +const Tabs = TabsPrimitive.Root; + +const TabsList = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...properties }, reference) => ( + +)); +TabsList.displayName = TabsPrimitive.List.displayName; + +const TabsTrigger = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...properties }, reference) => ( + +)); +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; + +const TabsContent = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...properties }, reference) => ( + +)); +TabsContent.displayName = TabsPrimitive.Content.displayName; + +export { Tabs, TabsList, TabsTrigger, TabsContent }; diff --git a/app/email/templates/_components/tailwindWrapper.tsx b/app/email/templates/_components/tailwindWrapper.tsx new file mode 100644 index 00000000..0d0c8280 --- /dev/null +++ b/app/email/templates/_components/tailwindWrapper.tsx @@ -0,0 +1,14 @@ +import { Tailwind } from "@react-email/components"; +import React from "react"; + +import TailwindConfig from "../../../../tailwind.config"; + +interface Properties { + children: React.ReactNode; +} + +export default function TailwindWrapper(properties: Properties) { + const { children } = properties; + + return {children}; +} diff --git a/app/email/templates/password-reset/no-image.tsx b/app/email/templates/password-reset/no-image.tsx new file mode 100644 index 00000000..d3156be5 --- /dev/null +++ b/app/email/templates/password-reset/no-image.tsx @@ -0,0 +1,32 @@ +import { Button, Head, Html, Preview, Text } from "@react-email/components"; + +import Tailwindwrapper from "~/email/templates/_components/tailwindWrapper"; + +interface IProperties { + name: string; +} + +export default function Email(properties: IProperties) { + const { name } = properties; + + return ( + + + {`Password Reset for ${name}`} + + + Hi there, {name}! + + + + ); +} + +Email.PreviewProps = { + name: "John Doe", +} satisfies IProperties; diff --git a/app/email/templates/welcome/no-image.tsx b/app/email/templates/welcome/no-image.tsx new file mode 100644 index 00000000..dd1e09e1 --- /dev/null +++ b/app/email/templates/welcome/no-image.tsx @@ -0,0 +1,32 @@ +import { Button, Head, Html, Preview, Text } from "@react-email/components"; + +import Tailwindwrapper from "~/email/templates/_components/tailwindWrapper"; + +interface IProperties { + name: string; +} + +export default function Email(properties: IProperties) { + const { name } = properties; + + return ( + + + {`Welcome ${name}`} + + + Hi there, {name}! + + + + ); +} + +Email.PreviewProps = { + name: "John Doe", +} satisfies IProperties; diff --git a/app/entry.client.tsx b/app/entry.client.tsx index 94d5dc0d..61291328 100644 --- a/app/entry.client.tsx +++ b/app/entry.client.tsx @@ -13,6 +13,6 @@ startTransition(() => { document, - + , ); }); diff --git a/app/entry.server.tsx b/app/entry.server.tsx index 45db3229..d1ca790c 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -5,14 +5,16 @@ */ import { PassThrough } from "node:stream"; - -import type { AppLoadContext, EntryContext } from "@remix-run/node"; -import { createReadableStreamFromReadable } from "@remix-run/node"; +import { + createReadableStreamFromReadable, + type AppLoadContext, + type EntryContext, +} from "@remix-run/node"; import { RemixServer } from "@remix-run/react"; import { isbot } from "isbot"; import { renderToPipeableStream } from "react-dom/server"; -const ABORT_DELAY = 5_000; +const ABORT_DELAY = 5000; export default function handleRequest( request: Request, @@ -22,20 +24,20 @@ export default function handleRequest( // This is ignored so we can keep it in the template for visibility. Feel // free to delete this parameter in your app if you're not using it! // eslint-disable-next-line @typescript-eslint/no-unused-vars - loadContext: AppLoadContext + _loadContext: AppLoadContext, ) { return isbot(request.headers.get("user-agent") || "") ? handleBotRequest( request, responseStatusCode, responseHeaders, - remixContext + remixContext, ) : handleBrowserRequest( request, responseStatusCode, responseHeaders, - remixContext + remixContext, ); } @@ -43,7 +45,7 @@ function handleBotRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, - remixContext: EntryContext + remixContext: EntryContext, ) { return new Promise((resolve, reject) => { let shellRendered = false; @@ -65,7 +67,7 @@ function handleBotRequest( new Response(stream, { headers: responseHeaders, status: responseStatusCode, - }) + }), ); pipe(body); @@ -82,7 +84,7 @@ function handleBotRequest( console.error(error); } }, - } + }, ); setTimeout(abort, ABORT_DELAY); @@ -93,7 +95,7 @@ function handleBrowserRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, - remixContext: EntryContext + remixContext: EntryContext, ) { return new Promise((resolve, reject) => { let shellRendered = false; @@ -115,7 +117,7 @@ function handleBrowserRequest( new Response(stream, { headers: responseHeaders, status: responseStatusCode, - }) + }), ); pipe(body); @@ -132,7 +134,7 @@ function handleBrowserRequest( console.error(error); } }, - } + }, ); setTimeout(abort, ABORT_DELAY); diff --git a/app/lib/utils/cn.ts b/app/lib/utils/cn.ts index 365058ce..a5ef1935 100644 --- a/app/lib/utils/cn.ts +++ b/app/lib/utils/cn.ts @@ -1,4 +1,4 @@ -import { type ClassValue, clsx } from "clsx"; +import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { diff --git a/app/root.tsx b/app/root.tsx index 8253a92c..0b76e794 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,3 +1,5 @@ +import { cssBundleHref } from "@remix-run/css-bundle"; +import { LinksFunction } from "@remix-run/node"; import { Links, Meta, @@ -5,9 +7,8 @@ import { Scripts, ScrollRestoration, } from "@remix-run/react"; -import { LinksFunction } from "@remix-run/node"; + import styles from "./styles/global.css?url"; -import { cssBundleHref } from "@remix-run/css-bundle"; export const links: LinksFunction = () => [ { rel: "stylesheet", href: styles }, diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index 1eeb5b2f..4cc46675 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -1,4 +1,5 @@ import type { MetaFunction } from "@remix-run/node"; + import { Button } from "~/components/ui/button"; import CardPlatform from "~/components/ui/card/card-platform"; @@ -11,9 +12,9 @@ export const meta: MetaFunction = () => { export default function Index() { return ( -
+

Welcome to Remix

-