1
0

Basic transaction system

This commit is contained in:
Ahmed Al-Taiar
2023-11-14 18:54:44 -05:00
parent f6f01594ec
commit 8060e1e452
60 changed files with 2037 additions and 507 deletions

View File

@ -1,25 +0,0 @@
-- CreateTable
CREATE TABLE "Part" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL,
"description" TEXT DEFAULT 'No description provided',
"availableStock" INTEGER NOT NULL DEFAULT 0,
"imageUrl" TEXT NOT NULL DEFAULT '/no_image.png',
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- CreateTable
CREATE TABLE "User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"firstName" TEXT NOT NULL,
"lastName" TEXT NOT NULL,
"email" TEXT NOT NULL,
"hashedPassword" TEXT NOT NULL,
"salt" TEXT NOT NULL,
"resetToken" TEXT,
"resetTokenExpiresAt" DATETIME,
"roles" TEXT NOT NULL DEFAULT 'user'
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");

View File

@ -0,0 +1,41 @@
-- CreateTable
CREATE TABLE "Part" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT DEFAULT 'No description provided',
"availableStock" INTEGER NOT NULL DEFAULT 0,
"imageUrl" TEXT NOT NULL DEFAULT '/no_image.png',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Part_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "User" (
"id" SERIAL NOT NULL,
"firstName" TEXT NOT NULL,
"lastName" TEXT NOT NULL,
"email" TEXT NOT NULL,
"hashedPassword" TEXT NOT NULL,
"salt" TEXT NOT NULL,
"resetToken" TEXT,
"resetTokenExpiresAt" TIMESTAMP(3),
"roles" TEXT NOT NULL DEFAULT 'user',
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Transaction" (
"id" SERIAL NOT NULL,
"date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"userId" INTEGER NOT NULL,
CONSTRAINT "Transaction_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- AddForeignKey
ALTER TABLE "Transaction" ADD CONSTRAINT "Transaction_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,18 @@
/*
Warnings:
- Added the required column `type` to the `Transaction` table without a default value. This is not possible if the table is not empty.
*/
-- CreateEnum
CREATE TYPE "TransactionType" AS ENUM ('in', 'out');
-- AlterTable
ALTER TABLE "Part" ADD COLUMN "transactionId" INTEGER;
-- AlterTable
ALTER TABLE "Transaction" ADD COLUMN "quantities" INTEGER[],
ADD COLUMN "type" "TransactionType" NOT NULL;
-- AddForeignKey
ALTER TABLE "Part" ADD CONSTRAINT "Part_transactionId_fkey" FOREIGN KEY ("transactionId") REFERENCES "Transaction"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@ -0,0 +1,27 @@
/*
Warnings:
- You are about to drop the column `quantities` on the `Transaction` table. All the data in the column will be lost.
*/
-- DropForeignKey
ALTER TABLE "Part" DROP CONSTRAINT "Part_transactionId_fkey";
-- AlterTable
ALTER TABLE "Transaction" DROP COLUMN "quantities";
-- CreateTable
CREATE TABLE "PartTransaction" (
"id" SERIAL NOT NULL,
"quantity" INTEGER NOT NULL,
"partId" INTEGER NOT NULL,
"transactionId" INTEGER NOT NULL,
CONSTRAINT "PartTransaction_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "PartTransaction" ADD CONSTRAINT "PartTransaction_partId_fkey" FOREIGN KEY ("partId") REFERENCES "Part"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PartTransaction" ADD CONSTRAINT "PartTransaction_transactionId_fkey" FOREIGN KEY ("transactionId") REFERENCES "Transaction"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,24 @@
/*
Warnings:
- You are about to drop the `PartTransaction` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `Transaction` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropForeignKey
ALTER TABLE "PartTransaction" DROP CONSTRAINT "PartTransaction_partId_fkey";
-- DropForeignKey
ALTER TABLE "PartTransaction" DROP CONSTRAINT "PartTransaction_transactionId_fkey";
-- DropForeignKey
ALTER TABLE "Transaction" DROP CONSTRAINT "Transaction_userId_fkey";
-- DropTable
DROP TABLE "PartTransaction";
-- DropTable
DROP TABLE "Transaction";
-- DropEnum
DROP TYPE "TransactionType";

View File

@ -0,0 +1,16 @@
-- CreateEnum
CREATE TYPE "TransactionType" AS ENUM ('in', 'out');
-- CreateTable
CREATE TABLE "Transaction" (
"id" SERIAL NOT NULL,
"date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"userId" INTEGER NOT NULL,
"type" "TransactionType" NOT NULL,
"parts" JSONB[],
CONSTRAINT "Transaction_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "Transaction" ADD CONSTRAINT "Transaction_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -1,3 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "sqlite"
provider = "postgresql"

View File

@ -1,5 +1,5 @@
datasource db {
provider = "sqlite"
provider = "postgresql"
url = env("DATABASE_URL")
}
@ -15,16 +15,32 @@ model Part {
availableStock Int @default(0)
imageUrl String @default("/no_image.png")
createdAt DateTime @default(now())
transactionId Int?
}
model User {
id Int @id @default(autoincrement())
id Int @id @default(autoincrement())
firstName String
lastName String
email String @unique
email String @unique
hashedPassword String
salt String
resetToken String?
resetTokenExpiresAt DateTime?
roles String @default("user")
roles String @default("user")
transactions Transaction[]
}
enum TransactionType {
in
out
}
model Transaction {
id Int @id @default(autoincrement())
date DateTime @default(now())
user User @relation(fields: [userId], references: [id])
userId Int
type TransactionType
parts Json[] // { part: Part, quantity: Int }[]
}

View File

@ -3,9 +3,9 @@
"version": "0.0.0",
"private": true,
"dependencies": {
"@redwoodjs/api": "6.3.2",
"@redwoodjs/auth-dbauth-api": "6.3.2",
"@redwoodjs/graphql-server": "6.3.2",
"@redwoodjs/api": "6.3.3",
"@redwoodjs/auth-dbauth-api": "6.3.3",
"@redwoodjs/graphql-server": "6.3.3",
"filestack-js": "^3.27.0"
}
}

View File

@ -0,0 +1,58 @@
export const schema = gql`
type Transaction {
id: Int!
date: DateTime!
user: User!
userId: Int!
type: TransactionType!
parts: [JSON]!
}
enum TransactionType {
in
out
}
enum FilterTransactionsByType {
in
out
both
}
type UserTransactions {
transactions: [Transaction!]!
filter: FilterTransactionsByType!
}
type Query {
transactions(filter: FilterTransactionsByType!): UserTransactions!
@requireAuth(roles: "admin")
transaction(id: Int!): Transaction @requireAuth(roles: "admin")
userTransactions(
userId: Int!
filter: FilterTransactionsByType!
): UserTransactions! @requireAuth
}
input CreateTransactionInput {
date: DateTime!
userId: Int!
type: TransactionType!
parts: [JSON]!
}
input UpdateTransactionInput {
date: DateTime
userId: Int
type: TransactionType
parts: [JSON]!
}
type Mutation {
createTransaction(input: CreateTransactionInput!): Transaction! @requireAuth
returnTransaction(id: Int!, userId: Int!): Transaction! @requireAuth
updateTransaction(id: Int!, input: UpdateTransactionInput!): Transaction!
@requireAuth(roles: "admin")
deleteTransaction(id: Int!): Transaction! @requireAuth(roles: "admin")
}
`

View File

@ -0,0 +1,48 @@
export const schema = gql`
type User {
id: Int!
firstName: String!
lastName: String!
email: String!
hashedPassword: String!
salt: String!
resetToken: String
resetTokenExpiresAt: DateTime
roles: String!
transactions: [Transaction]!
}
type Query {
users: [User!]! @requireAuth(roles: "admin")
user(id: Int!): User @requireAuth(roles: "admin")
}
input CreateUserInput {
firstName: String!
lastName: String!
email: String!
hashedPassword: String!
salt: String!
resetToken: String
resetTokenExpiresAt: DateTime
roles: String!
}
input UpdateUserInput {
firstName: String
lastName: String
email: String
hashedPassword: String
salt: String
resetToken: String
resetTokenExpiresAt: DateTime
roles: String
}
type Mutation {
createUser(input: CreateUserInput!): User! @requireAuth(roles: "admin")
updateUser(id: Int!, input: UpdateUserInput!): User!
@requireAuth(roles: "admin")
deleteUser(id: Int!): User! @requireAuth(roles: "admin")
}
`

View File

@ -27,7 +27,7 @@ export const getCurrentUser = async (session: Decoded) => {
return await db.user.findUnique({
where: { id: session.id },
select: { id: true, firstName: true, roles: true },
select: { id: true, firstName: true, roles: true, transactions: true },
})
}

View File

@ -77,12 +77,22 @@ export const partPage = ({
}
export const createPart: MutationResolvers['createPart'] = ({ input }) => {
input.description =
input.description.length == 0
? 'No description provided'
: input.description
return db.part.create({
data: input,
})
}
export const updatePart: MutationResolvers['updatePart'] = ({ id, input }) => {
input.description =
input.description.length == 0
? 'No description provided'
: input.description
return db.part.update({
data: input,
where: { id },

View File

@ -0,0 +1,39 @@
import type { Prisma, Transaction } from '@prisma/client'
import type { ScenarioData } from '@redwoodjs/testing/api'
export const standard = defineScenario<Prisma.TransactionCreateArgs>({
transaction: {
one: {
data: {
type: 'in',
parts: { foo: 'bar' },
user: {
create: {
firstName: 'String',
lastName: 'String',
email: 'String2420106',
hashedPassword: 'String',
salt: 'String',
},
},
},
},
two: {
data: {
type: 'in',
parts: { foo: 'bar' },
user: {
create: {
firstName: 'String',
lastName: 'String',
email: 'String1596322',
hashedPassword: 'String',
salt: 'String',
},
},
},
},
},
})
export type StandardScenario = ScenarioData<Transaction, 'transaction'>

View File

@ -0,0 +1,68 @@
import type { Transaction } from '@prisma/client'
import {
transactions,
transaction,
createTransaction,
updateTransaction,
deleteTransaction,
} from './transactions'
import type { StandardScenario } from './transactions.scenarios'
// Generated boilerplate tests do not account for all circumstances
// and can fail without adjustments, e.g. Float.
// Please refer to the RedwoodJS Testing Docs:
// https://redwoodjs.com/docs/testing#testing-services
// https://redwoodjs.com/docs/testing#jest-expect-type-considerations
describe('transactions', () => {
scenario('returns all transactions', async (scenario: StandardScenario) => {
const result = await transactions()
expect(result.length).toEqual(Object.keys(scenario.transaction).length)
})
scenario(
'returns a single transaction',
async (scenario: StandardScenario) => {
const result = await transaction({ id: scenario.transaction.one.id })
expect(result).toEqual(scenario.transaction.one)
}
)
scenario('creates a transaction', async (scenario: StandardScenario) => {
const result = await createTransaction({
input: {
userId: scenario.transaction.two.userId,
type: 'in',
parts: { foo: 'bar' },
},
})
expect(result.userId).toEqual(scenario.transaction.two.userId)
expect(result.type).toEqual('in')
expect(result.parts).toEqual({ foo: 'bar' })
})
scenario('updates a transaction', async (scenario: StandardScenario) => {
const original = (await transaction({
id: scenario.transaction.one.id,
})) as Transaction
const result = await updateTransaction({
id: original.id,
input: { type: 'out' },
})
expect(result.type).toEqual('out')
})
scenario('deletes a transaction', async (scenario: StandardScenario) => {
const original = (await deleteTransaction({
id: scenario.transaction.one.id,
})) as Transaction
const result = await transaction({ id: original.id })
expect(result).toEqual(null)
})
})

View File

@ -0,0 +1,158 @@
import type {
QueryResolvers,
MutationResolvers,
TransactionRelationResolvers,
Part,
} from 'types/graphql'
import { UserInputError } from '@redwoodjs/graphql-server'
import { db } from 'src/lib/db'
export const transactions: QueryResolvers['transactions'] = async ({
filter,
}) => {
const transactions =
filter == 'both'
? await db.transaction.findMany({
orderBy: {
date: 'desc',
},
})
: await db.transaction.findMany({
where: {
type: filter,
},
orderBy: {
date: 'desc',
},
})
return {
transactions,
filter,
}
}
export const userTransactions: QueryResolvers['userTransactions'] = async ({
userId,
filter,
}) => {
return {
transactions:
filter == 'both'
? await db.transaction.findMany({
where: {
userId,
},
orderBy: {
date: 'desc',
},
})
: await db.transaction.findMany({
where: {
AND: {
userId,
type: filter,
},
},
orderBy: {
date: 'desc',
},
}),
filter,
}
}
export const transaction: QueryResolvers['transaction'] = ({ id }) => {
return db.transaction.findUnique({
where: { id },
})
}
export const returnTransaction: MutationResolvers['returnTransaction'] =
async ({ id, userId }) => {
const transaction = await db.transaction.findUnique({ where: { id } })
if (transaction.type == 'out' && userId == transaction.userId) {
for (const partRaw of transaction.parts) {
const transactionPart = JSON.parse(partRaw['part']) as Part
const part = await db.part.findUnique({
where: { id: transactionPart.id },
})
await db.part.update({
where: { id: part.id },
data: {
availableStock: {
increment: partRaw['quantity'],
},
},
})
}
return await db.transaction.update({
where: { id: transaction.id },
data: { type: 'in' },
})
} else return transaction
}
export const createTransaction: MutationResolvers['createTransaction'] =
async ({ input }) => {
const basket = input.parts.map((item) => {
const part: Part = JSON.parse(item['part']) as Part
return { part: part.id, quantity: item['quantity'] }
})
for (const item of basket) {
const part = await db.part.findUnique({ where: { id: item.part } })
if (!part) throw new UserInputError(`Part ${item.part} does not exist`)
if (input.type == 'out') {
if (part.availableStock < item.quantity)
throw new UserInputError(
`Cannot take out more than available stock (${part.availableStock} available, ${item.quantity} requested)`
)
await db.part.update({
where: { id: item.part },
data: { availableStock: { decrement: item.quantity } },
})
} else if (input.type == 'in') {
await db.part.update({
where: { id: item.part },
data: { availableStock: { increment: item.quantity } },
})
}
}
return db.transaction.create({
data: input,
})
}
export const updateTransaction: MutationResolvers['updateTransaction'] = ({
id,
input,
}) => {
return db.transaction.update({
data: input,
where: { id },
})
}
export const deleteTransaction: MutationResolvers['deleteTransaction'] = ({
id,
}) => {
return db.transaction.delete({
where: { id },
})
}
export const Transaction: TransactionRelationResolvers = {
user: (_obj, { root }) => {
return db.transaction.findUnique({ where: { id: root?.id } }).user()
},
}

View File

@ -0,0 +1,27 @@
import type { Prisma, User } from '@prisma/client'
import type { ScenarioData } from '@redwoodjs/testing/api'
export const standard = defineScenario<Prisma.UserCreateArgs>({
user: {
one: {
data: {
firstName: 'String',
lastName: 'String',
email: 'String6743396',
hashedPassword: 'String',
salt: 'String',
},
},
two: {
data: {
firstName: 'String',
lastName: 'String',
email: 'String6958264',
hashedPassword: 'String',
salt: 'String',
},
},
},
})
export type StandardScenario = ScenarioData<User, 'user'>

View File

@ -0,0 +1,59 @@
import type { User } from '@prisma/client'
import { users, user, createUser, updateUser, deleteUser } from './users'
import type { StandardScenario } from './users.scenarios'
// Generated boilerplate tests do not account for all circumstances
// and can fail without adjustments, e.g. Float.
// Please refer to the RedwoodJS Testing Docs:
// https://redwoodjs.com/docs/testing#testing-services
// https://redwoodjs.com/docs/testing#jest-expect-type-considerations
describe('users', () => {
scenario('returns all users', async (scenario: StandardScenario) => {
const result = await users()
expect(result.length).toEqual(Object.keys(scenario.user).length)
})
scenario('returns a single user', async (scenario: StandardScenario) => {
const result = await user({ id: scenario.user.one.id })
expect(result).toEqual(scenario.user.one)
})
scenario('creates a user', async () => {
const result = await createUser({
input: {
firstName: 'String',
lastName: 'String',
email: 'String3921297',
hashedPassword: 'String',
salt: 'String',
},
})
expect(result.firstName).toEqual('String')
expect(result.lastName).toEqual('String')
expect(result.email).toEqual('String3921297')
expect(result.hashedPassword).toEqual('String')
expect(result.salt).toEqual('String')
})
scenario('updates a user', async (scenario: StandardScenario) => {
const original = (await user({ id: scenario.user.one.id })) as User
const result = await updateUser({
id: original.id,
input: { firstName: 'String2' },
})
expect(result.firstName).toEqual('String2')
})
scenario('deletes a user', async (scenario: StandardScenario) => {
const original = (await deleteUser({ id: scenario.user.one.id })) as User
const result = await user({ id: original.id })
expect(result).toEqual(null)
})
})

View File

@ -0,0 +1,42 @@
import type {
QueryResolvers,
MutationResolvers,
UserRelationResolvers,
} from 'types/graphql'
import { db } from 'src/lib/db'
export const users: QueryResolvers['users'] = () => {
return db.user.findMany()
}
export const user: QueryResolvers['user'] = ({ id }) => {
return db.user.findUnique({
where: { id },
})
}
export const createUser: MutationResolvers['createUser'] = ({ input }) => {
return db.user.create({
data: input,
})
}
export const updateUser: MutationResolvers['updateUser'] = ({ id, input }) => {
return db.user.update({
data: input,
where: { id },
})
}
export const deleteUser: MutationResolvers['deleteUser'] = ({ id }) => {
return db.user.delete({
where: { id },
})
}
export const User: UserRelationResolvers = {
transactions: (_obj, { root }) => {
return db.user.findUnique({ where: { id: root?.id } }).transactions()
},
}

View File

@ -7,7 +7,7 @@
]
},
"devDependencies": {
"@redwoodjs/core": "6.3.2",
"@redwoodjs/core": "6.3.3",
"prettier-plugin-tailwindcss": "0.4.1"
},
"eslintConfig": {

View File

@ -6,7 +6,7 @@
# https://redwoodjs.com/docs/app-configuration-redwood-toml
[web]
title = "Arduino Parts Inventory"
title = "Parts Inventory"
port = 8910
apiUrl = "/.redwood/functions" # You can customize graphql and dbauth urls individually too: see https://redwoodjs.com/docs/app-configuration-redwood-toml#api-paths
includeEnvironmentVariables = [

View File

@ -13,10 +13,11 @@
"dependencies": {
"@mdi/js": "^7.3.67",
"@mdi/react": "^1.6.1",
"@redwoodjs/auth-dbauth-web": "6.3.2",
"@redwoodjs/forms": "6.3.2",
"@redwoodjs/router": "6.3.2",
"@redwoodjs/web": "6.3.2",
"@redwoodjs/auth-dbauth-web": "6.3.3",
"@redwoodjs/forms": "6.3.3",
"@redwoodjs/router": "6.3.3",
"@redwoodjs/web": "6.3.3",
"dayjs": "^1.11.10",
"filestack-react": "^4.0.1",
"humanize-string": "2.1.0",
"prop-types": "15.8.1",
@ -25,7 +26,7 @@
"theme-change": "^2.5.0"
},
"devDependencies": {
"@redwoodjs/vite": "6.3.2",
"@redwoodjs/vite": "6.3.3",
"@types/filestack-react": "^4.0.3",
"@types/node": "^20.8.9",
"@types/react": "18.2.14",

View File

@ -20,12 +20,18 @@ const Routes = () => {
<Route path="/admin/parts/{id:Int}" page={PartPartPage} name="part" />
<Route path="/admin/parts" page={PartPartsPage} name="parts" />
</Set>
<Set wrap={ScaffoldLayout} title="Transactions" titleTo="adminTransactions">
<Route path="/admin/transactions" page={AdminTransactionsPage} name="adminTransactions" />
</Set>
</Private>
<Set wrap={NavbarLayout}>
<Route path="/" page={HomePage} name="home" />
<Route path="/part/{id:Int}" page={PartPage} name="partDetails" />
<Route path="/basket" page={BasketPage} name="basket" />
<Private unauthenticated="login">
<Route path="/transactions" page={TransactionsPage} name="userTransactions" />
</Private>
</Set>
<Route notfound page={NotFoundPage} />

View File

@ -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 AdminMenu from './AdminMenu'
const meta: Meta<typeof AdminMenu> = {
component: AdminMenu,
}
export default meta
type Story = StoryObj<typeof AdminMenu>
export const Primary: Story = {}

View File

@ -0,0 +1,14 @@
import { render } from '@redwoodjs/testing/web'
import AdminMenu from './AdminMenu'
// Improve this test with help from the Redwood Testing Doc:
// https://redwoodjs.com/docs/testing#testing-components
describe('AdminMenu', () => {
it('renders successfully', () => {
expect(() => {
render(<AdminMenu />)
}).not.toThrow()
})
})

View File

@ -0,0 +1,53 @@
import { mdiWrench } from '@mdi/js'
import Icon from '@mdi/react'
import { Link, routes } from '@redwoodjs/router'
import { useAuth } from 'src/auth'
interface Props {
mobile: boolean
className?: string
}
const AdminMenu = ({ mobile, className }: Props) => {
const { isAuthenticated, hasRole } = useAuth()
return isAuthenticated && hasRole('admin') ? (
<div className={className}>
<details
className={`dropdown ${
mobile ? 'dropdown-start space-y-2' : 'dropdown-end space-y-4'
}`}
>
<summary className="btn btn-ghost swap swap-rotate w-12 hover:shadow-lg">
<Icon path={mdiWrench} className="h-8 w-8 text-base-content" />
</summary>
<div className="dropdown-content flex flex-col items-center space-y-3 rounded-xl bg-base-100 p-3 shadow-lg">
<ul className="space-y-3 bg-base-100 text-base-content">
<li>
<Link
to={routes.parts()}
className="btn btn-ghost w-full font-inter hover:shadow-lg"
>
Parts
</Link>
</li>
<li>
<Link
to={routes.adminTransactions()}
className="btn btn-ghost w-full hover:shadow-lg"
>
<p className="font-inter">Transactions</p>
</Link>
</li>
</ul>
</div>
</details>
</div>
) : (
<></>
)
}
export default AdminMenu

View File

@ -0,0 +1,4 @@
// Define your own mock data here:
export const standard = (/* vars, { ctx, req } */) => ({
userTransactions: [{ id: 42 }, { id: 43 }, { id: 44 }],
})

View File

@ -0,0 +1,34 @@
import type { Meta, StoryObj } from '@storybook/react'
import { Loading, Empty, Failure, Success } from './UserTransactionsCell'
import { standard } from './UserTransactionsCell.mock'
const meta: Meta = {
title: 'Cells/UserTransactionsCell',
}
export default meta
export const loading: StoryObj<typeof Loading> = {
render: () => {
return Loading ? <Loading /> : <></>
},
}
export const empty: StoryObj<typeof Empty> = {
render: () => {
return Empty ? <Empty /> : <></>
},
}
export const failure: StoryObj<typeof Failure> = {
render: (args) => {
return Failure ? <Failure error={new Error('Oh no')} {...args} /> : <></>
},
}
export const success: StoryObj<typeof Success> = {
render: (args) => {
return Success ? <Success {...standard()} {...args} /> : <></>
},
}

View File

@ -0,0 +1,42 @@
import { render } from '@redwoodjs/testing/web'
import { Loading, Empty, Failure, Success } from './AdminTransactionsCell'
import { standard } from './AdminTransactionsCell.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('UserTransactionsCell', () => {
it('renders Loading successfully', () => {
expect(() => {
render(<Loading />)
}).not.toThrow()
})
it('renders Empty successfully', async () => {
expect(() => {
render(<Empty />)
}).not.toThrow()
})
it('renders Failure successfully', async () => {
expect(() => {
render(<Failure error={new Error('Oh no')} />)
}).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(<Success userTransactions={standard().userTransactions} />)
}).not.toThrow()
})
})

