diff --git a/apps/client/.babelrc.json b/apps/client/.babelrc.json new file mode 100644 index 00000000..86efa43b --- /dev/null +++ b/apps/client/.babelrc.json @@ -0,0 +1,14 @@ +{ + "presets": [ + "@babel/preset-typescript", + "@babel/preset-env", + [ + "@nrwl/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/apps/client/.storybook/main.js b/apps/client/.storybook/main.js new file mode 100644 index 00000000..2484425c --- /dev/null +++ b/apps/client/.storybook/main.js @@ -0,0 +1,18 @@ +const rootMain = require('../../../.storybook/main') + +module.exports = { + ...rootMain, + core: { ...rootMain.core, builder: 'webpack5' }, + stories: ['../**/*.stories.@(js|jsx|ts|tsx)'], + addons: [...rootMain.addons, '@nrwl/react/plugins/storybook'], + webpackFinal: async (config, { configType }) => { + // apply any global webpack configs that might have been specified in .storybook/main.js + if (rootMain.webpackFinal) { + config = await rootMain.webpackFinal(config, { configType }) + } + + // add your own webpack tweaks if needed + + return config + }, +} diff --git a/apps/client/.storybook/tsconfig.json b/apps/client/.storybook/tsconfig.json new file mode 100644 index 00000000..61b74d70 --- /dev/null +++ b/apps/client/.storybook/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": {}, + "include": ["**/*.ts", "**/*.tsx", "**/**/*.ts", "**/**/*.tsx"] +} diff --git a/apps/client/components/Maintenance.stories.tsx b/apps/client/components/Maintenance.stories.tsx new file mode 100644 index 00000000..468d8d2a --- /dev/null +++ b/apps/client/components/Maintenance.stories.tsx @@ -0,0 +1,18 @@ +import type { Story, Meta } from '@storybook/react' +import Maintenance from './Maintenance.tsx' +import React from 'react' + +export default { + title: 'components/Maintenance.tsx', + component: Maintenance, +} as Meta + +const Template: Story = () => { + return ( + <> + + + ) +} + +export const Base = Template.bind({}) diff --git a/apps/client/pages/api/auth/[...nextauth].ts b/apps/client/pages/api/auth/[...nextauth].ts index a641f0ac..485d75cb 100644 --- a/apps/client/pages/api/auth/[...nextauth].ts +++ b/apps/client/pages/api/auth/[...nextauth].ts @@ -2,7 +2,8 @@ import NextAuth from 'next-auth' import type { SessionStrategy, NextAuthOptions } from 'next-auth' import CredentialsProvider from 'next-auth/providers/credentials' import { z } from 'zod' -import { PrismaClient, type Prisma } from '@prisma/client' +import { PrismaClient, AuthUserRole } from '@prisma/client' +import type { Prisma } from '@prisma/client' import { PrismaAdapter } from '@auth/prisma-adapter' import type { SharedType } from '@maybe-finance/shared' import bcrypt from 'bcrypt' @@ -36,6 +37,7 @@ async function validateCredentials(credentials: any): Promise { - const { firstName, lastName, email, password } = credentials + const { firstName, lastName, email, password, isAdmin } = credentials if (!firstName || !lastName) { throw new Error('Both first name and last name are required.') } + const isDevelopment = process.env.NODE_ENV === 'development' const hashedPassword = await bcrypt.hash(password, 10) return createAuthUser({ firstName, @@ -65,6 +69,7 @@ async function createNewAuthUser(credentials: { name: `${firstName} ${lastName}`, email, password: hashedPassword, + role: isAdmin && isDevelopment ? AuthUserRole.admin : AuthUserRole.user, }) } @@ -94,10 +99,14 @@ export const authOptions = { lastName: { label: 'Last name', type: 'text', placeholder: 'Last name' }, email: { label: 'Email', type: 'email', placeholder: 'hello@maybe.co' }, password: { label: 'Password', type: 'password' }, + isAdmin: { label: 'Admin', type: 'checkbox' }, }, async authorize(credentials) { - const { firstName, lastName, email, password } = await validateCredentials( - credentials + const { firstName, lastName, email, password, isAdmin } = await validateCredentials( + { + ...credentials, + isAdmin: Boolean(credentials?.isAdmin), + } ) const existingUser = await getAuthUserByEmail(email) @@ -114,7 +123,7 @@ export const authOptions = { throw new Error('Invalid credentials provided.') } - return createNewAuthUser({ firstName, lastName, email, password }) + return createNewAuthUser({ firstName, lastName, email, password, isAdmin }) }, }), ], @@ -126,6 +135,7 @@ export const authOptions = { token.firstName = authUser.firstName token.lastName = authUser.lastName token.name = authUser.name + token.role = authUser.role } return token }, @@ -136,6 +146,7 @@ export const authOptions = { session.firstName = token.firstName session.lastName = token.lastName session.name = token.name + session.role = token.role return session }, }, diff --git a/apps/client/pages/register.tsx b/apps/client/pages/register.tsx index 2552a3dd..6a0a64f5 100644 --- a/apps/client/pages/register.tsx +++ b/apps/client/pages/register.tsx @@ -1,5 +1,5 @@ import { useState, type ReactElement } from 'react' -import { Input, InputPassword, Button } from '@maybe-finance/design-system' +import { Input, InputPassword, Button, Checkbox } from '@maybe-finance/design-system' import { FullPageLayout } from '@maybe-finance/client/features' import { signIn, useSession } from 'next-auth/react' import { useRouter } from 'next/router' @@ -15,6 +15,7 @@ export default function RegisterPage() { const [isValid, setIsValid] = useState(false) const [errorMessage, setErrorMessage] = useState(null) const [isLoading, setIsLoading] = useState(false) + const [isAdmin, setIsAdmin] = useState(false) const { data: session } = useSession() const router = useRouter() @@ -38,6 +39,7 @@ export default function RegisterPage() { password, firstName, lastName, + isAdmin, redirect: false, }) @@ -108,6 +110,8 @@ export default function RegisterPage() { ) : null} + +