From 53e0070fd8196e7c432db400d0cb92a703c0b6cb Mon Sep 17 00:00:00 2001 From: Ahmed Al-Taiar Date: Sat, 4 Nov 2023 17:32:14 -0400 Subject: [PATCH] Part browsing --- api/src/graphql/parts.sdl.ts | 22 ++ api/src/services/parts/parts.ts | 65 ++++- web/src/Routes.tsx | 13 +- .../NavbarAccountIcon/NavbarAccountIcon.tsx | 11 +- web/src/components/Part/Part/Part.tsx | 2 +- web/src/components/Part/PartForm/PartForm.tsx | 8 +- .../PartDetailsCell/PartDetailsCell.mock.ts | 4 + .../PartDetailsCell.stories.tsx | 34 +++ .../PartDetailsCell/PartDetailsCell.test.tsx | 41 +++ .../PartDetailsCell/PartDetailsCell.tsx | 99 ++++++++ .../components/PartsCell/PartsCell.mock.ts | 4 + .../PartsCell/PartsCell.stories.tsx | 34 +++ .../components/PartsCell/PartsCell.test.tsx | 41 +++ web/src/components/PartsCell/PartsCell.tsx | 238 ++++++++++++++++++ .../PartsGridUnit/PartsGridUnit.stories.tsx | 25 ++ .../PartsGridUnit/PartsGridUnit.test.tsx | 14 ++ .../PartsGridUnit/PartsGridUnit.tsx | 66 +++++ web/src/index.css | 10 - web/src/layouts/NavbarLayout/NavbarLayout.tsx | 49 ++-- .../layouts/ScaffoldLayout/ScaffoldLayout.tsx | 14 +- .../ForgotPasswordPage/ForgotPasswordPage.tsx | 2 +- web/src/pages/HomePage/HomePage.tsx | 28 ++- web/src/pages/LoginPage/LoginPage.tsx | 2 +- web/src/pages/PartPage/PartPage.stories.tsx | 13 + web/src/pages/PartPage/PartPage.test.tsx | 14 ++ web/src/pages/PartPage/PartPage.tsx | 21 ++ web/src/pages/SignupPage/SignupPage.tsx | 3 +- web/src/scaffold.css | 2 +- 28 files changed, 817 insertions(+), 62 deletions(-) create mode 100644 web/src/components/PartDetailsCell/PartDetailsCell.mock.ts create mode 100644 web/src/components/PartDetailsCell/PartDetailsCell.stories.tsx create mode 100644 web/src/components/PartDetailsCell/PartDetailsCell.test.tsx create mode 100644 web/src/components/PartDetailsCell/PartDetailsCell.tsx create mode 100644 web/src/components/PartsCell/PartsCell.mock.ts create mode 100644 web/src/components/PartsCell/PartsCell.stories.tsx create mode 100644 web/src/components/PartsCell/PartsCell.test.tsx create mode 100644 web/src/components/PartsCell/PartsCell.tsx create mode 100644 web/src/components/PartsGridUnit/PartsGridUnit.stories.tsx create mode 100644 web/src/components/PartsGridUnit/PartsGridUnit.test.tsx create mode 100644 web/src/components/PartsGridUnit/PartsGridUnit.tsx create mode 100644 web/src/pages/PartPage/PartPage.stories.tsx create mode 100644 web/src/pages/PartPage/PartPage.test.tsx create mode 100644 web/src/pages/PartPage/PartPage.tsx diff --git a/api/src/graphql/parts.sdl.ts b/api/src/graphql/parts.sdl.ts index 7395a1e..3e5334a 100644 --- a/api/src/graphql/parts.sdl.ts +++ b/api/src/graphql/parts.sdl.ts @@ -8,7 +8,29 @@ export const schema = gql` createdAt: DateTime! } + type PartPage { + parts: [Part!]! + count: Int! + page: Int! + sort: SortMethod! + order: SortOrder! + } + + enum SortMethod { + id + name + description + stock + createdAt + } + + enum SortOrder { + ascending + descending + } + type Query { + partPage(page: Int, sort: SortMethod, order: SortOrder): PartPage @skipAuth parts: [Part!]! @skipAuth part(id: Int!): Part @skipAuth } diff --git a/api/src/services/parts/parts.ts b/api/src/services/parts/parts.ts index 63b31e6..8f6b409 100644 --- a/api/src/services/parts/parts.ts +++ b/api/src/services/parts/parts.ts @@ -1,8 +1,18 @@ import * as Filestack from 'filestack-js' -import type { QueryResolvers, MutationResolvers } from 'types/graphql' +import type { + QueryResolvers, + MutationResolvers, + SortMethod, + SortOrder, +} from 'types/graphql' import { db } from 'src/lib/db' +const PARTS_PER_PAGE = 8 + +const removeEnding = (input: string): string => + input.endsWith('ending') ? input.slice(0, -6) : input + export const parts: QueryResolvers['parts'] = () => { return db.part.findMany() } @@ -13,6 +23,59 @@ export const part: QueryResolvers['part'] = ({ id }) => { }) } +export const partPage = ({ + page = 1, + sort = 'id', + order = 'ascending', +}: { + page: number + sort: SortMethod + order: SortOrder +}) => { + const offset = (page - 1) * PARTS_PER_PAGE + let orderByCase + + switch (sort) { + case 'id': + orderByCase = { id: removeEnding(order) } + break + + case 'name': + orderByCase = { name: removeEnding(order) } + break + + case 'createdAt': + orderByCase = { createdAt: removeEnding(order) } + break + + case 'description': + orderByCase = { description: removeEnding(order) } + break + + case 'stock': + orderByCase = { + availableStock: removeEnding(order), + } + break + + default: + orderByCase = { id: removeEnding(order) } + break + } + + return { + parts: db.part.findMany({ + take: PARTS_PER_PAGE, + skip: offset, + orderBy: orderByCase, + }), + count: db.part.count(), + page, + sort, + order, + } +} + export const createPart: MutationResolvers['createPart'] = ({ input }) => { return db.part.create({ data: input, diff --git a/web/src/Routes.tsx b/web/src/Routes.tsx index 4268f80..0c34cb6 100644 --- a/web/src/Routes.tsx +++ b/web/src/Routes.tsx @@ -1,12 +1,3 @@ -// In this file, all Page components from 'src/pages` are auto-imported. Nested -// directories are supported, and should be uppercase. Each subdirectory will be -// prepended onto the component name. -// -// Examples: -// -// 'src/pages/HomePage/HomePage.js' -> HomePage -// 'src/pages/Admin/BooksPage/BooksPage.js' -> AdminBooksPage - import { Router, Route, Set, Private } from '@redwoodjs/router' import NavbarLayout from 'src/layouts/NavbarLayout' @@ -21,6 +12,7 @@ const Routes = () => { + @@ -29,9 +21,12 @@ const Routes = () => { + + + ) diff --git a/web/src/components/NavbarAccountIcon/NavbarAccountIcon.tsx b/web/src/components/NavbarAccountIcon/NavbarAccountIcon.tsx index 1c5634a..c1571f5 100644 --- a/web/src/components/NavbarAccountIcon/NavbarAccountIcon.tsx +++ b/web/src/components/NavbarAccountIcon/NavbarAccountIcon.tsx @@ -11,7 +11,7 @@ interface Props { } const NavbarAccountIcon = ({ mobile, className }: Props) => { - const { isAuthenticated, currentUser, logOut } = useAuth() + const { isAuthenticated, currentUser, logOut, hasRole } = useAuth() return isAuthenticated ? (
@@ -21,11 +21,16 @@ const NavbarAccountIcon = ({ mobile, className }: Props) => { }`} > - +

- Hello, {currentUser.firstName}! + {currentUser ? `Hello, ${currentUser.firstName}!` : ``}

diff --git a/web/src/components/PartDetailsCell/PartDetailsCell.mock.ts b/web/src/components/PartDetailsCell/PartDetailsCell.mock.ts new file mode 100644 index 0000000..9ea466a --- /dev/null +++ b/web/src/components/PartDetailsCell/PartDetailsCell.mock.ts @@ -0,0 +1,4 @@ +// Define your own mock data here: +export const standard = (/* vars, { ctx, req } */) => ({ + partDetails: [{ id: 42 }, { id: 43 }, { id: 44 }], +}) diff --git a/web/src/components/PartDetailsCell/PartDetailsCell.stories.tsx b/web/src/components/PartDetailsCell/PartDetailsCell.stories.tsx new file mode 100644 index 0000000..b8e1ed7 --- /dev/null +++ b/web/src/components/PartDetailsCell/PartDetailsCell.stories.tsx @@ -0,0 +1,34 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import { Loading, Empty, Failure, Success } from './PartDetailsCell' +import { standard } from './PartDetailsCell.mock' + +const meta: Meta = { + title: 'Cells/PartDetailsCell', +} + +export default meta + +export const loading: StoryObj = { + render: () => { + return Loading ? : <> + }, +} + +export const empty: StoryObj = { + render: () => { + return Empty ? : <> + }, +} + +export const failure: StoryObj = { + render: (args) => { + return Failure ? : <> + }, +} + +export const success: StoryObj = { + render: (args) => { + return Success ? : <> + }, +} diff --git a/web/src/components/PartDetailsCell/PartDetailsCell.test.tsx b/web/src/components/PartDetailsCell/PartDetailsCell.test.tsx new file mode 100644 index 0000000..b687802 --- /dev/null +++ b/web/src/components/PartDetailsCell/PartDetailsCell.test.tsx @@ -0,0 +1,41 @@ +import { render } from '@redwoodjs/testing/web' +import { Loading, Empty, Failure, Success } from './PartDetailsCell' +import { standard } from './PartDetailsCell.mock' + +// Generated boilerplate tests do not account for all circumstances +// and can fail without adjustments, e.g. Float and DateTime types. +// Please refer to the RedwoodJS Testing Docs: +// https://redwoodjs.com/docs/testing#testing-cells +// https://redwoodjs.com/docs/testing#jest-expect-type-considerations + +describe('PartDetailsCell', () => { + it('renders Loading successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) + + it('renders Empty successfully', async () => { + expect(() => { + render() + }).not.toThrow() + }) + + it('renders Failure successfully', async () => { + expect(() => { + render() + }).not.toThrow() + }) + + // When you're ready to test the actual output of your component render + // you could test that, for example, certain text is present: + // + // 1. import { screen } from '@redwoodjs/testing/web' + // 2. Add test: expect(screen.getByText('Hello, world')).toBeInTheDocument() + + it('renders Success successfully', async () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/web/src/components/PartDetailsCell/PartDetailsCell.tsx b/web/src/components/PartDetailsCell/PartDetailsCell.tsx new file mode 100644 index 0000000..aa43eb8 --- /dev/null +++ b/web/src/components/PartDetailsCell/PartDetailsCell.tsx @@ -0,0 +1,99 @@ +import { useState } from 'react' + +import { mdiAlert, mdiPlus, mdiMinus } from '@mdi/js' +import { Icon } from '@mdi/react' +import type { FindPartById } from 'types/graphql' + +import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web' + +export const QUERY = gql` + query FindPartById($id: Int!) { + part: part(id: $id) { + name + description + availableStock + imageUrl + } + } +` + +export const Loading = () => ( +
+

+

+) + +export const Empty = () => ( +
+
+

It's empty in here...

+
+
+) + +export const Failure = ({ error }: CellFailureProps) => ( +
+
+ +

Error! {error?.message}

+
+
+) + +const image = (url: string, size: number) => { + if (url.includes('no_image.png')) return url + const parts = url.split('/') + parts.splice(3, 0, `resize=height:${size}`) + return parts.join('/') +} + +export const Success = ({ part }: CellSuccessProps) => { + const [toTake, setToTake] = useState(1) + return ( +
+
+

{part.name}

+
+ +
+
+
+ {part.name} +
+
+
+

{part.description}

+
+

+ Current stock: {part.availableStock} +

+
+
+ +

+ {toTake} +

+ +
+ +
+
+
+ ) +} diff --git a/web/src/components/PartsCell/PartsCell.mock.ts b/web/src/components/PartsCell/PartsCell.mock.ts new file mode 100644 index 0000000..344d650 --- /dev/null +++ b/web/src/components/PartsCell/PartsCell.mock.ts @@ -0,0 +1,4 @@ +// Define your own mock data here: +export const standard = (/* vars, { ctx, req } */) => ({ + parts: [{ id: 42 }, { id: 43 }, { id: 44 }], +}) diff --git a/web/src/components/PartsCell/PartsCell.stories.tsx b/web/src/components/PartsCell/PartsCell.stories.tsx new file mode 100644 index 0000000..cad853f --- /dev/null +++ b/web/src/components/PartsCell/PartsCell.stories.tsx @@ -0,0 +1,34 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import { Loading, Empty, Failure, Success } from './PartsCell' +import { standard } from './PartsCell.mock' + +const meta: Meta = { + title: 'Cells/PartsCell', +} + +export default meta + +export const loading: StoryObj = { + render: () => { + return Loading ? : <> + }, +} + +export const empty: StoryObj = { + render: () => { + return Empty ? : <> + }, +} + +export const failure: StoryObj = { + render: (args) => { + return Failure ? : <> + }, +} + +export const success: StoryObj = { + render: (args) => { + return Success ? : <> + }, +} diff --git a/web/src/components/PartsCell/PartsCell.test.tsx b/web/src/components/PartsCell/PartsCell.test.tsx new file mode 100644 index 0000000..25a7708 --- /dev/null +++ b/web/src/components/PartsCell/PartsCell.test.tsx @@ -0,0 +1,41 @@ +import { render } from '@redwoodjs/testing/web' +import { Loading, Empty, Failure, Success } from './PartsCell' +import { standard } from './PartsCell.mock' + +// Generated boilerplate tests do not account for all circumstances +// and can fail without adjustments, e.g. Float and DateTime types. +// Please refer to the RedwoodJS Testing Docs: +// https://redwoodjs.com/docs/testing#testing-cells +// https://redwoodjs.com/docs/testing#jest-expect-type-considerations + +describe('PartsCell', () => { + it('renders Loading successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) + + it('renders Empty successfully', async () => { + expect(() => { + render() + }).not.toThrow() + }) + + it('renders Failure successfully', async () => { + expect(() => { + render() + }).not.toThrow() + }) + + // When you're ready to test the actual output of your component render + // you could test that, for example, certain text is present: + // + // 1. import { screen } from '@redwoodjs/testing/web' + // 2. Add test: expect(screen.getByText('Hello, world')).toBeInTheDocument() + + it('renders Success successfully', async () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/web/src/components/PartsCell/PartsCell.tsx b/web/src/components/PartsCell/PartsCell.tsx new file mode 100644 index 0000000..321711c --- /dev/null +++ b/web/src/components/PartsCell/PartsCell.tsx @@ -0,0 +1,238 @@ +/* eslint-disable jsx-a11y/no-noninteractive-tabindex */ +import { + mdiAlert, + mdiChevronRight, + mdiChevronLeft, + mdiChevronDoubleRight, + mdiChevronDoubleLeft, +} from '@mdi/js' +import { Icon } from '@mdi/react' +import type { PartsQuery, SortMethod, SortOrder } from 'types/graphql' + +import { Link, routes } from '@redwoodjs/router' +import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web' + +import PartsGridUnit from '../PartsGridUnit/PartsGridUnit' + +const POSTS_PER_PAGE = 8 + +export const beforeQuery = ({ page, sort, order }) => { + page = page ? parseInt(page, 10) : 1 + sort = + sort && + (['createdAt', 'description', 'id', 'name', 'stock'].includes(sort) + ? sort + : 'id') + order = + order && (['ascending', 'descending'].includes(order) ? order : 'ascending') + + return { variables: { page, sort, order } } +} + +export const QUERY = gql` + query PartsQuery($page: Int, $sort: SortMethod, $order: SortOrder) { + partPage(page: $page, sort: $sort, order: $order) { + parts { + id + name + description + availableStock + imageUrl + } + count + page + sort + order + } + } +` + +export const Loading = () => ( +
+

+

+) + +export const Empty = () => ( +
+
+

It's empty in here...

+
+
+) + +export const Failure = ({ error }: CellFailureProps) => ( +
+
+ +

Error! {error?.message}

+
+
+) + +export const Success = ({ partPage }: CellSuccessProps) => { + const sortMethodToText = (sortByText: string) => { + switch (sortByText as SortMethod) { + case 'createdAt': + sortByText = 'Created at' + break + + case 'description': + sortByText = 'Description' + break + + case 'id': + sortByText = 'ID' + break + + case 'name': + sortByText = 'Name' + break + + case 'stock': + sortByText = 'Stock' + break + } + + return sortByText + } + + const sortOrderToText = (orderText: string) => { + switch (orderText as SortOrder) { + case 'ascending': + orderText = 'Ascending' + break + + case 'descending': + orderText = 'Descending' + break + } + + return orderText + } + + if (partPage.count == 0) return Empty() + else { + const sortByText: string = sortMethodToText(partPage.sort) + const orderText: string = sortOrderToText(partPage.order) + + return ( +
+
+
+ +
    + {['id', 'createdAt', 'description', 'name', 'stock'].map( + (sort) => ( +
  • + + {sortMethodToText(sort)} + +
  • + ) + )} +
+
+
+ +
    + {['ascending', 'descending'].map((order) => ( +
  • + + {sortOrderToText(order)} + +
  • + ))} +
+
+
+
+ {partPage.parts.map((part) => ( + + ))} +
+
+ + + + + + +

+ Page {partPage.page} of {Math.ceil(partPage.count / POSTS_PER_PAGE)} +

+ + + + + + +
+
+ ) + } +} diff --git a/web/src/components/PartsGridUnit/PartsGridUnit.stories.tsx b/web/src/components/PartsGridUnit/PartsGridUnit.stories.tsx new file mode 100644 index 0000000..9affb92 --- /dev/null +++ b/web/src/components/PartsGridUnit/PartsGridUnit.stories.tsx @@ -0,0 +1,25 @@ +// Pass props to your component by passing an `args` object to your story +// +// ```tsx +// export const Primary: Story = { +// args: { +// propName: propValue +// } +// } +// ``` +// +// See https://storybook.js.org/docs/react/writing-stories/args. + +import type { Meta, StoryObj } from '@storybook/react' + +import PartsGridUnit from './PartsGridUnit' + +const meta: Meta = { + component: PartsGridUnit, +} + +export default meta + +type Story = StoryObj + +export const Primary: Story = {} diff --git a/web/src/components/PartsGridUnit/PartsGridUnit.test.tsx b/web/src/components/PartsGridUnit/PartsGridUnit.test.tsx new file mode 100644 index 0000000..ea0ed45 --- /dev/null +++ b/web/src/components/PartsGridUnit/PartsGridUnit.test.tsx @@ -0,0 +1,14 @@ +import { render } from '@redwoodjs/testing/web' + +import PartsGridUnit from './PartsGridUnit' + +// Improve this test with help from the Redwood Testing Doc: +// https://redwoodjs.com/docs/testing#testing-components + +describe('PartsGridUnit', () => { + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/web/src/components/PartsGridUnit/PartsGridUnit.tsx b/web/src/components/PartsGridUnit/PartsGridUnit.tsx new file mode 100644 index 0000000..887a001 --- /dev/null +++ b/web/src/components/PartsGridUnit/PartsGridUnit.tsx @@ -0,0 +1,66 @@ +import { Link, routes } from '@redwoodjs/router' + +interface Props { + part: { + id: number + name: string + description?: string + availableStock: number + imageUrl: string + } +} + +const thumbnail = (url: string) => { + if (url.includes('no_image.png')) return url + const parts = url.split('/') + parts.splice(3, 0, 'resize=width:384') + return parts.join('/') +} + +const PartsGridUnit = ({ part }: Props) => { + return ( +
+
+ {part.name +
+
+

+ + {part.name} + + {part.availableStock == 0 ? ( +
Out of stock
+ ) : ( +
+ {part.availableStock + ' left'} +
+ )} +

+

+ {part.description} +

+
+ +
+
+
+ // TODO: add to basket funcionality + ) +} + +export default PartsGridUnit diff --git a/web/src/index.css b/web/src/index.css index b31cb33..b5c61c9 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -1,13 +1,3 @@ -/** - * START --- SETUP TAILWINDCSS EDIT - * - * `yarn rw setup ui tailwindcss` placed these directives here - * to inject Tailwind's styles into your CSS. - * For more information, see: https://tailwindcss.com/docs/installation - */ @tailwind base; @tailwind components; @tailwind utilities; -/** - * END --- SETUP TAILWINDCSS EDIT - */ diff --git a/web/src/layouts/NavbarLayout/NavbarLayout.tsx b/web/src/layouts/NavbarLayout/NavbarLayout.tsx index 92e714d..1983f85 100644 --- a/web/src/layouts/NavbarLayout/NavbarLayout.tsx +++ b/web/src/layouts/NavbarLayout/NavbarLayout.tsx @@ -3,6 +3,7 @@ import Icon from '@mdi/react' import { Link, routes } from '@redwoodjs/router' +import { useAuth } from 'src/auth' import NavbarAccountIcon from 'src/components/NavbarAccountIcon/NavbarAccountIcon' import ThemeToggle from 'src/components/ThemeToggle/ThemeToggle' @@ -11,9 +12,11 @@ type NavBarLayoutProps = { } const NavBarLayout = ({ children }: NavBarLayoutProps) => { + const { hasRole } = useAuth() + return ( <> -
+
{
- {/*
    -
  • - - FAQ - -
  • -
*/} + {hasRole('admin') ? ( +
    +
  • + + Parts + +
  • +
+ ) : ( + <> + )}
@@ -75,14 +82,18 @@ const NavBarLayout = ({ children }: NavBarLayoutProps) => {
- {/*
  • - -

    FAQ

    - -
  • */} + {hasRole('admin') ? ( +
  • + +

    Parts

    + +
  • + ) : ( + <> + )}
    diff --git a/web/src/layouts/ScaffoldLayout/ScaffoldLayout.tsx b/web/src/layouts/ScaffoldLayout/ScaffoldLayout.tsx index 3f78cc6..fcc3ece 100644 --- a/web/src/layouts/ScaffoldLayout/ScaffoldLayout.tsx +++ b/web/src/layouts/ScaffoldLayout/ScaffoldLayout.tsx @@ -1,3 +1,6 @@ +import { mdiHome } from '@mdi/js' +import Icon from '@mdi/react' + import { Link, routes } from '@redwoodjs/router' import { Toaster } from '@redwoodjs/web/toast' @@ -20,9 +23,14 @@ const ScaffoldLayout = ({
    -

    - {title} -

    +
    + + + +

    + {title} +

    +
    +
    {buttonLabel} diff --git a/web/src/pages/ForgotPasswordPage/ForgotPasswordPage.tsx b/web/src/pages/ForgotPasswordPage/ForgotPasswordPage.tsx index 05e6d9e..8a793bd 100644 --- a/web/src/pages/ForgotPasswordPage/ForgotPasswordPage.tsx +++ b/web/src/pages/ForgotPasswordPage/ForgotPasswordPage.tsx @@ -22,7 +22,7 @@ const ForgotPasswordPage = () => { }, []) const onSubmit = async (data: { email: string }) => { - const response = await forgotPassword(data.email) + const response = await forgotPassword(data.email.toLowerCase()) if (response.error) { toast.error(response.error) diff --git a/web/src/pages/HomePage/HomePage.tsx b/web/src/pages/HomePage/HomePage.tsx index a35adfa..6d92211 100644 --- a/web/src/pages/HomePage/HomePage.tsx +++ b/web/src/pages/HomePage/HomePage.tsx @@ -1,19 +1,27 @@ -import { Link, routes } from '@redwoodjs/router' +import { SortMethod, SortOrder } from 'types/graphql' + import { MetaTags } from '@redwoodjs/web' -const HomePage = () => { +import PartsCell from 'src/components/PartsCell' + +interface Props { + page: number + sort: SortMethod + order: SortOrder +} + +const HomePage = ({ page = 1, sort = 'id', order = 'ascending' }: Props) => { return ( <> -

    HomePage

    -

    - Find me in ./web/src/pages/HomePage/HomePage.tsx -

    -

    - My default route is named home, link to me with ` - Home` -

    +
    +

    + Arduino Parts Inventory +

    +

    Only take what you need

    +
    + ) } diff --git a/web/src/pages/LoginPage/LoginPage.tsx b/web/src/pages/LoginPage/LoginPage.tsx index d122d40..bde21a3 100644 --- a/web/src/pages/LoginPage/LoginPage.tsx +++ b/web/src/pages/LoginPage/LoginPage.tsx @@ -30,7 +30,7 @@ const LoginPage = () => { const onSubmit = async (data: Record) => { const response = await logIn({ - username: data.email, + username: data.email.toLowerCase(), password: data.password, }) diff --git a/web/src/pages/PartPage/PartPage.stories.tsx b/web/src/pages/PartPage/PartPage.stories.tsx new file mode 100644 index 0000000..748bda9 --- /dev/null +++ b/web/src/pages/PartPage/PartPage.stories.tsx @@ -0,0 +1,13 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import PartPage from './PartPage' + +const meta: Meta = { + component: PartPage, +} + +export default meta + +type Story = StoryObj + +export const Primary: Story = {} diff --git a/web/src/pages/PartPage/PartPage.test.tsx b/web/src/pages/PartPage/PartPage.test.tsx new file mode 100644 index 0000000..f8c8e3c --- /dev/null +++ b/web/src/pages/PartPage/PartPage.test.tsx @@ -0,0 +1,14 @@ +import { render } from '@redwoodjs/testing/web' + +import PartPage from './PartPage' + +// Improve this test with help from the Redwood Testing Doc: +// https://redwoodjs.com/docs/testing#testing-pages-layouts + +describe('PartPage', () => { + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/web/src/pages/PartPage/PartPage.tsx b/web/src/pages/PartPage/PartPage.tsx new file mode 100644 index 0000000..1f5192f --- /dev/null +++ b/web/src/pages/PartPage/PartPage.tsx @@ -0,0 +1,21 @@ +import { MetaTags } from '@redwoodjs/web' + +import PartDetailsCell from 'src/components/PartDetailsCell' + +interface Props { + id: number +} + +const PartPage = ({ id }: Props) => { + return ( + <> + + +
    + +
    + + ) +} + +export default PartPage diff --git a/web/src/pages/SignupPage/SignupPage.tsx b/web/src/pages/SignupPage/SignupPage.tsx index 218b5a1..66e580f 100644 --- a/web/src/pages/SignupPage/SignupPage.tsx +++ b/web/src/pages/SignupPage/SignupPage.tsx @@ -31,7 +31,7 @@ const SignupPage = () => { const onSubmit = async (data: Record) => { const response = await signUp({ - username: data.email, + username: data.email.toLowerCase(), password: data.password, firstName: data.firstName, lastName: data.lastName, @@ -114,6 +114,7 @@ const SignupPage = () => { message: 'Email is not valid', }, }} + inputMode="email" /> diff --git a/web/src/scaffold.css b/web/src/scaffold.css index 826ad48..5ce8c9f 100644 --- a/web/src/scaffold.css +++ b/web/src/scaffold.css @@ -107,7 +107,7 @@ @apply input input-bordered w-full max-w-xs font-inter text-base-content; } .rw-textarea { - @apply textarea textarea-bordered font-inter max-w-xs w-full text-base-content; + @apply textarea textarea-bordered text-base font-inter max-w-xs w-full text-base-content; } .rw-check-radio-items { @apply flex justify-items-center;