View File

@ -0,0 +1,104 @@
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
import { mdiAlert } from '@mdi/js'
import { Icon } from '@mdi/react'
import type { TransactionsQuery } from 'types/graphql'
import { Link, routes } from '@redwoodjs/router'
import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web'
import TransactionListItem from '../TransactionListItem/TransactionListItem'
export const beforeQuery = ({ filter }) => {
filter = filter && ['both', 'in', 'out'].includes(filter) ? filter : 'both'
return { variables: { filter } }
}
export const QUERY = gql`
query TransactionsQuery($filter: FilterTransactionsByType!) {
transactions(filter: $filter) {
transactions {
id
date
parts
type
user {
firstName
lastName
}
}
filter
}
}
`
export const Loading = () => (
<div className="flex w-auto justify-center">
<p className="loading loading-bars loading-lg" />
</div>
)
export const Empty = () => (
<div className="flex">
<div className="alert w-auto">
<p className="text-center font-inter">It&#39;s empty in here...</p>
</div>
</div>
)
export const Failure = ({ error }: CellFailureProps) => (
<div className="flex w-auto justify-center">
<div className="alert alert-error w-auto">
<Icon path={mdiAlert} className="h-6 w-6" />
<p className="font-inter">Error! {error?.message}</p>
</div>
</div>
)
export const Success = ({
transactions,
}: CellSuccessProps<TransactionsQuery>) => {
if (transactions.transactions.length == 0) return Empty()
return (
<div className="flex flex-col space-y-6 font-inter">
<div className="dropdown">
<label tabIndex={0} className="btn m-1 normal-case">
Filter:{' '}
{transactions.filter == 'both'
? 'None'
: transactions.filter.replace(
transactions.filter[0],
transactions.filter[0].toUpperCase()
)}
</label>
<ul
tabIndex={0}
className="dropdown-content rounded-box z-10 mt-3 w-auto space-y-3 bg-base-100 p-3 shadow"
>
{['None', 'In', 'Out'].map((filter) => (
<li key={filter}>
<Link
className="btn btn-ghost w-full normal-case"
to={routes.adminTransactions({
filter: filter === 'None' ? 'both' : filter.toLowerCase(),
})}
>
{filter}
</Link>
</li>
))}
</ul>
</div>
{transactions.transactions.map((item, i) => {
return (
<TransactionListItem
transaction={item}
key={i}
returnButton={false}
admin
/>
)
})}
</div>
)
}

