From 126be8d8c6a05066b252299de8606bca00ebc54d Mon Sep 17 00:00:00 2001 From: Sylver Date: Thu, 15 Feb 2024 19:20:35 +0800 Subject: [PATCH] feat: change password page --- .../api/src/modules/user/user.resolver.ts | 21 +++- packages/api/src/modules/user/user.service.ts | 13 +- packages/api/src/schema.gql | 1 + packages/web/package.json | 2 +- packages/web/src/@generated/gql.ts | 8 ++ packages/web/src/@generated/graphql.ts | 56 +++++++++ .../web/src/components/toast/useToasts.tsx | 9 +- packages/web/src/containers/login-form.tsx | 2 - packages/web/src/containers/password-form.tsx | 45 +++++++ packages/web/src/hooks/useUser.tsx | 17 +-- .../src/pages/dashboard/preferences/+Page.tsx | 28 ++++- .../preferences/change-password/+Page.tsx | 57 +++++++++ packages/web/vite.config.ts | 2 - pnpm-lock.yaml | 116 ++++++++++-------- 14 files changed, 304 insertions(+), 73 deletions(-) create mode 100644 packages/web/src/containers/password-form.tsx create mode 100644 packages/web/src/pages/dashboard/preferences/change-password/+Page.tsx diff --git a/packages/api/src/modules/user/user.resolver.ts b/packages/api/src/modules/user/user.resolver.ts index 40f11e8..5e1f8d8 100644 --- a/packages/api/src/modules/user/user.resolver.ts +++ b/packages/api/src/modules/user/user.resolver.ts @@ -30,7 +30,7 @@ export class UserResolver { @InjectRepository(UserVerification) private readonly verificationRepo: EntityRepository, private readonly authService: AuthService, private readonly userService: UserService, - private readonly inviteService: InviteService + private readonly inviteService: InviteService, ) {} @Query(() => User) @@ -51,7 +51,7 @@ export class UserResolver { @UserId() userId: string, @Parent() user: User, @Args('first', { nullable: true }) limit: number = 0, - @Args('after', { nullable: true }) cursor?: string + @Args('after', { nullable: true }) cursor?: string, ): Promise { if (userId !== user.id) throw new UnauthorizedException(); if (limit > 100) limit = 100; @@ -74,7 +74,7 @@ export class UserResolver { @UserId() userId: string, @Parent() user: User, @Args('first', { nullable: true }) limit: number = 0, - @Args('after', { nullable: true }) cursor?: string + @Args('after', { nullable: true }) cursor?: string, ): Promise { if (userId !== user.id) throw new UnauthorizedException(); if (limit > 100) limit = 100; @@ -119,11 +119,22 @@ export class UserResolver { return this.userService.createUser(data, invite); } + @Mutation(() => Boolean) + @UseGuards(JWTAuthGuard) + async changePassword( + @UserId() userId: string, + @Args('currentPassword') currentPassword: string, + @Args('newPassword') newPassword: string, + ) { + await this.userService.changePassword(userId, currentPassword, newPassword); + return true; + } + @Mutation(() => Boolean) @UseGuards(JWTAuthGuard) async resendVerificationEmail( @UserId() userId: string, - @Args('data', { nullable: true }) body?: ResendVerificationEmailDto + @Args('data', { nullable: true }) body?: ResendVerificationEmailDto, ) { const user = await this.userService.getUser(userId, false); const latestVerification = await this.verificationRepo.findOne( @@ -137,7 +148,7 @@ export class UserResolver { orderBy: { expiresAt: 'DESC', }, - } + }, ); if (latestVerification && latestVerification.expiresAt.getTime() > Date.now() + UserResolver.MIN_RESEND_INTERVAL) { diff --git a/packages/api/src/modules/user/user.service.ts b/packages/api/src/modules/user/user.service.ts index eb529a0..182cb14 100644 --- a/packages/api/src/modules/user/user.service.ts +++ b/packages/api/src/modules/user/user.service.ts @@ -125,7 +125,7 @@ export class UserService { throw new ConflictException('You must provide an email address to create a user.'); } - const hashedPassword = await bcrypt.hash(data.password, 10); + const hashedPassword = await bcrypt.hash(data.password, 12); const user = this.userRepo.create({ id: generateContentId(), secret: nanoid(), @@ -190,6 +190,17 @@ export class UserService { } } + async changePassword(userId: string, currentPassword: string, newPassword: string) { + const user = await this.userRepo.findOneOrFail(userId); + const passwordMatches = await bcrypt.compare(currentPassword, user.password); + if (!passwordMatches) { + throw new BadRequestException('Invalid password'); + } + + user.password = await bcrypt.hash(newPassword, 12); + await this.userRepo.persistAndFlush(user); + } + @Cron(CronExpression.EVERY_HOUR) async deleteExpiredVerifications() { await this.verificationRepo.nativeDelete({ diff --git a/packages/api/src/schema.gql b/packages/api/src/schema.gql index 0c986cd..3ad9e8f 100644 --- a/packages/api/src/schema.gql +++ b/packages/api/src/schema.gql @@ -102,6 +102,7 @@ type Link { } type Mutation { + changePassword(currentPassword: String!, newPassword: String!): Boolean! confirmOTP(otpCode: String!): Boolean! createInvite: Invite! createLink(destination: String!, host: String): Link! diff --git a/packages/web/package.json b/packages/web/package.json index fe5983d..0221806 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -11,6 +11,7 @@ }, "scripts": { "build": "tsc --noEmit && rm -rf ./dist/* && vavite build && tsup && rm -rf ./dist/server", + "lint": "eslint --fix ./src/**/*.{ts,tsx}", "start": "node ./dist/index.js", "watch": "vavite serve" }, @@ -57,7 +58,6 @@ "vavite": "^4.0.3", "vike": "^0.4.161", "vite": "^5.1.1", - "vite-plugin-eslint": "^1.8.1", "vite-plugin-graphql-codegen": "^3.3.6", "yup": "^1.3.3" } diff --git a/packages/web/src/@generated/gql.ts b/packages/web/src/@generated/gql.ts index 13cbbd5..d6aa832 100644 --- a/packages/web/src/@generated/gql.ts +++ b/packages/web/src/@generated/gql.ts @@ -38,6 +38,8 @@ const documents = { '\n mutation DisableOTP($otpCode: String!) {\n disableOTP(otpCode: $otpCode)\n }\n': types.DisableOtpDocument, '\n query UserQueryWithToken {\n user {\n ...RegularUser\n token\n otpEnabled\n }\n }\n': types.UserQueryWithTokenDocument, + '\n mutation ChangePassword($oldPassword: String!, $newPassword: String!) {\n changePassword(currentPassword: $oldPassword, newPassword: $newPassword)\n }\n': + types.ChangePasswordDocument, '\n query GetFile($fileId: ID!) {\n file(fileId: $fileId) {\n id\n type\n displayName\n size\n sizeFormatted\n textContent\n isOwner\n metadata {\n height\n width\n }\n paths {\n view\n thumbnail\n direct\n }\n urls {\n view\n }\n }\n }\n': types.GetFileDocument, '\n mutation DeleteFile($fileId: ID!, $deleteKey: String) {\n deleteFile(fileId: $fileId, key: $deleteKey)\n }\n': @@ -158,6 +160,12 @@ export function graphql( export function graphql( source: '\n query UserQueryWithToken {\n user {\n ...RegularUser\n token\n otpEnabled\n }\n }\n', ): (typeof documents)['\n query UserQueryWithToken {\n user {\n ...RegularUser\n token\n otpEnabled\n }\n }\n']; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql( + source: '\n mutation ChangePassword($oldPassword: String!, $newPassword: String!) {\n changePassword(currentPassword: $oldPassword, newPassword: $newPassword)\n }\n', +): (typeof documents)['\n mutation ChangePassword($oldPassword: String!, $newPassword: String!) {\n changePassword(currentPassword: $oldPassword, newPassword: $newPassword)\n }\n']; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/packages/web/src/@generated/graphql.ts b/packages/web/src/@generated/graphql.ts index fad6e2e..d5f4040 100644 --- a/packages/web/src/@generated/graphql.ts +++ b/packages/web/src/@generated/graphql.ts @@ -118,6 +118,7 @@ export type Link = { export type Mutation = { __typename?: 'Mutation'; + changePassword: Scalars['Boolean']['output']; confirmOTP: Scalars['Boolean']['output']; createInvite: Invite; createLink: Link; @@ -131,6 +132,11 @@ export type Mutation = { resendVerificationEmail: Scalars['Boolean']['output']; }; +export type MutationChangePasswordArgs = { + currentPassword: Scalars['String']['input']; + newPassword: Scalars['String']['input']; +}; + export type MutationConfirmOtpArgs = { otpCode: Scalars['String']['input']; }; @@ -458,6 +464,13 @@ export type UserQueryWithTokenQuery = { }; }; +export type ChangePasswordMutationVariables = Exact<{ + oldPassword: Scalars['String']['input']; + newPassword: Scalars['String']['input']; +}>; + +export type ChangePasswordMutation = { __typename?: 'Mutation'; changePassword: boolean }; + export type GetFileQueryVariables = Exact<{ fileId: Scalars['ID']['input']; }>; @@ -1250,6 +1263,49 @@ export const UserQueryWithTokenDocument = { }, ], } as unknown as DocumentNode; +export const ChangePasswordDocument = { + kind: 'Document', + definitions: [ + { + kind: 'OperationDefinition', + operation: 'mutation', + name: { kind: 'Name', value: 'ChangePassword' }, + variableDefinitions: [ + { + kind: 'VariableDefinition', + variable: { kind: 'Variable', name: { kind: 'Name', value: 'oldPassword' } }, + type: { kind: 'NonNullType', type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } } }, + }, + { + kind: 'VariableDefinition', + variable: { kind: 'Variable', name: { kind: 'Name', value: 'newPassword' } }, + type: { kind: 'NonNullType', type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } } }, + }, + ], + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'changePassword' }, + arguments: [ + { + kind: 'Argument', + name: { kind: 'Name', value: 'currentPassword' }, + value: { kind: 'Variable', name: { kind: 'Name', value: 'oldPassword' } }, + }, + { + kind: 'Argument', + name: { kind: 'Name', value: 'newPassword' }, + value: { kind: 'Variable', name: { kind: 'Name', value: 'newPassword' } }, + }, + ], + }, + ], + }, + }, + ], +} as unknown as DocumentNode; export const GetFileDocument = { kind: 'Document', definitions: [ diff --git a/packages/web/src/components/toast/useToasts.tsx b/packages/web/src/components/toast/useToasts.tsx index b5165e7..86b92a0 100644 --- a/packages/web/src/components/toast/useToasts.tsx +++ b/packages/web/src/components/toast/useToasts.tsx @@ -1,12 +1,11 @@ -import { useContext } from "react"; -import { ToastContext } from "./context"; +import { useContext } from 'react'; +import { ToastContext } from './context'; export const useToasts = () => { const createToast = useContext(ToastContext); if (!createToast) { - // todo: this should be an error, but it seems like it can be undefined. - // maybe due to concurrent rendering? idk shit about fuck. - return () => undefined; + if (typeof window === 'undefined') return () => {}; + throw new Error('useToasts must be used within a ToastProvider'); } return createToast; diff --git a/packages/web/src/containers/login-form.tsx b/packages/web/src/containers/login-form.tsx index e9b6952..1e6391b 100644 --- a/packages/web/src/containers/login-form.tsx +++ b/packages/web/src/containers/login-form.tsx @@ -39,11 +39,9 @@ export const LoginForm: FC = () => { setLoginInfo(values); setInvalidOTP(false); await user.login(values); - console.log('ball'); setError(null); redirect(); } catch (error: any) { - console.log(error); if (user.otpRequired && error.message.toLowerCase().includes('invalid otp')) { setInvalidOTP(true); return; diff --git a/packages/web/src/containers/password-form.tsx b/packages/web/src/containers/password-form.tsx new file mode 100644 index 0000000..a206c62 --- /dev/null +++ b/packages/web/src/containers/password-form.tsx @@ -0,0 +1,45 @@ +import { Form, Formik } from 'formik'; +import type { FC } from 'react'; +import { Fragment } from 'react'; +import * as Yup from 'yup'; +import { Input } from '../components/input/input'; +import { Submit } from '../components/input/submit'; + +interface PasswordData { + oldPassword: string; + newPassword: string; +} + +interface PasswordFormProps { + onSubmit: (data: PasswordData) => Promise | void; +} + +const schema = Yup.object().shape({ + oldPassword: Yup.string().required().min(5), + newPassword: Yup.string().required().min(5), +}); + +export const PasswordForm: FC = ({ onSubmit }) => { + return ( + + { + await onSubmit(values); + }} + > +
+ + + + Change Password + +
+
+
+ ); +}; diff --git a/packages/web/src/hooks/useUser.tsx b/packages/web/src/hooks/useUser.tsx index dd7ab4f..385f9f7 100644 --- a/packages/web/src/hooks/useUser.tsx +++ b/packages/web/src/hooks/useUser.tsx @@ -1,10 +1,10 @@ -import { useEffect, useState } from 'react'; import type { CombinedError, TypedDocumentNode } from '@urql/preact'; import { useMutation, useQuery } from '@urql/preact'; +import { useEffect, useState } from 'react'; import { graphql } from '../@generated/gql'; import type { GetUserQuery, LoginMutationVariables } from '../@generated/graphql'; -import { navigate, reload } from '../helpers/routing'; import { type RegularUserFragment } from '../@generated/graphql'; +import { navigate } from '../helpers/routing'; import { useAsync } from './useAsync'; const RegularUserFragment = graphql(` @@ -42,15 +42,16 @@ export const useLoginUser = () => { const [otp, setOtp] = useState(false); const [, loginMutation] = useMutation(LoginMutation); const [login] = useAsync(async (variables: LoginMutationVariables) => { - try { - await loginMutation(variables); + const result = await loginMutation(variables); + if (result.data) { navigate('/dashboard'); - } catch (error: any) { - if (error.message.toLowerCase().includes('otp')) { + } else if (result.error) { + if (result.error.message.toLowerCase().includes('otp')) { setOtp(true); + return; } - throw error; + throw result.error; } }); @@ -64,7 +65,7 @@ export const useLogoutUser = () => { const [, logoutMutation] = useMutation(LogoutMutation); const [logout] = useAsync(async () => { await logoutMutation({}); - reload(); + navigate('/'); }); return { logout }; diff --git a/packages/web/src/pages/dashboard/preferences/+Page.tsx b/packages/web/src/pages/dashboard/preferences/+Page.tsx index 4b85550..a014f7b 100644 --- a/packages/web/src/pages/dashboard/preferences/+Page.tsx +++ b/packages/web/src/pages/dashboard/preferences/+Page.tsx @@ -1,6 +1,6 @@ +import { useMutation, useQuery } from '@urql/preact'; import type { FC } from 'react'; import { Fragment } from 'react'; -import { useMutation, useQuery } from '@urql/preact'; import { graphql } from '../../../@generated/gql'; import { Breadcrumbs } from '../../../components/breadcrumbs'; import { Button } from '../../../components/button'; @@ -98,6 +98,32 @@ export const Page: FC = () => { {!user.data && } +
+
+ {user.data && ( + +
Change Password
+

+ Change your account password. This will not sign out other devices. +

+
+ )} + {!user.data && ( + + + + + )} +
+
+ {user.data && ( + + )} + {!user.data && } +
+
diff --git a/packages/web/src/pages/dashboard/preferences/change-password/+Page.tsx b/packages/web/src/pages/dashboard/preferences/change-password/+Page.tsx new file mode 100644 index 0000000..ff57038 --- /dev/null +++ b/packages/web/src/pages/dashboard/preferences/change-password/+Page.tsx @@ -0,0 +1,57 @@ +import { useMutation } from '@urql/preact'; +import type { FC } from 'react'; +import { graphql } from '../../../../@generated'; +import type { ChangePasswordMutationVariables } from '../../../../@generated/graphql'; +import { Breadcrumbs } from '../../../../components/breadcrumbs'; +import { Container } from '../../../../components/container'; +import { Title } from '../../../../components/title'; +import { useToasts } from '../../../../components/toast'; +import { PasswordForm } from '../../../../containers/password-form'; +import { navigate, prefetch } from '../../../../helpers/routing'; +import { useAsync } from '../../../../hooks/useAsync'; +import { useUser } from '../../../../hooks/useUser'; + +const ChangePassword = graphql(` + mutation ChangePassword($oldPassword: String!, $newPassword: String!) { + changePassword(currentPassword: $oldPassword, newPassword: $newPassword) + } +`); + +export const Page: FC = () => { + const createToast = useToasts(); + const [, changeInner] = useMutation(ChangePassword); + const [change] = useAsync(async (values: ChangePasswordMutationVariables) => { + prefetch('/dashboard/preferences'); + const result = await changeInner(values); + if (result.data) { + createToast({ text: 'Your password has been changed.' }); + navigate('/dashboard/preferences'); + } else if (result.error) { + if (result.error.message.toLowerCase().includes('unauthorized')) { + createToast({ text: 'Invalid password.' }); + return; + } else { + createToast({ text: 'An error occurred changing your password.' }); + } + + throw result.error; + } + }); + + useUser(); + + return ( + + Change Password +
+
+ + Dashboard / Preferences + +

Change Password

+ +
+
+
+ ); +}; diff --git a/packages/web/vite.config.ts b/packages/web/vite.config.ts index a915e43..b7eda8f 100644 --- a/packages/web/vite.config.ts +++ b/packages/web/vite.config.ts @@ -2,7 +2,6 @@ import { preact } from '@preact/preset-vite'; import { vavite } from 'vavite'; import ssr from 'vike/plugin'; import { defineConfig } from 'vite'; -import eslint from 'vite-plugin-eslint'; import codegen from 'vite-plugin-graphql-codegen'; export default defineConfig({ @@ -25,7 +24,6 @@ export default defineConfig({ }, plugins: [ codegen(), - eslint({ cache: true }), preact(), ssr({ disableAutoFullBuild: true }), vavite({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5997930..cd86315 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -353,10 +353,7 @@ importers: version: 0.4.161(vite@5.1.1) vite: specifier: ^5.1.1 - version: 5.1.1(@types/node@20.11.17) - vite-plugin-eslint: - specifier: ^1.8.1 - version: 1.8.1(eslint@8.56.0)(vite@5.1.1) + version: 5.1.1 vite-plugin-graphql-codegen: specifier: ^3.3.6 version: 3.3.6(@graphql-codegen/cli@5.0.2)(graphql@16.8.1)(vite@5.1.1) @@ -1389,7 +1386,7 @@ packages: '@fastify/reply-from': 9.7.0 fast-querystring: 1.1.2 fastify-plugin: 4.5.1 - ws: 8.16.0(utf-8-validate@6.0.3) + ws: 8.16.0 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -1783,7 +1780,7 @@ packages: graphql-ws: 5.14.3(graphql@16.8.1) isomorphic-ws: 5.0.0(ws@8.16.0) tslib: 2.6.2 - ws: 8.16.0(utf-8-validate@6.0.3) + ws: 8.16.0 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -1818,7 +1815,7 @@ packages: graphql: 16.8.1 isomorphic-ws: 5.0.0(ws@8.16.0) tslib: 2.6.2 - ws: 8.16.0(utf-8-validate@6.0.3) + ws: 8.16.0 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -2044,7 +2041,7 @@ packages: isomorphic-ws: 5.0.0(ws@8.16.0) tslib: 2.6.2 value-or-promise: 1.0.12 - ws: 8.16.0(utf-8-validate@6.0.3) + ws: 8.16.0 transitivePeerDependencies: - '@types/node' - bufferutil @@ -3099,7 +3096,7 @@ packages: magic-string: 0.30.5 node-html-parser: 6.1.12 resolve: 1.22.8 - vite: 5.1.1(@types/node@20.11.17) + vite: 5.1.1 transitivePeerDependencies: - preact - supports-color @@ -3133,7 +3130,7 @@ packages: '@prefresh/utils': 1.2.0 '@rollup/pluginutils': 4.2.1 preact: 10.19.4 - vite: 5.1.1(@types/node@20.11.17) + vite: 5.1.1 transitivePeerDependencies: - supports-color dev: true @@ -3848,13 +3845,6 @@ packages: resolution: {integrity: sha512-kRiitIeUg1mPV9yH4VUJ/1uk2XjyANfeL8/7rH1tsjvHeO9PJLBHJIYsFWmAvmGj5u8rj+1TZx7PZzW2qLw3Lw==} dev: true - /@types/eslint@8.56.2: - resolution: {integrity: sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==} - dependencies: - '@types/estree': 1.0.5 - '@types/json-schema': 7.0.15 - dev: true - /@types/estree-jsx@1.0.3: resolution: {integrity: sha512-pvQ+TKeRHeiUGRhvYwRrQ/ISnohKkSJR14fT2yqyZ4e9K5vqc7hrtY2Y1Dw0ZwAzQ6DQsxsaCUuSIIi8v0Cq6w==} dependencies: @@ -4251,7 +4241,7 @@ packages: vite: ^2.8.1 || 3 || 4 || 5 dependencies: '@types/node': 20.11.17 - vite: 5.1.1(@types/node@20.11.17) + vite: 5.1.1 dev: true /@vavite/expose-vite-dev-server@4.0.3(vite@5.1.1): @@ -4259,7 +4249,7 @@ packages: peerDependencies: vite: ^2.8.1 || 3 || 4 || 5 dependencies: - vite: 5.1.1(@types/node@20.11.17) + vite: 5.1.1 dev: true /@vavite/multibuild-cli@4.0.3(vite@5.1.1): @@ -4272,7 +4262,7 @@ packages: '@vavite/multibuild': 4.0.3(vite@5.1.1) cac: 6.7.14 picocolors: 1.0.0 - vite: 5.1.1(@types/node@20.11.17) + vite: 5.1.1 dev: true /@vavite/multibuild@4.0.3(vite@5.1.1): @@ -4283,7 +4273,7 @@ packages: '@types/node': 20.11.17 cac: 6.7.14 picocolors: 1.0.0 - vite: 5.1.1(@types/node@20.11.17) + vite: 5.1.1 dev: true /@vavite/node-loader@4.0.3(vite@5.1.1): @@ -4292,7 +4282,7 @@ packages: peerDependencies: vite: ^2.8.1 || 3 || 4 || 5 dependencies: - vite: 5.1.1(@types/node@20.11.17) + vite: 5.1.1 dev: true /@vavite/reloader@4.0.3(vite@5.1.1): @@ -4300,7 +4290,7 @@ packages: peerDependencies: vite: ^2.8.1 || 3 || 4 || 5 dependencies: - vite: 5.1.1(@types/node@20.11.17) + vite: 5.1.1 dev: true /@vitest/expect@1.2.2: @@ -7214,7 +7204,7 @@ packages: peerDependencies: ws: '*' dependencies: - ws: 8.16.0(utf-8-validate@6.0.3) + ws: 8.16.0 dev: true /istextorbinary@9.5.0: @@ -8356,6 +8346,7 @@ packages: /node-gyp-build@4.8.0: resolution: {integrity: sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==} hasBin: true + dev: false /node-html-parser@6.1.12: resolution: {integrity: sha512-/bT/Ncmv+fbMGX96XG9g05vFt43m/+SYKIs9oAemQVYyVcZmDAI2Xq/SbNcpOA35eF0Zk2av3Ksf+Xk8Vt8abA==} @@ -9509,14 +9500,6 @@ packages: glob: 7.2.3 dev: true - /rollup@2.79.1: - resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} - engines: {node: '>=10.0.0'} - hasBin: true - optionalDependencies: - fsevents: 2.3.3 - dev: true - /rollup@4.10.0: resolution: {integrity: sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -10808,6 +10791,7 @@ packages: requiresBuild: true dependencies: node-gyp-build: 4.8.0 + dev: false /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -10864,7 +10848,7 @@ packages: '@vavite/reloader': 4.0.3(vite@5.1.1) cac: 6.7.14 picocolors: 1.0.0 - vite: 5.1.1(@types/node@20.11.17) + vite: 5.1.1 dev: true /version-range@4.14.0: @@ -10910,7 +10894,7 @@ packages: fast-glob: 3.3.2 sirv: 2.0.4 source-map-support: 0.5.21 - vite: 5.1.1(@types/node@20.11.17) + vite: 5.1.1 dev: true /vite-node@1.2.2(@types/node@20.11.17): @@ -10934,19 +10918,6 @@ packages: - terser dev: true - /vite-plugin-eslint@1.8.1(eslint@8.56.0)(vite@5.1.1): - resolution: {integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==} - peerDependencies: - eslint: '>=7' - vite: '>=2' - dependencies: - '@rollup/pluginutils': 4.2.1 - '@types/eslint': 8.56.2 - eslint: 8.56.0 - rollup: 2.79.1 - vite: 5.1.1(@types/node@20.11.17) - dev: true - /vite-plugin-graphql-codegen@3.3.6(@graphql-codegen/cli@5.0.2)(graphql@16.8.1)(vite@5.1.1): resolution: {integrity: sha512-TXMaUpPCfqzSpujjzFjVeeCH9JOSBwFWxOJottZ+gouQtNhnNpgXcj4nZep3om5Wq0UlDwDYLqXWrAa8XaZW1w==} peerDependencies: @@ -10957,7 +10928,42 @@ packages: '@graphql-codegen/cli': 5.0.2(@parcel/watcher@2.4.0)(graphql@16.8.1)(typescript@5.3.3) '@graphql-codegen/plugin-helpers': 5.0.3(graphql@16.8.1) graphql: 16.8.1 - vite: 5.1.1(@types/node@20.11.17) + vite: 5.1.1 + dev: true + + /vite@5.1.1: + resolution: {integrity: sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.19.12 + postcss: 8.4.35 + rollup: 4.10.0 + optionalDependencies: + fsevents: 2.3.3 dev: true /vite@5.1.1(@types/node@20.11.17): @@ -11224,6 +11230,19 @@ packages: utf-8-validate: 6.0.3 dev: false + /ws@8.16.0: + resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + /ws@8.16.0(utf-8-validate@6.0.3): resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==} engines: {node: '>=10.0.0'} @@ -11237,6 +11256,7 @@ packages: optional: true dependencies: utf-8-validate: 6.0.3 + dev: false /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}