View File

@ -0,0 +1,6 @@
// Define your own mock data here:
export const standard = (/* vars, { ctx, req } */) => ({
basket: {
id: 42,
},
})

View File

@ -0,0 +1,34 @@
import type { Meta, StoryObj } from '@storybook/react'
import { Loading, Empty, Failure, Success } from './BasketCell'
import { standard } from './BasketCell.mock'
const meta: Meta = {
title: 'Cells/BasketCell',
}
export default meta
export const loading: StoryObj<typeof Loading> = {
render: () => {
return Loading ? <Loading /> : <></>
},
}
export const empty: StoryObj<typeof Empty> = {
render: () => {
return Empty ? <Empty /> : <></>
},
}
export const failure: StoryObj<typeof Failure> = {
render: (args) => {
return Failure ? <Failure error={new Error('Oh no')} {...args} /> : <></>
},
}
export const success: StoryObj<typeof Success> = {
render: (args) => {
return Success ? <Success {...standard()} {...args} /> : <></>
},
}

View File

@ -0,0 +1,41 @@
import { render } from '@redwoodjs/testing/web'
import { Loading, Empty, Failure, Success } from './BasketCell'
import { standard } from './BasketCell.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('BasketCell', () => {
it('renders Loading successfully', () => {
expect(() => {
render(<Loading />)
}).not.toThrow()
})
it('renders Empty successfully', async () => {
expect(() => {
render(<Empty />)
}).not.toThrow()
})
it('renders Failure successfully', async () => {
expect(() => {
render(<Failure error={new Error('Oh no')} />)
}).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(<Success basket={standard().basket} />)
}).not.toThrow()
})
})

View File

@ -0,0 +1,207 @@
import { useState } from 'react'
import { mdiMinus, mdiPlus, mdiDelete } from '@mdi/js'
import Icon from '@mdi/react'
import type { CreateTransactionInput } from 'types/graphql'
import { useMutation } from '@redwoodjs/web'
import { toast } from '@redwoodjs/web/dist/toast'
import { useAuth } from 'src/auth'
import {
getBasket,
setBasket,
removeFromBasket,
clearBasket,
} from 'src/lib/basket'
import ToastNotification from '../ToastNotification'
export const CREATE_TRANSACTION_MUTATION = gql`
mutation CreateTransactionMutation($input: CreateTransactionInput!) {
createTransaction(input: $input) {
id
}
}
`
const thumbnail = (url: string) => {
if (url.includes('no_image.png')) return url
const parts = url.split('/')
parts.splice(3, 0, 'resize=width:160')
return parts.join('/')
}
export const BasketCell = () => {
const { isAuthenticated, currentUser } = useAuth()
const [basket, setBasketState] = useState(getBasket())
const [createTransaction, { loading }] = useMutation(
CREATE_TRANSACTION_MUTATION,
{
onCompleted: () => {
toast.custom((t) => (
<ToastNotification
toast={t}
type="success"
message="Transaction complete"
/>
))
setBasketState(clearBasket())
},
onError: (error) => {
toast.custom((t) => (
<ToastNotification toast={t} type="error" message={error.message} />
))
},
}
)
const submitBasket = (input: CreateTransactionInput) => {
createTransaction({ variables: { input } })
}
return (
<div className="space-y-3">
{basket.length > 0 ? (
basket.map((item, i) => (
<div
key={i}
className="flex max-w-5xl items-center rounded-xl bg-base-200 shadow-xl"
>
<img
alt={item.part.name}
className="hidden h-20 w-20 rounded-l-xl object-cover sm:flex"
src={thumbnail(item.part.imageUrl)}
/>
<div className="m-3 w-full items-center justify-between space-y-3 sm:flex sm:space-y-0">
<p className="overflow-hidden text-ellipsis whitespace-nowrap font-inter text-lg font-bold">
{item.part.name}
</p>
<div className="flex justify-between space-x-3">
<div className="join">
<button
className={`btn join-item ${
item.quantity <= 1 ? 'btn-disabled' : ''
}`}
onClick={() => {
const newBasket = basket
newBasket[i].quantity -= 1
setBasketState(setBasket(newBasket))
}}
>
<Icon path={mdiMinus} className="h-6 w-6" />
</button>
<p className="btn join-item items-center font-inter text-lg">
{item.quantity}
</p>
<button
className={`btn join-item ${
item.quantity >= item.part.availableStock
? 'btn-disabled'
: ''
}`}
onClick={() => {
const newBasket = basket
newBasket[i].quantity += 1
setBasketState(setBasket(newBasket))
}}
>
<Icon path={mdiPlus} className="h-6 w-6" />
</button>
</div>
<button
className="btn btn-ghost hover:shadow-lg"
onClick={() => {
const newBasket = removeFromBasket(i)
if (typeof newBasket == 'string')
toast.custom((t) => (
<ToastNotification
toast={t}
type="error"
message={newBasket}
/>
))
else {
setBasketState(newBasket)
toast.custom((t) => (
<ToastNotification
toast={t}
type="success"
message={`Removed ${item.part.name} from basket`}
/>
))
}
}}
>
<Icon
path={mdiDelete}
className="h-8 w-8 text-base-content"
/>
</button>
</div>
</div>
</div>
))
) : (
<div className="flex">
<div className="alert w-auto shadow-lg">
<p className="text-center font-inter">It&#39;s empty in here...</p>
</div>
</div>
)}
{basket.length > 0 ? (
<div className="flex space-x-3 pt-3">
<button
onClick={() => {
setBasketState(clearBasket())
toast.custom((t) => (
<ToastNotification
toast={t}
type="success"
message="Basket cleared"
/>
))
}}
className="btn font-inter"
>
Clear basket
</button>
<button
disabled={loading}
onClick={() => {
if (!isAuthenticated)
toast.custom((t) => (
<ToastNotification
toast={t}
type="error"
message="You must be logged in to do that"
/>
))
else {
submitBasket({
date: new Date().toISOString(),
type: 'out',
userId: currentUser.id,
parts: basket.map((item) => ({
part: JSON.stringify(item.part),
quantity: item.quantity,
})),
})
}
}}
className={`btn btn-primary font-inter ${
loading ? 'btn-disabled' : ''
}`}
>
Checkout
</button>
</div>
) : (
<></>
)}
</div>
)
}

View File

@ -40,18 +40,25 @@ const Part = ({ part }: Props) => {
}
}
const preview = (url: string) => {
if (url.includes('no_image.png')) return url
const parts = url.split('/')
parts.splice(3, 0, 'resize=height:500')
return parts.join('/')
}
return (
<>
<div className="rw-segment">
<div className="rw-segment font-inter">
<header className="rw-segment-header">
<h2 className="rw-heading rw-heading-secondary">
Part {part.id} Detail
Part {part.id} details
</h2>
</header>
<table className="rw-table">
<tbody>
<tr>
<th>Id</th>
<th>ID</th>
<td>{part.id}</td>
</tr>
<tr>
@ -68,7 +75,13 @@ const Part = ({ part }: Props) => {
</tr>
<tr>
<th>Image</th>
<td>{part.imageUrl}</td>
<td>
<img
alt=""
src={preview(part.imageUrl)}
style={{ display: 'block', margin: '2rem 0' }}
/>
</td>
</tr>
<tr>
<th>Created at</th>

View File

@ -28,11 +28,11 @@ const PartForm = (props: PartFormProps) => {
const [imageUrl, setImageUrl] = useState(props?.part?.imageUrl)
const onSubmit = (data: FormPart) => {
console.log(imageUrl)
const dataWithImageUrl = Object.assign(data, {
const dataUpdated = Object.assign(data, {
imageUrl: imageUrl ?? '/no_image.png',
})
props.onSave(dataWithImageUrl, props?.part?.id)
props.onSave(dataUpdated, props?.part?.id)
}
const onImageUpload = (response) => {

View File

@ -1,6 +1,7 @@
import { mdiAlert } from '@mdi/js'
import Icon from '@mdi/react'
import type { FindParts } from 'types/graphql'
import { Link, routes } from '@redwoodjs/router'
import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web'
import Parts from 'src/components/Part/Parts'
@ -18,21 +19,27 @@ export const QUERY = gql`
}
`
export const Loading = () => <div>Loading...</div>
export const Loading = () => (
<div className="flex w-auto justify-center">
<p className="loading loading-bars loading-lg" />
</div>
)
export const Empty = () => {
return (
<div className="rw-text-center p-4">
<span className="font-inter">No parts yet.</span>{' '}
<Link to={routes.newPart()} className="rw-link">
{'Create one?'}
</Link>
export const Empty = () => (
<div className="flex justify-center">
<div className="alert w-auto">
<p className="text-center font-inter">It&#39;s empty in here...</p>
</div>
)
}
</div>
)
export const Failure = ({ error }: CellFailureProps) => (
<div className="rw-cell-error">{error?.message}</div>
<div className="flex w-auto justify-center">
<div className="alert alert-error w-auto">
<Icon path={mdiAlert} className="h-6 w-6" />
<p className="font-inter">Error! {error?.message}</p>
</div>
</div>
)
export const Success = ({ parts }: CellSuccessProps<FindParts>) => {

View File

@ -1,3 +1,5 @@
import type { Part } from 'types/graphql'
import { Link, routes } from '@redwoodjs/router'
import { toast } from '@redwoodjs/web/toast'
@ -6,14 +8,7 @@ import { addToBasket } from 'src/lib/basket'
import ToastNotification from '../ToastNotification'
interface Props {
part: {
id: number
name: string
description?: string
availableStock: number
imageUrl: string
createdAt: string
}
part: Part
}
const thumbnail = (url: string) => {
@ -25,66 +20,66 @@ const thumbnail = (url: string) => {
const PartsGridUnit = ({ part }: Props) => {
return (
<div className="card-compact card w-72 space-y-3 bg-base-100 font-inter shadow-xl sm:w-96">
<figure>
<img
className="h-48 object-cover"
src={thumbnail(part.imageUrl)}
width={384}
height={128}
alt={part.name + ' image'}
/>
</figure>
<div className="card-body">
<h2 className="card-title justify-between">
<Link
className="link-hover link w-auto overflow-hidden text-ellipsis whitespace-nowrap"
to={routes.partDetails({ id: part.id })}
>
<Link to={routes.partDetails({ id: part.id })}>
<div className="card-compact card w-72 space-y-3 bg-base-100 font-inter shadow-xl transition-all duration-200 hover:-translate-y-2 hover:shadow-2xl sm:w-96">
<figure>
<img
className="h-48 object-cover"
src={thumbnail(part.imageUrl)}
width={384}
height={128}
alt={part.name + ' image'}
/>
</figure>
<div className="card-body">
<h2 className="card-title justify-between">
{part.name}
</Link>
{part.availableStock == 0 ? (
<div className="badge badge-error">Out of stock</div>
) : (
<div className="badge badge-ghost whitespace-nowrap">
{part.availableStock + ' left'}
</div>
)}
</h2>
<p className="overflow-hidden text-ellipsis whitespace-nowrap">
{part.description}
</p>
<div className="card-actions justify-end">
<button
className={`btn btn-primary ${
part.availableStock == 0 ? 'btn-disabled' : ''
}`}
onClick={() => {
const newBasket = addToBasket(part, 1)
{part.availableStock == 0 ? (
<div className="badge badge-error">Out of stock</div>
) : (
<div className="badge badge-ghost whitespace-nowrap">
{part.availableStock + ' left'}
</div>
)}
</h2>
<p className="overflow-hidden text-ellipsis whitespace-nowrap">
{part.description}
</p>
<div className="card-actions justify-end">
<button
className={`btn btn-primary ${
part.availableStock == 0 ? 'btn-disabled' : ''
}`}
onClick={(event) => {
event.stopPropagation()
event.preventDefault()
if (typeof newBasket == 'string')
toast.custom((t) => (
<ToastNotification
toast={t}
type="error"
message={newBasket}
/>
))
else
toast.custom((t) => (
<ToastNotification
toast={t}
type="success"
message={`Added 1 ${part.name} to basket`}
/>
))
}}
>
Add to basket
</button>
const newBasket = addToBasket(part, 1)
if (typeof newBasket == 'string')
toast.custom((t) => (
<ToastNotification
toast={t}
type="error"
message={newBasket}
/>
))
else
toast.custom((t) => (
<ToastNotification
toast={t}
type="success"
message={`Added 1 ${part.name} to basket`}
/>
))
}}
>
Add to basket
</button>
</div>
</div>
</div>
</div>
</Link>
)
}

View File

@ -13,12 +13,12 @@ const ThemeToggle = () => {
useEffect(() => {
themeChange(false)
return () => {
themeChange(false)
themeChange(true)
}
}, [])
return (
<label className="btn btn-ghost swap swap-rotate w-12 hover:shadow-lg">
<label className="swap-rotate btn btn-ghost swap w-12 hover:shadow-lg">
<input
type="checkbox"
defaultChecked={isDark}

View File

@ -35,7 +35,7 @@ const ToastNotification = ({ type, message, toast }: Props) => (
: mdiInformation
}
/>
<p className="font-inter">{message}</p>
<p className="m-3 ml-0 font-inter">{message}</p>
</div>
)

View File

@ -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 TransactionListItem from './TransactionListItem'
const meta: Meta<typeof TransactionListItem> = {
component: TransactionListItem,
}
export default meta
type Story = StoryObj<typeof TransactionListItem>
export const Primary: Story = {}

View File

@ -0,0 +1,14 @@
import { render } from '@redwoodjs/testing/web'
import TransactionListItem from './TransactionListItem'
// Improve this test with help from the Redwood Testing Doc:
// https://redwoodjs.com/docs/testing#testing-components
describe('TransactionListItem', () => {
it('renders successfully', () => {
expect(() => {
render(<TransactionListItem />)
}).not.toThrow()
})
})

View File

@ -0,0 +1,137 @@
import { Prisma } from '@prisma/client'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import type { Part, Transaction, TransactionType } from 'types/graphql'
import { useMutation } from '@redwoodjs/web'
import { toast } from '@redwoodjs/web/dist/toast'
import { useAuth } from 'src/auth'
import ToastNotification from '../ToastNotification'
interface Props {
transaction:
| {
id: number
date: string
parts: Prisma.JsonValue[]
type: TransactionType
}
| Transaction
returnButton?: boolean
admin?: boolean
}
dayjs.extend(relativeTime)
export const UPDATE_TRANSACTION_MUTATION = gql`
mutation UpdateTransactionMutation($id: Int!, $userId: Int!) {
returnTransaction(id: $id, userId: $userId) {
id
}
}
`
const TransactionListItem = ({
transaction,
returnButton = true,
admin = false,
}: Props) => {
const elapsedTime = dayjs(transaction.date).fromNow()
const [returnTransaction, { loading }] = useMutation(
UPDATE_TRANSACTION_MUTATION,
{
onCompleted: () => {
toast.custom((t) => (
<ToastNotification
toast={t}
type="success"
message="Transaction updated"
/>
))
},
onError: (error) => {
toast.custom((t) => (
<ToastNotification toast={t} type="error" message={error.message} />
))
},
}
)
const { currentUser } = useAuth()
return (
<div className="collapse-arrow collapse max-w-5xl bg-base-200 font-inter">
<input type="checkbox" />
<div className="collapse-title text-xl font-medium">
<div className="items-center justify-between space-x-3 space-y-3 sm:flex sm:space-y-0">
<p className="overflow-hidden text-ellipsis whitespace-nowrap">
{transaction.parts.length} items
</p>
<div className="flex w-fit space-x-3">
{admin ? (
<div className="badge whitespace-nowrap bg-base-300 sm:badge-lg">{`${
(transaction as Transaction).user?.firstName
} ${(transaction as Transaction).user?.lastName}`}</div>
) : (
<></>
)}
<div
className={`badge sm:badge-lg ${
transaction.type == 'out' ? 'badge-error' : 'badge-success'
}`}
>
{transaction.type.replace(
transaction.type[0],
transaction.type[0].toUpperCase()
)}
</div>
<div className="badge whitespace-nowrap bg-base-300 sm:badge-lg">
{elapsedTime.replace(
elapsedTime[0],
elapsedTime[0].toUpperCase()
)}
</div>
</div>
</div>
</div>
<div className="collapse-content space-y-3">
<ul className="mx-4 list-disc space-y-2">
{transaction.parts.map((raw) => {
const part = JSON.parse(raw['part']) as Part
const quantity = raw['quantity']
return (
<li className="list-item" key={part.id}>
<div className="flex justify-between space-x-3">
<p>{part.name}</p>
<div className="badge badge-info">
<p>Quantity: {quantity}</p>
</div>
</div>
</li>
)
})}
</ul>
{transaction.type == 'out' && returnButton ? (
<button
className={`btn btn-primary ${loading ? 'btn-disabled' : ''}`}
disabled={loading}
onClick={() =>
returnTransaction({
variables: { id: transaction.id, userId: currentUser.id },
})
}
>
Return
</button>
) : (
<></>
)}
</div>
</div>
)
}
export default TransactionListItem

View File

@ -0,0 +1,4 @@
// Define your own mock data here:
export const standard = (/* vars, { ctx, req } */) => ({
userTransactions: [{ id: 42 }, { id: 43 }, { id: 44 }],
})

View File

@ -0,0 +1,34 @@
import type { Meta, StoryObj } from '@storybook/react'
import { Loading, Empty, Failure, Success } from './UserTransactionsCell'
import { standard } from './UserTransactionsCell.mock'
const meta: Meta = {
title: 'Cells/UserTransactionsCell',
}
export default meta
export const loading: StoryObj<typeof Loading> = {
render: () => {
return Loading ? <Loading /> : <></>
},
}
export const empty: StoryObj<typeof Empty> = {
render: () => {
return Empty ? <Empty /> : <></>
},
}
export const failure: StoryObj<typeof Failure> = {
render: (args) => {
return Failure ? <Failure error={new Error('Oh no')} {...args} /> : <></>
},
}
export const success: StoryObj<typeof Success> = {
render: (args) => {
return Success ? <Success {...standard()} {...args} /> : <></>
},
}

View File

@ -0,0 +1,41 @@
import { render } from '@redwoodjs/testing/web'
import { Loading, Empty, Failure, Success } from './UserTransactionsCell'
import { standard } from './UserTransactionsCell.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('UserTransactionsCell', () => {
it('renders Loading successfully', () => {
expect(() => {
render(<Loading />)
}).not.toThrow()
})
it('renders Empty successfully', async () => {
expect(() => {
render(<Empty />)
}).not.toThrow()
})
it('renders Failure successfully', async () => {
expect(() => {
render(<Failure error={new Error('Oh no')} />)
}).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(<Success userTransactions={standard().userTransactions} />)
}).not.toThrow()
})
})

View File

@ -0,0 +1,97 @@
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
import { mdiAlert } from '@mdi/js'
import { Icon } from '@mdi/react'
import type { UserTransactionsQuery } from 'types/graphql'
import { Link, routes } from '@redwoodjs/router'
import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web'
import TransactionListItem from '../TransactionListItem/TransactionListItem'
export const beforeQuery = ({ filter, userId }) => {
userId = userId ?? null
filter = filter && ['both', 'in', 'out'].includes(filter) ? filter : 'both'
return { variables: { filter, userId } }
}
export const QUERY = gql`
query UserTransactionsQuery(
$userId: Int!
$filter: FilterTransactionsByType!
) {
userTransactions(userId: $userId, filter: $filter) {
transactions {
id
date
parts
type
}
filter
}
}
`
export const Loading = () => (
<div className="flex w-auto justify-center">
<p className="loading loading-bars loading-lg" />
</div>
)
export const Empty = () => (
<div className="flex">
<div className="alert w-auto">
<p className="text-center font-inter">It&#39;s empty in here...</p>
</div>
</div>
)
export const Failure = ({ error }: CellFailureProps) => (
<div className="flex w-auto justify-center">
<div className="alert alert-error w-auto">
<Icon path={mdiAlert} className="h-6 w-6" />
<p className="font-inter">Error! {error?.message}</p>
</div>
</div>
)
export const Success = ({
userTransactions,
}: CellSuccessProps<UserTransactionsQuery>) => {
if (userTransactions.transactions.length == 0) return Empty()
return (
<div className="flex flex-col space-y-6 font-inter">
<div className="dropdown">
<label tabIndex={0} className="btn m-1 normal-case">
Filter:{' '}
{userTransactions.filter == 'both'
? 'None'
: userTransactions.filter.replace(
userTransactions.filter[0],
userTransactions.filter[0].toUpperCase()
)}
</label>
<ul
tabIndex={0}
className="dropdown-content rounded-box z-10 mt-3 w-auto space-y-3 bg-base-100 p-3 shadow"
>
{['None', 'In', 'Out'].map((filter) => (
<li key={filter}>
<Link
className="btn btn-ghost w-full normal-case"
to={routes.userTransactions({
filter: filter === 'None' ? 'both' : filter.toLowerCase(),
})}
>
{filter}
</Link>
</li>
))}
</ul>
</div>
{userTransactions.transactions.map((item, i) => {
return <TransactionListItem transaction={item} key={i} />
})}
</div>
)
}

View File

@ -7,6 +7,7 @@ import { Link, routes } from '@redwoodjs/router'
import { Toaster } from '@redwoodjs/web/toast'
import { useAuth } from 'src/auth'
import AdminMenu from 'src/components/AdminMenu/AdminMenu'
import NavbarAccountIcon from 'src/components/NavbarAccountIcon/NavbarAccountIcon'
import ThemeToggle from 'src/components/ThemeToggle/ThemeToggle'
import { getBasket } from 'src/lib/basket'
@ -38,20 +39,16 @@ const NavBarLayout = ({ children }: NavBarLayoutProps) => {
</Link>
</div>
<div className="ml-auto justify-end space-x-3">
{hasRole('admin') ? (
<ul className="relative hidden items-center space-x-3 lg:flex">
<li>
<Link
to={routes.parts()}
className="btn btn-ghost font-inter hover:shadow-lg"
>
Parts
</Link>
</li>
</ul>
) : (
<></>
)}
<ul className="relative hidden items-center space-x-3 lg:flex">
<li>
<Link
to={routes.userTransactions()}
className="btn btn-ghost font-inter hover:shadow-lg"
>
Transactions
</Link>
</li>
</ul>
<ThemeToggle />
<Link
to={routes.basket()}
@ -70,6 +67,7 @@ const NavBarLayout = ({ children }: NavBarLayoutProps) => {
</div>
</Link>
<NavbarAccountIcon mobile={false} className="hidden lg:block" />
<AdminMenu mobile={false} className="hidden lg:block" />
<div className="lg:hidden">
<input
id="mobile-menu-drawer"
@ -92,7 +90,11 @@ const NavBarLayout = ({ children }: NavBarLayoutProps) => {
<ul className="min-h-full w-80 space-y-3 bg-base-100 p-3 text-base-content shadow-lg">
<li>
<div className="flex items-center justify-between">
<Icon path={mdiChip} className="ml-3 h-10 text-logo" />
{hasRole('admin') ? (
<AdminMenu mobile={true} />
) : (
<Icon path={mdiChip} className="ml-3 h-10 text-logo" />
)}
<Link
to={routes.home()}
className="btn btn-ghost items-center hover:shadow-lg"
@ -104,18 +106,6 @@ const NavBarLayout = ({ children }: NavBarLayoutProps) => {
<NavbarAccountIcon mobile={true} />
</div>
</li>
{hasRole('admin') ? (
<li>
<Link
to={routes.parts()}
className="btn btn-ghost w-full hover:shadow-lg"
>
<p className="font-inter text-base">Parts</p>
</Link>
</li>
) : (
<></>
)}
<li>
<Link
to={routes.basket()}
@ -124,6 +114,14 @@ const NavBarLayout = ({ children }: NavBarLayoutProps) => {
<p className="font-inter text-base">Basket</p>
</Link>
</li>
<li>
<Link
to={routes.userTransactions()}
className="btn btn-ghost w-full hover:shadow-lg"
>
<p className="font-inter text-base">Transactions</p>
</Link>
</li>
</ul>
</div>
</div>

View File

@ -7,8 +7,8 @@ import { Toaster } from '@redwoodjs/web/toast'
type LayoutProps = {
title: string
titleTo: string
buttonLabel: string
buttonTo: string
buttonLabel?: string
buttonTo?: string
children: React.ReactNode
}
@ -24,16 +24,20 @@ const ScaffoldLayout = ({
<Toaster />
<header className="rw-header">
<div className="space-x-3">
<Link to={routes.home()} className="btn btn-ghost">
<Link to={routes.home()} className="btn btn-ghost hover:shadow-lg">
<Icon path={mdiHome} className="h-8 w-8" />
</Link>
<h1 className="rw-heading rw-heading-primary rw-button btn-ghost normal-case">
<Link to={routes[titleTo]()}>{title}</Link>
</h1>
</div>
<Link to={routes[buttonTo]()} className="rw-button btn-success">
<div className="rw-button-icon">+</div> {buttonLabel}
</Link>
{buttonLabel && buttonTo ? (
<Link to={routes[buttonTo]()} className="rw-button btn-success">
<div className="rw-button-icon">+</div> {buttonLabel}
</Link>
) : (
<></>
)}
</header>
<main className="rw-main">{children}</main>
</div>

View File

@ -0,0 +1,13 @@
import type { Meta, StoryObj } from '@storybook/react'
import AdminTransactionsPage from './AdminTransactionsPage'
const meta: Meta<typeof AdminTransactionsPage> = {
component: AdminTransactionsPage,
}
export default meta
type Story = StoryObj<typeof AdminTransactionsPage>
export const Primary: Story = {}

View File

@ -0,0 +1,14 @@
import { render } from '@redwoodjs/testing/web'
import AdminTransactionsPage from './AdminTransactionsPage'
// Improve this test with help from the Redwood Testing Doc:
// https://redwoodjs.com/docs/testing#testing-pages-layouts
describe('AdminTransactionsPage', () => {
it('renders successfully', () => {
expect(() => {
render(<AdminTransactionsPage />)
}).not.toThrow()
})
})

View File

@ -0,0 +1,50 @@
import { mdiAlert } from '@mdi/js'
import Icon from '@mdi/react'
import { FilterTransactionsByType } from 'types/graphql'
import { MetaTags } from '@redwoodjs/web'
import { useAuth } from 'src/auth'
import AdminTransactionsCell from 'src/components/AdminTransactionsCell'
interface Props {
filter: FilterTransactionsByType
}
const AdminTransactionsPage = ({ filter = 'both' }: Props) => {
const { isAuthenticated, hasRole } = useAuth()
return (
<>
<MetaTags
title="Admin Transactions"
description="Admin Transactions page"
/>
<div className="m-8">
<p className="mb-8 font-inter text-3xl font-bold">Transactions</p>
{isAuthenticated ? (
hasRole('admin') ? (
<AdminTransactionsCell filter={filter} />
) : (
<div className="flex w-auto justify-center">
<div className="alert alert-error w-auto">
<Icon path={mdiAlert} className="h-6 w-6" />
<p className="font-inter">You must be admin to do that.</p>
</div>
</div>
)
) : (
<div className="flex w-auto justify-center">
<div className="alert alert-error w-auto">
<Icon path={mdiAlert} className="h-6 w-6" />
<p className="font-inter">You must be logged in to do that.</p>
</div>
</div>
)}
</div>
</>
)
}
export default AdminTransactionsPage

View File

@ -1,169 +1,15 @@
import { useState } from 'react'
import { mdiDelete, mdiMinus, mdiPlus } from '@mdi/js'
import Icon from '@mdi/react'
import { MetaTags } from '@redwoodjs/web'
import { toast } from '@redwoodjs/web/toast'
import { useAuth } from 'src/auth'
import ToastNotification from 'src/components/ToastNotification'
import {
clearBasket,
getBasket,
removeFromBasket,
setBasket,
} from 'src/lib/basket'
const thumbnail = (url: string) => {
if (url.includes('no_image.png')) return url
const parts = url.split('/')
parts.splice(3, 0, 'resize=width:160')
return parts.join('/')
}
import { BasketCell } from 'src/components/BasketCell/BasketCell'
const BasketPage = () => {
const { isAuthenticated } = useAuth()
const [basket, setBasketState] = useState(getBasket())
return (
<>
<MetaTags title="Basket" description="Basket page" />
<div className="m-8">
<h1 className="mb-8 font-inter text-3xl font-bold">Basket</h1>
<div className="space-y-3">
{basket.length > 0 ? (
basket.map((item, i) => (
<div
key={i}
className="flex max-w-5xl items-center rounded-xl bg-base-100 shadow-xl"
>
<img
alt={item.part.name}
className="hidden h-20 w-20 rounded-l-xl object-cover sm:flex"
src={thumbnail(item.part.imageUrl)}
/>
<div className="m-3 w-full items-center justify-between space-y-3 sm:flex sm:space-y-0">
<p className="overflow-hidden text-ellipsis whitespace-nowrap font-inter text-lg font-bold">
{item.part.name}
</p>
<div className="flex justify-between space-x-3">
<div className="join">
<button
className={`btn join-item ${
item.quantity == 1 ? 'btn-disabled' : ''
}`}
onClick={() => {
const newBasket = basket
newBasket[i].quantity -= 1
setBasketState(setBasket(newBasket))
}}
>
<Icon path={mdiMinus} className="h-6 w-6" />
</button>
<p className="btn join-item items-center font-inter text-lg">
{item.quantity}
</p>
<button
className={`btn join-item ${
item.quantity == item.part.availableStock
? 'btn-disabled'
: ''
}`}
onClick={() => {
const newBasket = basket
newBasket[i].quantity += 1
setBasketState(setBasket(newBasket))
}}
>
<Icon path={mdiPlus} className="h-6 w-6" />
</button>
</div>
<button
className="btn btn-ghost hover:shadow-lg"
onClick={() => {
const newBasket = removeFromBasket(i)
if (typeof newBasket == 'string')
toast.custom((t) => (
<ToastNotification
toast={t}
type="error"
message={newBasket}
/>
))
else {
setBasketState(newBasket)
toast.custom((t) => (
<ToastNotification
toast={t}
type="success"
message={`Removed ${item.part.name} from basket`}
/>
))
}
}}
>
<Icon
path={mdiDelete}
className="h-8 w-8 text-base-content"
/>
</button>
</div>
</div>
</div>
))
) : (
<div className="flex">
<div className="alert w-auto shadow-lg">
<p className="text-center font-inter">
It&#39;s empty in here...
</p>
</div>
</div>
)}
{basket.length > 0 ? (
<div className="flex space-x-3 pt-3">
<button
onClick={() => {
setBasketState(clearBasket())
toast.custom((t) => (
<ToastNotification
toast={t}
type="success"
message="Basket cleared"
/>
))
}}
className="btn font-inter"
>
Clear basket
</button>
<button
onClick={() => {
if (!isAuthenticated)
toast.custom((t) => (
<ToastNotification
toast={t}
type="error"
message="You must be logged in to do that"
/>
))
else {
toast.custom((t) => (
<ToastNotification toast={t} type="info" message="Todo" />
))
}
}}
className="btn btn-primary font-inter"
>
Checkout
</button>
</div>
) : (
<></>
)}
</div>
<BasketCell />
</div>
</>
)

View File

@ -33,6 +33,7 @@ const ForgotPasswordPage = () => {
// The function `forgotPassword.handler` in api/src/functions/auth.js has
// been invoked, let the user know how to get the link to reset their
// password (sent in email, perhaps?)
// TODO: forgot password
toast.custom((t) => (
<ToastNotification
toast={t}

View File

@ -17,7 +17,7 @@ const HomePage = ({ page = 1, sort = 'id', order = 'ascending' }: Props) => {
<div className="my-16 text-center font-inter md:my-24">
<h1 className="text-4xl font-extrabold tracking-wide md:text-6xl">
Arduino Parts Inventory
Parts Inventory
</h1>
<p className="pt-4 text-xl">Only take what you need</p>
</div>

View File

@ -0,0 +1,13 @@
import type { Meta, StoryObj } from '@storybook/react'
import TransactionsPage from './TransactionsPage'
const meta: Meta<typeof TransactionsPage> = {
component: TransactionsPage,
}
export default meta
type Story = StoryObj<typeof TransactionsPage>
export const Primary: Story = {}

View File

@ -0,0 +1,14 @@
import { render } from '@redwoodjs/testing/web'
import TransactionsPage from './TransactionsPage'
// Improve this test with help from the Redwood Testing Doc:
// https://redwoodjs.com/docs/testing#testing-pages-layouts
describe('TransactionsPage', () => {
it('renders successfully', () => {
expect(() => {
render(<TransactionsPage />)
}).not.toThrow()
})
})

View File

@ -0,0 +1,42 @@
import { mdiAlert } from '@mdi/js'
import Icon from '@mdi/react'
import { FilterTransactionsByType } from 'types/graphql'
import { MetaTags } from '@redwoodjs/web'
import { useAuth } from 'src/auth'
import UserTransactionsCell from 'src/components/UserTransactionsCell'
interface Props {
filter: FilterTransactionsByType
}
const TransactionsPage = ({ filter = 'both' }: Props) => {
const { isAuthenticated, currentUser } = useAuth()
return (
<>
<MetaTags title="Transactions" description="Transactions page" />
<div className="m-8">
<h1 className="mb-8 font-inter text-3xl font-bold">
Transaction History
</h1>
{isAuthenticated ? (
<UserTransactionsCell
filter={filter}
userId={currentUser ? currentUser.id : -1}
/>
) : (
<div className="flex w-auto justify-center">
<div className="alert alert-error w-auto">
<Icon path={mdiAlert} className="h-6 w-6" />
<p className="font-inter">You must be logged in to do that.</p>
</div>
</div>
)}
</div>
</>
)
}
export default TransactionsPage

View File

@ -5,9 +5,6 @@
.rw-scaffold h2 {
@apply m-0;
}
.rw-scaffold ul {
@apply m-0 p-0;
}
.rw-scaffold input:-ms-input-placeholder {
@apply text-gray-500;
}

348
yarn.lock
View File

@ -4209,15 +4209,15 @@ __metadata:
languageName: node
linkType: hard
"@redwoodjs/api-server@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/api-server@npm:6.3.2"
"@redwoodjs/api-server@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/api-server@npm:6.3.3"
dependencies:
"@babel/runtime-corejs3": 7.22.15
"@fastify/http-proxy": 9.2.1
"@fastify/static": 6.11.1
"@fastify/url-data": 5.3.1
"@redwoodjs/project-config": 6.3.2
"@redwoodjs/project-config": 6.3.3
ansi-colors: 4.1.3
chalk: 4.1.2
chokidar: 3.5.3
@ -4236,13 +4236,13 @@ __metadata:
rw-api-server-watch: dist/watch.js
rw-log-formatter: dist/logFormatter/bin.js
rw-server: dist/index.js
checksum: b78b5492d35801c0219b940da49d19f5ab1e381c9f78f7cfc91a54a7f0364b28190b04db34fdd865b1509a4e9bc5d0d87722c5419a28cc60991a06177668e59d
checksum: 29eb571c542093251eb64e1e4741774280f3e5d07369c485f97623ef736181748e70eaedf60be40f4fdeccad0f0003ee177373cc6c996056a4d726c81cd23533
languageName: node
linkType: hard
"@redwoodjs/api@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/api@npm:6.3.2"
"@redwoodjs/api@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/api@npm:6.3.3"
dependencies:
"@babel/runtime-corejs3": 7.22.15
"@prisma/client": 5.3.1
@ -4266,50 +4266,49 @@ __metadata:
rw: dist/bins/redwood.js
rwfw: dist/bins/rwfw.js
tsc: dist/bins/tsc.js
checksum: 405a6247635cdab502a639ec180364aff57053d9dba83e4cd780caa3ce2015a92511c0297a6063f2b9245210f134131560578c9ca1015b69a0812e42fb2794ee
checksum: 60f296d43593ed1a6ca71d2042df8a1ee54bd8f371f222534bb5e9b45b1566e22e2d776a84fc338531bbf46b83c32faebf859b09d7516287bab069b8e20faa5a
languageName: node
linkType: hard
"@redwoodjs/auth-dbauth-api@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/auth-dbauth-api@npm:6.3.2"
"@redwoodjs/auth-dbauth-api@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/auth-dbauth-api@npm:6.3.3"
dependencies:
"@babel/runtime-corejs3": 7.22.15
base64url: 3.0.1
core-js: 3.32.2
crypto-js: 4.1.1
md5: 2.3.0
uuid: 9.0.0
checksum: f282ec993c7b25270cbc825070b736169e4924652e229948fd634e6835719e1133c9d623f81a0bd148b684f8382c7e94503f4a8d269c272d8a192d6d8f5aed23
checksum: 87ba04b2b9e7ff1c1fd0636bdc380fe9bf1e1788e83e3af28e557d6d02ea29370e85fb22a0ec184d672d45030e40e5c2d8aa22a7c1011ab3bdf8cb4226e0935c
languageName: node
linkType: hard
"@redwoodjs/auth-dbauth-web@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/auth-dbauth-web@npm:6.3.2"
"@redwoodjs/auth-dbauth-web@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/auth-dbauth-web@npm:6.3.3"
dependencies:
"@babel/runtime-corejs3": 7.22.15
"@redwoodjs/auth": 6.3.2
"@redwoodjs/auth": 6.3.3
"@simplewebauthn/browser": 7.2.0
core-js: 3.32.2
checksum: 725297f0ba9935f80443d0a4aebb1e500db94c0761ebd79a189d9e835de9329a6ca62a707dec402085dcc8f6595392b1f2e00eecda7b13307b6437b8ccc26ff1
checksum: cca850f6e5486690cdfe58bc9f3938e346a07174d1be90ca592240b58df1fb9aa97d5c9a198a8cd870fda144208a8c2caa58243b9f0fb885e44e27f3f30deace
languageName: node
linkType: hard
"@redwoodjs/auth@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/auth@npm:6.3.2"
"@redwoodjs/auth@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/auth@npm:6.3.3"
dependencies:
"@babel/runtime-corejs3": 7.22.15
core-js: 3.32.2
react: 18.2.0
checksum: 3031d01e8a3bcd0bcd046754c1f41a46f0693f10677a6c7eef39e99f6aa32eb4ca2c562498b2b8d0f0e6456d18187c3faa6c96dcca415326efe3d1a125ede48f
checksum: b11adbdb02e161792af53e996ddace608b2b189706fc33b56a02d1a7592dd478d8bfe3b3f6b96f6b5c65f8efe35bf955f5dd86db0e296a5e138f518500ea160a
languageName: node
linkType: hard
"@redwoodjs/babel-config@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/babel-config@npm:6.3.2"
"@redwoodjs/babel-config@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/babel-config@npm:6.3.3"
dependencies:
"@babel/core": ^7.22.20
"@babel/parser": ^7.22.16
@ -4324,7 +4323,7 @@ __metadata:
"@babel/register": ^7.22.15
"@babel/runtime-corejs3": 7.22.15
"@babel/traverse": ^7.22.20
"@redwoodjs/project-config": 6.3.2
"@redwoodjs/project-config": 6.3.3
babel-plugin-auto-import: 1.1.0
babel-plugin-graphql-tag: 3.3.0
babel-plugin-module-resolver: 5.0.0
@ -4332,19 +4331,19 @@ __metadata:
fast-glob: 3.3.1
graphql: 16.8.1
typescript: 5.2.2
checksum: 9a3c08256daaef6a42d735a82823746825b3e132adee811f8798f37c745c6b9d096a0d9233c7f1e29c0e709712dac69a87af3de8cf76d140fe3f5f030009de38
checksum: 2b6837d7ce952c1ba8700bee5475d63f5d1c66fe5a1840aac104289a63aa9443df9ff0c86233e935003b206a275e520cc9b83d926dee06d2517fba33d352de02
languageName: node
linkType: hard
"@redwoodjs/cli-helpers@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/cli-helpers@npm:6.3.2"
"@redwoodjs/cli-helpers@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/cli-helpers@npm:6.3.3"
dependencies:
"@babel/core": ^7.22.20
"@babel/runtime-corejs3": 7.22.15
"@opentelemetry/api": 1.4.1
"@redwoodjs/project-config": 6.3.2
"@redwoodjs/telemetry": 6.3.2
"@redwoodjs/project-config": 6.3.3
"@redwoodjs/telemetry": 6.3.3
chalk: 4.1.2
core-js: 3.32.2
execa: 5.1.1
@ -4354,13 +4353,13 @@ __metadata:
prettier: 2.8.8
prompts: 2.4.2
terminal-link: 2.1.1
checksum: a54dd0617091c50293d207704059ac88d756ba54d8e9656c74651076db992ecb79056d6cdb90f08c8ab729d42b7ede0177931b0d6c2763a73f01e9549eaddd33
checksum: 84b63e75daf82c6a10f6249e4c327202eb86827ab65a6fc0e728d41b8ac653f0babc29e7517a56e86a3d0a4e7ed8af56eebfc1e4a3f175831d73bbf1cab4ce45
languageName: node
linkType: hard
"@redwoodjs/cli@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/cli@npm:6.3.2"
"@redwoodjs/cli@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/cli@npm:6.3.3"
dependencies:
"@babel/runtime-corejs3": 7.22.15
"@iarna/toml": 2.2.5
@ -4371,15 +4370,14 @@ __metadata:
"@opentelemetry/sdk-trace-node": 1.15.2
"@opentelemetry/semantic-conventions": 1.15.2
"@prisma/internals": 5.3.1
"@redwoodjs/api-server": 6.3.2
"@redwoodjs/cli-helpers": 6.3.2
"@redwoodjs/fastify": 6.3.2
"@redwoodjs/internal": 6.3.2
"@redwoodjs/prerender": 6.3.2
"@redwoodjs/project-config": 6.3.2
"@redwoodjs/structure": 6.3.2
"@redwoodjs/telemetry": 6.3.2
"@types/secure-random-password": 0.2.1
"@redwoodjs/api-server": 6.3.3
"@redwoodjs/cli-helpers": 6.3.3
"@redwoodjs/fastify": 6.3.3
"@redwoodjs/internal": 6.3.3
"@redwoodjs/prerender": 6.3.3
"@redwoodjs/project-config": 6.3.3
"@redwoodjs/structure": 6.3.3
"@redwoodjs/telemetry": 6.3.3
boxen: 5.1.2
camelcase: 6.3.0
chalk: 4.1.2
@ -4388,7 +4386,6 @@ __metadata:
configstore: 3.1.5
core-js: 3.32.2
cross-env: 7.0.3
crypto-js: 4.1.1
decamelize: 5.0.1
dotenv-defaults: 5.0.2
enquirer: 2.4.1
@ -4408,7 +4405,6 @@ __metadata:
prisma: 5.3.1
prompts: 2.4.2
rimraf: 5.0.1
secure-random-password: 0.2.3
semver: 7.5.3
string-env-interpolation: 1.0.1
systeminformation: 5.21.7
@ -4420,23 +4416,23 @@ __metadata:
redwood: dist/index.js
rw: dist/index.js
rwfw: dist/rwfw.js
checksum: 8e00b10053bb3effa9a91453d83ca71a07e1273104d95132f64aa27e2d8200bf745b7478cf12011ec66fe80a10f7b5f0f8db7c02e7de9bca19ad4fe49fa67ac5
checksum: b40d6192b81ba6778e6324b4106f551e79c4e273f94379447417f03f5687c02ac9edac0c3535856e4a5471fe366c82791d73cf799b6f05202f8f53f856e28f8d
languageName: node
linkType: hard
"@redwoodjs/core@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/core@npm:6.3.2"
"@redwoodjs/core@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/core@npm:6.3.3"
dependencies:
"@babel/cli": 7.22.15
"@babel/runtime-corejs3": 7.22.15
"@pmmmwh/react-refresh-webpack-plugin": 0.5.10
"@redwoodjs/cli": 6.3.2
"@redwoodjs/eslint-config": 6.3.2
"@redwoodjs/internal": 6.3.2
"@redwoodjs/project-config": 6.3.2
"@redwoodjs/testing": 6.3.2
"@redwoodjs/web-server": 6.3.2
"@redwoodjs/cli": 6.3.3
"@redwoodjs/eslint-config": 6.3.3
"@redwoodjs/internal": 6.3.3
"@redwoodjs/project-config": 6.3.3
"@redwoodjs/testing": 6.3.3
"@redwoodjs/web-server": 6.3.3
babel-loader: ^9.1.3
babel-timing: 0.9.1
copy-webpack-plugin: 11.0.0
@ -4476,20 +4472,20 @@ __metadata:
rw-log-formatter: dist/bins/rw-log-formatter.js
rw-web-server: dist/bins/rw-web-server.js
rwfw: dist/bins/rwfw.js
checksum: adfea9cf231a089e5f2e5d3f4c4ab5fbab4e23ae6fd155f19539589b57687491295388ab849141323574c962df20bdc6d9599274bb29f724562bec2192ab3d7d
checksum: f588e8e03ebd11c8f3bbf741455977ae1086a38d596486aa8ea3a9cacb86379cc1956f7e4e65277872c901cb982851efa01c1bd7962198dfa701b479ac84a132
languageName: node
linkType: hard
"@redwoodjs/eslint-config@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/eslint-config@npm:6.3.2"
"@redwoodjs/eslint-config@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/eslint-config@npm:6.3.3"
dependencies:
"@babel/core": ^7.22.20
"@babel/eslint-parser": 7.22.15
"@babel/eslint-plugin": 7.22.10
"@redwoodjs/eslint-plugin": 6.3.2
"@redwoodjs/internal": 6.3.2
"@redwoodjs/project-config": 6.3.2
"@redwoodjs/eslint-plugin": 6.3.3
"@redwoodjs/internal": 6.3.3
"@redwoodjs/project-config": 6.3.3
"@typescript-eslint/eslint-plugin": 5.61.0
"@typescript-eslint/parser": 5.61.0
eslint: 8.46.0
@ -4503,42 +4499,42 @@ __metadata:
eslint-plugin-react: 7.32.2
eslint-plugin-react-hooks: 4.6.0
prettier: 2.8.8
checksum: 7bcc93633435286bf1d85a4003ad4ac7579adb29e99e0de19304a329b3c8bf35d1cb0434abac8639f33d237683c53e78defbee8ceb5612836956f9b6919bc47c
checksum: 2eba9b3bca6f4055efe39d4d6ea02c9d998795068ae99c229d67a39762b73216bf8f74108c6d59fa0f89f79b6006d92872e10f21da4b574dce28409ba828c999
languageName: node
linkType: hard
"@redwoodjs/eslint-plugin@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/eslint-plugin@npm:6.3.2"
"@redwoodjs/eslint-plugin@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/eslint-plugin@npm:6.3.3"
dependencies:
"@typescript-eslint/utils": 5.61.0
eslint: 8.46.0
checksum: e327f70873b7fc97d03b5ab7b29d0103d16468d88329d5f958f77f48c03b8de9720873e8140cbda1cffe0ad3b9cf3a945dc9d8bc3dd78435980278b30dbb36b1
checksum: 9ca180c3e9449a2c261367b8b57d09150e1e0d0a8c2fb644201534fe008bb8b48c6742949ec0d0bb9c61e674101d98f34f7475a5a6d9b2b6525ad056123028a7
languageName: node
linkType: hard
"@redwoodjs/fastify@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/fastify@npm:6.3.2"
"@redwoodjs/fastify@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/fastify@npm:6.3.3"
dependencies:
"@fastify/http-proxy": 9.2.1
"@fastify/static": 6.11.1
"@fastify/url-data": 5.3.1
"@redwoodjs/graphql-server": 6.3.2
"@redwoodjs/project-config": 6.3.2
"@redwoodjs/graphql-server": 6.3.3
"@redwoodjs/project-config": 6.3.3
ansi-colors: 4.1.3
fast-glob: 3.3.1
fastify: 4.23.2
fastify-raw-body: 4.2.2
lodash: 4.17.21
qs: 6.11.2
checksum: 8fe43bd01825fdb10f5f3f32a8d63799fd1648035beb54142e94a58e490b822da6987d2bbd6b387192338e5ca7a39877b0da509274723ca1cb0c4c099865cf90
checksum: 1d5f6ea134e18ebf6dbd1f0667b500b98215d956a65db84788916d43333a9ea8ae81ba77a55a15bb16b311f8f1b8ec14f85d1f79a7b383a3e89df5de3f328b47
languageName: node
linkType: hard
"@redwoodjs/forms@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/forms@npm:6.3.2"
"@redwoodjs/forms@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/forms@npm:6.3.3"
dependencies:
"@babel/runtime-corejs3": 7.22.15
core-js: 3.32.2
@ -4547,13 +4543,13 @@ __metadata:
peerDependencies:
graphql: 16.8.1
react: 18.2.0
checksum: 11714ddd94fb0e72b56831d986146d7603cff9e641fcca07a7722990556bc67133bb13c6be48b053cea79799a668e64bb0c0e815e55dad5a3441cae3ddcd049e
checksum: 8d0d17a557f08e7a02307b90668dfd18c4ca5b0c73ae459263c7aaf5511a83329a2c1a103b6182c6d7894dff6ba18b2305c0a6b299aa4f3d60dedb25c3d68434
languageName: node
linkType: hard
"@redwoodjs/graphql-server@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/graphql-server@npm:6.3.2"
"@redwoodjs/graphql-server@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/graphql-server@npm:6.3.3"
dependencies:
"@babel/runtime-corejs3": 7.22.15
"@envelop/core": 4.0.0
@ -4566,7 +4562,7 @@ __metadata:
"@graphql-tools/schema": 10.0.0
"@graphql-tools/utils": 10.0.1
"@opentelemetry/api": 1.4.1
"@redwoodjs/api": 6.3.2
"@redwoodjs/api": 6.3.3
core-js: 3.32.2
graphql: 16.8.1
graphql-scalars: 1.22.2
@ -4574,13 +4570,13 @@ __metadata:
graphql-yoga: 4.0.2
lodash: 4.17.21
uuid: 9.0.0
checksum: b9df83d7e7be03ecdca51075eb959c269bd529a9bf1897f52765bab886a754cea72955f35b21063b82dd8f9b281d365f59a14da9d7c98590a8e0b86fa89ad641
checksum: f66875bd697f272bc950320c321a1edfb176080972a251070c577834bc9095d2ff569a0100330844847db7a7e2a7856bd15aab58779285a50a73f659b5012e69
languageName: node
linkType: hard
"@redwoodjs/internal@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/internal@npm:6.3.2"
"@redwoodjs/internal@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/internal@npm:6.3.3"
dependencies:
"@babel/parser": ^7.22.16
"@babel/plugin-transform-react-jsx": ^7.22.15
@ -4595,9 +4591,9 @@ __metadata:
"@graphql-codegen/typescript-operations": 3.0.4
"@graphql-codegen/typescript-react-apollo": 3.3.7
"@graphql-codegen/typescript-resolvers": 3.2.1
"@redwoodjs/babel-config": 6.3.2
"@redwoodjs/graphql-server": 6.3.2
"@redwoodjs/project-config": 6.3.2
"@redwoodjs/babel-config": 6.3.3
"@redwoodjs/graphql-server": 6.3.3
"@redwoodjs/project-config": 6.3.3
"@sdl-codegen/node": 0.0.10
chalk: 4.1.2
core-js: 3.32.2
@ -4617,21 +4613,21 @@ __metadata:
bin:
rw-gen: dist/generate/generate.js
rw-gen-watch: dist/generate/watch.js
checksum: bbac68265326ec2796ef679378e14757ba94451374a27df008db8716fa777f14289fd98c3897ffe11104736ec71d3848cf55901b5bdf10602d49f21649814d55
checksum: 22c8aeedb13e6dd49d463cbe7645fbff13eb19cf1d75a8b7a6d6a25ffce1a20de530eb8e71a68821a15aa562889835cab4137995c0ff65bdf89f3ae946a5a134
languageName: node
linkType: hard
"@redwoodjs/prerender@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/prerender@npm:6.3.2"
"@redwoodjs/prerender@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/prerender@npm:6.3.3"
dependencies:
"@babel/runtime-corejs3": 7.22.15
"@redwoodjs/auth": 6.3.2
"@redwoodjs/internal": 6.3.2
"@redwoodjs/project-config": 6.3.2
"@redwoodjs/router": 6.3.2
"@redwoodjs/structure": 6.3.2
"@redwoodjs/web": 6.3.2
"@redwoodjs/auth": 6.3.3
"@redwoodjs/internal": 6.3.3
"@redwoodjs/project-config": 6.3.3
"@redwoodjs/router": 6.3.3
"@redwoodjs/structure": 6.3.3
"@redwoodjs/web": 6.3.3
"@whatwg-node/fetch": 0.9.9
babel-plugin-ignore-html-and-css-imports: 0.1.0
cheerio: 1.0.0-rc.12
@ -4641,45 +4637,45 @@ __metadata:
peerDependencies:
react: 18.2.0
react-dom: 18.2.0
checksum: a087cebf9ffec00976497eda2692893889764325d612424efed91baa4b283119ebff3bdb70c548b218f15d5b580f18263810ebb48930bbda6272f10c89e95b4f
checksum: a9db268cf8ab3893744b45931f092ee77cfff9db1f8932d52d90a7a7f5febe880b4e3bee45eea73a632e79071646a2369b96434c314310bfb9d2a622193af361
languageName: node
linkType: hard
"@redwoodjs/project-config@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/project-config@npm:6.3.2"
"@redwoodjs/project-config@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/project-config@npm:6.3.3"
dependencies:
"@iarna/toml": 2.2.5
deepmerge: 4.3.1
fast-glob: 3.3.1
string-env-interpolation: 1.0.1
checksum: 4f0c28d1a5273c9ca8d5bfe27644d238f73bf608ac14b55153a8e83ec63e228721e9590f01492b22dd98f8b7c0146daf1dc486865d7227171611ccf7899eb237
checksum: d009cddcd953a40dfbb04ea2fd32831cde17177ef0031f12a67bb407115c1dd50a738e4da2aa143479042dea9d77262b5c8f44c0b4f65db6b1afdb5de80c1543
languageName: node
linkType: hard
"@redwoodjs/router@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/router@npm:6.3.2"
"@redwoodjs/router@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/router@npm:6.3.3"
dependencies:
"@babel/runtime-corejs3": 7.22.15
"@reach/skip-nav": 0.18.0
"@redwoodjs/auth": 6.3.2
"@redwoodjs/auth": 6.3.3
core-js: 3.32.2
peerDependencies:
react: 18.2.0
react-dom: 18.2.0
checksum: 9d41bbe07b2286e10bc37e11987b4ff70e3f50874b68860c54676a21fd64952a04e65083e0d12f497af783f077491cd4925855ba68fe1fc76d39d777baeeb76a
checksum: 292bade4975ee90344106997d24ad1f18e8499c8bd95ee60b9e33cd6bff3aeadcf5c46b48923b9d0154f693ea040665d45f7339ca2f191c9ef739d5e664876b2
languageName: node
linkType: hard
"@redwoodjs/structure@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/structure@npm:6.3.2"
"@redwoodjs/structure@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/structure@npm:6.3.3"
dependencies:
"@babel/runtime-corejs3": 7.22.15
"@iarna/toml": 2.2.5
"@prisma/internals": 5.3.1
"@redwoodjs/project-config": 6.3.2
"@redwoodjs/project-config": 6.3.3
"@types/line-column": 1.0.0
camelcase: 6.3.0
core-js: 3.32.2
@ -4699,17 +4695,17 @@ __metadata:
vscode-languageserver-textdocument: 1.0.8
vscode-languageserver-types: 3.17.3
yargs-parser: 21.1.1
checksum: ee186e5b099f479b803ed7d415a27b98763ed8b3382e167208bdceb31aa47594c738e60dd2a305ef954c1edb36f6764ea0cc0714cdd84ccb0d54bd9d4bdf7057
checksum: 7b301838c7fe882d3db27083bb4b99d5dcf19635d3b8cc0d4c11667329a2255973e53262efced4979016c5931e7aece923bd85d8303b3772af0c002e20144ee2
languageName: node
linkType: hard
"@redwoodjs/telemetry@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/telemetry@npm:6.3.2"
"@redwoodjs/telemetry@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/telemetry@npm:6.3.3"
dependencies:
"@babel/runtime-corejs3": 7.22.15
"@redwoodjs/project-config": 6.3.2
"@redwoodjs/structure": 6.3.2
"@redwoodjs/project-config": 6.3.3
"@redwoodjs/structure": 6.3.3
"@whatwg-node/fetch": 0.9.9
ci-info: 3.8.0
core-js: 3.32.2
@ -4717,21 +4713,21 @@ __metadata:
systeminformation: 5.21.7
uuid: 9.0.0
yargs: 17.7.2
checksum: 6d041d6c745e74cdd60c266ffbd8d3bb4e767b03999e5464ca5a8b9cb7c125d143f589f04f780732f43e151bd08dabe4801847d37b063cee616f7750c17cfc35
checksum: 1034240860fcc333bae802a54ca096411820202812664a21bc9bb4a362567a98f09ffa40adbff339444fc53673fb085a860b3f2d05a731ef73a6a41ca04b5c85
languageName: node
linkType: hard
"@redwoodjs/testing@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/testing@npm:6.3.2"
"@redwoodjs/testing@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/testing@npm:6.3.3"
dependencies:
"@babel/runtime-corejs3": 7.22.15
"@redwoodjs/auth": 6.3.2
"@redwoodjs/babel-config": 6.3.2
"@redwoodjs/graphql-server": 6.3.2
"@redwoodjs/project-config": 6.3.2
"@redwoodjs/router": 6.3.2
"@redwoodjs/web": 6.3.2
"@redwoodjs/auth": 6.3.3
"@redwoodjs/babel-config": 6.3.3
"@redwoodjs/graphql-server": 6.3.3
"@redwoodjs/project-config": 6.3.3
"@redwoodjs/router": 6.3.3
"@redwoodjs/web": 6.3.3
"@testing-library/jest-dom": 5.16.5
"@testing-library/react": 14.0.0
"@testing-library/user-event": 14.4.3
@ -4752,17 +4748,17 @@ __metadata:
msw: 1.3.1
ts-toolbelt: 9.6.0
whatwg-fetch: 3.6.17
checksum: 32be1bebf976197707654ccadf8746f47dcf53147fac79d6475ebc6f9b8709b3171237c47685e839dc30b500f8eb1286d8abb9d3c8a8215598e8fdaff7ebb16c
checksum: 54cf5937c55526f5c89602af77e6ed945f8f24396735c007f21e36c21fc6001bb000e86556fa0e9a8cab527eb630bb24531b086284b74a90fb6df2052a2a9ba0
languageName: node
linkType: hard
"@redwoodjs/vite@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/vite@npm:6.3.2"
"@redwoodjs/vite@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/vite@npm:6.3.3"
dependencies:
"@babel/runtime-corejs3": 7.22.15
"@redwoodjs/internal": 6.3.2
"@redwoodjs/project-config": 6.3.2
"@redwoodjs/internal": 6.3.3
"@redwoodjs/project-config": 6.3.3
"@vitejs/plugin-react": 4.0.4
buffer: 6.0.3
core-js: 3.32.2
@ -4772,15 +4768,15 @@ __metadata:
rw-vite-build: bins/rw-vite-build.mjs
rw-vite-dev: bins/rw-vite-dev.mjs
vite: bins/vite.mjs
checksum: 229bdf774e4639f2fc0a967c1becae60c7efa4c8fea250e95d74c2b17ed0357d51e17a08d20ced6a372de8835db6ec57024ce8403eee1d360e1f3f306ab78c81
checksum: 692fd73f60b7c9c07f680d3f95f6d6cd23dc46ac03f47af79758b0f35a9295e82266851b9a6456007e49452e3f1c4b94574c2a8c51c0ba89faec76f3f31874d0
languageName: node
linkType: hard
"@redwoodjs/web-server@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/web-server@npm:6.3.2"
"@redwoodjs/web-server@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/web-server@npm:6.3.3"
dependencies:
"@redwoodjs/project-config": 6.3.2
"@redwoodjs/project-config": 6.3.3
chalk: 4.1.2
dotenv-defaults: 5.0.2
fast-glob: 3.3.1
@ -4788,17 +4784,17 @@ __metadata:
yargs-parser: 21.1.1
bin:
rw-web-server: dist/server.js
checksum: 21628853111f904ce065677b621282d50edcec1e60cd59a2a5dc0b0424e28631f463581aa274a9b73df2d3c1c933d2f092ad2eb83fb81e4aed61fb50d0a2f865
checksum: 5bf07ced27c7a947eec62f63736f877c8f85f8b8b2ebc43195206f658faa5184e658d40e33f877e2c56d17b502b969c8a6773dc64ef900248debde43dc9efcfc
languageName: node
linkType: hard
"@redwoodjs/web@npm:6.3.2":
version: 6.3.2
resolution: "@redwoodjs/web@npm:6.3.2"
"@redwoodjs/web@npm:6.3.3":
version: 6.3.3
resolution: "@redwoodjs/web@npm:6.3.3"
dependencies:
"@apollo/client": 3.8.4
"@babel/runtime-corejs3": 7.22.15
"@redwoodjs/auth": 6.3.2
"@redwoodjs/auth": 6.3.3
core-js: 3.32.2
graphql: 16.8.1
graphql-tag: 2.12.6
@ -4819,7 +4815,7 @@ __metadata:
storybook: dist/bins/storybook.js
tsc: dist/bins/tsc.js
webpack: dist/bins/webpack.js
checksum: c676d9edd7496ddb0ca101c0c8064fdafe1f50f640cbc31ff27ebb77875175b3bc7f8bf0ca77f0102927dd5e68b5ab5d6a789f9c988a117e3152c4f0cdebae96
checksum: ff69b49ea1faa5229d22b968f960ad184b195b4d2653ba335b943ef8a002202b4c826519ebd8fbe6dd0c67459df7d5f6e7c0c542cd6b40231d8798086d4f669f
languageName: node
linkType: hard
@ -5617,13 +5613,6 @@ __metadata:
languageName: node
linkType: hard
"@types/secure-random-password@npm:0.2.1":
version: 0.2.1
resolution: "@types/secure-random-password@npm:0.2.1"
checksum: 87f0528b7ccb907706b0cc77160c6771279508de3852213f2c4f28a83af482b016cf3e0b414f62d2e8b3713f1733ad787e3bd40c201463ac822c117856a7886a
languageName: node
linkType: hard
"@types/semver@npm:^7.3.12":
version: 7.5.3
resolution: "@types/semver@npm:7.5.3"
@ -6739,9 +6728,9 @@ __metadata:
version: 0.0.0-use.local
resolution: "api@workspace:api"
dependencies:
"@redwoodjs/api": 6.3.2
"@redwoodjs/auth-dbauth-api": 6.3.2
"@redwoodjs/graphql-server": 6.3.2
"@redwoodjs/api": 6.3.3
"@redwoodjs/auth-dbauth-api": 6.3.3
"@redwoodjs/graphql-server": 6.3.3
filestack-js: ^3.27.0
languageName: unknown
linkType: soft
@ -9049,13 +9038,6 @@ __metadata:
languageName: node
linkType: hard
"crypto-js@npm:4.1.1":
version: 4.1.1
resolution: "crypto-js@npm:4.1.1"
checksum: 50cc66a35f2738171d9a6d80c85ba7d00cb6440b756db035ba9ccd03032c0a803029a62969ecd4c844106c980af87687c64b204dd967989379c4f354fb482d37
languageName: node
linkType: hard
"crypto-random-string@npm:^1.0.0":
version: 1.0.0
resolution: "crypto-random-string@npm:1.0.0"
@ -9365,6 +9347,13 @@ __metadata:
languageName: node
linkType: hard
"dayjs@npm:^1.11.10":
version: 1.11.10
resolution: "dayjs@npm:1.11.10"
checksum: 4de9af50639d47df87f2e15fa36bb07e0f9ed1e9c52c6caa1482788ee9a384d668f1dbd00c54f82aaab163db07d61d2899384b8254da3a9184fc6deca080e2fe
languageName: node
linkType: hard
"debounce@npm:^1.2.0":
version: 1.2.1
resolution: "debounce@npm:1.2.1"
@ -18237,7 +18226,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "root-workspace-0b6124@workspace:."
dependencies:
"@redwoodjs/core": 6.3.2
"@redwoodjs/core": 6.3.3
prettier-plugin-tailwindcss: 0.4.1
languageName: unknown
linkType: soft
@ -18422,22 +18411,6 @@ __metadata:
languageName: node
linkType: hard
"secure-random-password@npm:0.2.3":
version: 0.2.3
resolution: "secure-random-password@npm:0.2.3"
dependencies:
secure-random: ^1.1.2
checksum: ced04529b96724b921b82b7527e1ba2f1299c3f327f941076e9edaea66445118b67a6b7f6edfe3b705a240e595de49144b2b796116c994cb52eb52efdd3d51b5
languageName: node
linkType: hard
"secure-random@npm:^1.1.2":
version: 1.1.2
resolution: "secure-random@npm:1.1.2"
checksum: 612934cd5b1ea217d5e248a16ff2752411474997ede1f460ff37fe3214eedfd66ef6a5936ff76b3a5df3d057a8d2d4ed48298f5500bf837beb911522caac7f5c
languageName: node
linkType: hard
"select-hose@npm:^2.0.0":
version: 2.0.0
resolution: "select-hose@npm:2.0.0"
@ -20936,17 +20909,18 @@ __metadata:
dependencies:
"@mdi/js": ^7.3.67
"@mdi/react": ^1.6.1
"@redwoodjs/auth-dbauth-web": 6.3.2
"@redwoodjs/forms": 6.3.2
"@redwoodjs/router": 6.3.2
"@redwoodjs/vite": 6.3.2
"@redwoodjs/web": 6.3.2
"@redwoodjs/auth-dbauth-web": 6.3.3
"@redwoodjs/forms": 6.3.3
"@redwoodjs/router": 6.3.3
"@redwoodjs/vite": 6.3.3
"@redwoodjs/web": 6.3.3
"@types/filestack-react": ^4.0.3
"@types/node": ^20.8.9
"@types/react": 18.2.14
"@types/react-dom": 18.2.6
autoprefixer: ^10.4.16
daisyui: ^3.9.3
dayjs: ^1.11.10
filestack-react: ^4.0.1
humanize-string: 2.1.0
postcss: ^8.4.31