From 87a19981ec482713685a41ccc803969784cf9813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lin=20=28Light-it=29?= Date: Wed, 15 Nov 2023 18:11:57 -0300 Subject: [PATCH 1/4] feat: add query examples --- resources/js/api/config/config.ts | 131 ----------------------------- resources/js/api/config/domains.ts | 13 --- resources/js/api/config/index.ts | 2 - resources/js/api/users.ts | 67 +++++++++++++++ 4 files changed, 67 insertions(+), 146 deletions(-) delete mode 100644 resources/js/api/config/config.ts delete mode 100644 resources/js/api/config/domains.ts delete mode 100644 resources/js/api/config/index.ts create mode 100644 resources/js/api/users.ts diff --git a/resources/js/api/config/config.ts b/resources/js/api/config/config.ts deleted file mode 100644 index 37578ef857b..00000000000 --- a/resources/js/api/config/config.ts +++ /dev/null @@ -1,131 +0,0 @@ -import type { - InvalidateQueryFilters, - QueryClient, -} from "@tanstack/react-query"; - -import type { Domain, SubDomains } from "./domains"; - -const ALL = "all"; -const NO_DOMAIN = ""; -type QueryKeySignature = readonly [ - Domain | "", - string | number, - SubDomains | "", - string, - ...(TParams | undefined)[], -]; - -/** - * Invalidates queries by domain - * - * @param queryClient the react-query client https://tanstack.com/query/v4/docs/react/reference/useQueryClient - * @param queries: N arguments of either domain or [domain, id] tuples - */ -export function invalidateDomains( - queryClient: QueryClient, - ...queries: ( - | Domain - | [domain: Domain, id?: string | number | null | object] - )[] -) { - queries.forEach((arg) => { - const [domain, id] = typeof arg === "string" ? [arg] : arg; - void queryClient.invalidateQueries([domain, ALL] as InvalidateQueryFilters); - if (id !== undefined && id !== null && id !== ALL) { - void queryClient.invalidateQueries([ - domain, - id, - ] as InvalidateQueryFilters); - } - }); - - const subDomainsToInvalidate = queries.map((q) => - typeof q === "string" ? q : q[0], - ); - - // Here we search all the cache for subdomains and clear those out because we have no way to check - // if the data that was mutated will affect these endpoints or not - queryClient - .getQueryCache() - .getAll() - .forEach(({ queryKey }) => { - const subDomains = queryKey.at(2); - if ( - typeof subDomains === "string" && - subDomainsToInvalidate.some((d) => subDomains.split(",").includes(d)) - ) { - void queryClient.invalidateQueries(queryKey as InvalidateQueryFilters); - } - }); -} - -/** - * Invalidates queries by name - * - * @param queryClient the react-query client https://tanstack.com/query/v4/docs/react/reference/useQueryClient - * @param queryName the unique identifier for the query, it's the main reason why you'd use this function - * @param id the specific id we want to invalidate, if not given, we assume "all" - */ -export function invalidateQuery( - queryClient: QueryClient, - queryName: string, - id?: string | number | null, -) { - const paramsId = id ?? ALL; - - queryClient - .getQueryCache() - .getAll() - .forEach(({ queryKey }) => { - if (queryName === queryKey.at(3) && paramsId === queryKey.at(1)) { - void queryClient.invalidateQueries(queryKey as InvalidateQueryFilters); - } - }); -} - -/** - * Normalizes the possibly empty, possibly already an array, params into an array - * @param params the unknown value - * @returns params converted to array - */ -function getParamsList(params: T | T[]) { - if (params !== null && params !== undefined) { - return Array.isArray(params) ? params : [params]; - } - return []; -} - -/** - * This function generates the query keys for you in the specific order we need - * - * @param queryName the literal name of the function calling the endpoint, serves as a unique identifier for this specific query since there could be multiple queries for the same domain that give different payloads - * @param options.domain the main domain being queried - * @param options.id if specified, the id of the value inside the domain that is mutated. Primarily used to later invalidate queries for THIS id in THIS domain - * @param options.subDomains comma separated domains whose data is also included in the query - * @param options.params extra data, usually query params like filtering, sorting, pagination, etc - * - */ -export function generateQueryKey( - queryName: string, - { - domain, - id, - subDomains, - params, - }: { - domain?: Domain; - id?: string | number | null; - subDomains?: SubDomains; - params?: TParams | TParams[]; - } = {}, -) { - const queryKey: QueryKeySignature = [ - domain ?? NO_DOMAIN, - id ?? ALL, - subDomains ?? NO_DOMAIN, - queryName, - ...getParamsList(params), - ] as const; - - return queryKey; -} diff --git a/resources/js/api/config/domains.ts b/resources/js/api/config/domains.ts deleted file mode 100644 index 4c04aafb377..00000000000 --- a/resources/js/api/config/domains.ts +++ /dev/null @@ -1,13 +0,0 @@ -export const DOMAINS = { - project: "project", - user: "user", -} as const; -export type Domain = keyof typeof DOMAINS; - -export type SubDomains = T extends Domain - ? T - : T extends `${infer First},${infer Rest}` - ? First extends Domain - ? `${First},${SubDomains}` - : never - : never; diff --git a/resources/js/api/config/index.ts b/resources/js/api/config/index.ts deleted file mode 100644 index 597f4869eff..00000000000 --- a/resources/js/api/config/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./config"; -export * from "./domains"; diff --git a/resources/js/api/users.ts b/resources/js/api/users.ts new file mode 100644 index 00000000000..ff19adcf201 --- /dev/null +++ b/resources/js/api/users.ts @@ -0,0 +1,67 @@ +import type { QueryClient } from "@tanstack/react-query"; + +import type { ServiceResponse } from "./api.types"; +import { privateAPI } from "./axios"; + +const DOMAIN = "user"; +const ALL = "all"; + +export interface User { + id: number; + name: string; + email: string; +} + +export const getUsersQuery = () => ({ + queryKey: [DOMAIN, ALL, "getUsersQuery"], + queryFn: async () => { + const response = await privateAPI.get>("/users"); + + return response.data.data; + }, +}); + +export const getUserQuery = (userId: User["id"]) => ({ + queryKey: [DOMAIN, userId, "getUserQuery"], + queryFn: async () => { + const response = await privateAPI.get>( + `/users/${userId}`, + ); + + return response.data.data; + }, +}); + +interface CreateUserParams { + name: string; + email: string; + password: string; + passwordConfirmation: string; +} + +export const createUser = { + mutation: async (user: CreateUserParams) => { + const response = await privateAPI.post>( + "/users", + user, + ); + + return response.data.data; + }, + invalidates: (queryClient: QueryClient) => { + void queryClient.invalidateQueries({ queryKey: [DOMAIN, ALL] }); + }, +}; + +export const deleteUser = { + mutation: async (userId: User["id"]) => { + await privateAPI.delete(`/users/${userId}`); + }, + invalidates: ( + queryClient: QueryClient, + { userId }: { userId: User["id"] }, + ) => { + void queryClient.invalidateQueries({ queryKey: [DOMAIN, ALL] }); + void queryClient.invalidateQueries({ queryKey: [DOMAIN, userId] }); + }, +}; From 84e55e181018773fc60b6a4f0fbfd053539812ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lin=20=28Light-it=29?= Date: Thu, 16 Nov 2023 10:09:33 -0300 Subject: [PATCH 2/4] feat: modal example --- package-lock.json | 723 +++++++++++++++++- package.json | 2 + resources/js/modals/ExampleModal.tsx | 17 + resources/js/modals/SuccessModal.tsx | 75 -- resources/js/modals/index.ts | 2 +- resources/js/router/ModalRouter.tsx | 6 +- resources/js/router/routes.ts | 2 +- resources/js/screens/Home.tsx | 6 +- resources/js/ui/{ => common}/Button.tsx | 0 resources/js/ui/{ => common}/Icons.tsx | 0 resources/js/ui/common/Modal.tsx | 56 ++ .../js/ui/{ => common}/Toast/ToastMessage.tsx | 0 resources/js/ui/{ => common}/Toast/Toasts.tsx | 0 .../js/ui/{ => common}/Toast/errorToast.ts | 0 resources/js/ui/{ => common}/Toast/index.ts | 0 .../js/ui/{ => common}/Toast/toastIcons.tsx | 0 .../js/ui/{ => common}/Toast/toastStore.ts | 0 resources/js/ui/common/index.ts | 4 + resources/js/ui/index.ts | 3 +- tailwind.config.js | 2 +- 20 files changed, 806 insertions(+), 92 deletions(-) create mode 100644 resources/js/modals/ExampleModal.tsx delete mode 100644 resources/js/modals/SuccessModal.tsx rename resources/js/ui/{ => common}/Button.tsx (100%) rename resources/js/ui/{ => common}/Icons.tsx (100%) create mode 100644 resources/js/ui/common/Modal.tsx rename resources/js/ui/{ => common}/Toast/ToastMessage.tsx (100%) rename resources/js/ui/{ => common}/Toast/Toasts.tsx (100%) rename resources/js/ui/{ => common}/Toast/errorToast.ts (100%) rename resources/js/ui/{ => common}/Toast/index.ts (100%) rename resources/js/ui/{ => common}/Toast/toastIcons.tsx (100%) rename resources/js/ui/{ => common}/Toast/toastStore.ts (100%) create mode 100644 resources/js/ui/common/index.ts diff --git a/package-lock.json b/package-lock.json index e2a1aa79777..ef868f5c83e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "dependencies": { "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.0.18", + "@radix-ui/react-dialog": "^1.0.5", "@tanstack/react-query": "^5.8.2", "axios": "^1.4.0", "jwt-decode": "^4.0.0", @@ -48,6 +49,7 @@ "prettier": "^3.0.3", "prettier-plugin-tailwindcss": "^0.5.4", "tailwindcss": "^3.3.2", + "tailwindcss-animate": "^1.0.7", "typescript": "^5.2.2", "vite": "^4.3.9", "vite-plugin-checker": "^0.6.0" @@ -1442,6 +1444,329 @@ "node": ">= 8" } }, + "node_modules/@radix-ui/primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz", + "integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", + "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", + "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz", + "integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz", + "integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-escape-keydown": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", + "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz", + "integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", + "integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", + "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", + "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", + "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", + "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz", + "integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz", + "integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", + "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@react-oauth/google": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.11.1.tgz", @@ -1640,7 +1965,7 @@ "version": "18.2.15", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.15.tgz", "integrity": "sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==", - "dev": true, + "devOptional": true, "dependencies": { "@types/react": "*" } @@ -1990,6 +2315,17 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/aria-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz", + "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -3012,6 +3348,11 @@ "node": ">=6" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -4040,6 +4381,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/get-stdin": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", @@ -4371,6 +4720,14 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -5844,6 +6201,51 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", + "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", + "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.18.0.tgz", @@ -5874,6 +6276,28 @@ "react-dom": ">=16.8" } }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -6496,6 +6920,15 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "dev": true, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -6610,8 +7043,7 @@ "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/type-check": { "version": "0.4.0", @@ -6799,6 +7231,47 @@ "punycode": "^2.1.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", + "integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", @@ -8375,6 +8848,164 @@ "fastq": "^1.6.0" } }, + "@radix-ui/primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz", + "integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, + "@radix-ui/react-compose-refs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", + "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, + "@radix-ui/react-context": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", + "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, + "@radix-ui/react-dialog": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz", + "integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + } + }, + "@radix-ui/react-dismissable-layer": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz", + "integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-escape-keydown": "1.0.3" + } + }, + "@radix-ui/react-focus-guards": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", + "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, + "@radix-ui/react-focus-scope": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz", + "integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1" + } + }, + "@radix-ui/react-id": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", + "integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + } + }, + "@radix-ui/react-portal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", + "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + } + }, + "@radix-ui/react-presence": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", + "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1" + } + }, + "@radix-ui/react-primitive": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", + "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.2" + } + }, + "@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + } + }, + "@radix-ui/react-use-callback-ref": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", + "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, + "@radix-ui/react-use-controllable-state": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz", + "integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + } + }, + "@radix-ui/react-use-escape-keydown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz", + "integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + } + }, + "@radix-ui/react-use-layout-effect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", + "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, "@react-oauth/google": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.11.1.tgz", @@ -8529,7 +9160,7 @@ "version": "18.2.15", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.15.tgz", "integrity": "sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==", - "dev": true, + "devOptional": true, "requires": { "@types/react": "*" } @@ -8757,6 +9388,14 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "aria-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz", + "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==", + "requires": { + "tslib": "^2.0.0" + } + }, "aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -9492,6 +10131,11 @@ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true }, + "detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, "didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -10277,6 +10921,11 @@ "hasown": "^2.0.0" } }, + "get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==" + }, "get-stdin": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", @@ -10510,6 +11159,14 @@ "side-channel": "^1.0.4" } }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -11480,6 +12137,27 @@ "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", "dev": true }, + "react-remove-scroll": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", + "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "requires": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + } + }, + "react-remove-scroll-bar": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", + "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==", + "requires": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + } + }, "react-router": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.18.0.tgz", @@ -11497,6 +12175,16 @@ "react-router": "6.18.0" } }, + "react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "requires": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + } + }, "react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -11949,6 +12637,13 @@ "sucrase": "^3.32.0" } }, + "tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "dev": true, + "requires": {} + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -12045,8 +12740,7 @@ "tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "type-check": { "version": "0.4.0", @@ -12171,6 +12865,23 @@ "punycode": "^2.1.0" } }, + "use-callback-ref": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", + "integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==", + "requires": { + "tslib": "^2.0.0" + } + }, + "use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "requires": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + } + }, "use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", diff --git a/package.json b/package.json index 5658eab2bc2..788205b9bed 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "prettier": "^3.0.3", "prettier-plugin-tailwindcss": "^0.5.4", "tailwindcss": "^3.3.2", + "tailwindcss-animate": "^1.0.7", "typescript": "^5.2.2", "vite": "^4.3.9", "vite-plugin-checker": "^0.6.0" @@ -44,6 +45,7 @@ "dependencies": { "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.0.18", + "@radix-ui/react-dialog": "^1.0.5", "@tanstack/react-query": "^5.8.2", "axios": "^1.4.0", "jwt-decode": "^4.0.0", diff --git a/resources/js/modals/ExampleModal.tsx b/resources/js/modals/ExampleModal.tsx new file mode 100644 index 00000000000..2666d2a3272 --- /dev/null +++ b/resources/js/modals/ExampleModal.tsx @@ -0,0 +1,17 @@ +import { Modal } from "@/ui"; + +interface ExampleModalProps { + show: boolean; + onClose: () => void; +} + +export const ExampleModal = ({ show, onClose }: ExampleModalProps) => { + return ( + + ); +}; diff --git a/resources/js/modals/SuccessModal.tsx b/resources/js/modals/SuccessModal.tsx deleted file mode 100644 index ebed13e4fb5..00000000000 --- a/resources/js/modals/SuccessModal.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Fragment } from "react"; -import { Dialog, Transition } from "@headlessui/react"; -import { CheckIcon } from "@heroicons/react/24/outline"; - -interface SuccessModalProps { - show: boolean; - onClose: () => void; -} - -export const SuccessModal = ({ show, onClose }: SuccessModalProps) => { - return ( - - - -
- - -
-
- - -
-
-
-
- - Payment successful - -
-

- Some example successful text goes here -

-
-
-
-
- -
-
-
-
-
-
-
- ); -}; diff --git a/resources/js/modals/index.ts b/resources/js/modals/index.ts index ab1b2c5cecb..7464e31fa50 100644 --- a/resources/js/modals/index.ts +++ b/resources/js/modals/index.ts @@ -1 +1 @@ -export * from "./SuccessModal"; +export * from "./ExampleModal"; diff --git a/resources/js/router/ModalRouter.tsx b/resources/js/router/ModalRouter.tsx index 7f207e942de..a375bb576e2 100644 --- a/resources/js/router/ModalRouter.tsx +++ b/resources/js/router/ModalRouter.tsx @@ -2,7 +2,7 @@ import { useRef } from "react"; import { Route, Routes, useLocation, useNavigate } from "react-router-dom"; import { Transition, TransitionGroup } from "react-transition-group"; -import { SuccessModal } from "@/modals"; +import { ExampleModal } from "@/modals"; import { MODAL_ROUTES } from "./routes"; export const ModalRouter = ({ showModal }: { showModal: boolean }) => { @@ -26,8 +26,8 @@ export const ModalRouter = ({ showModal }: { showModal: boolean }) => {
} + path={`${MODAL_ROUTES.exampleModal}`} + element={} />
diff --git a/resources/js/router/routes.ts b/resources/js/router/routes.ts index 52bacd888ab..4145c3d5177 100644 --- a/resources/js/router/routes.ts +++ b/resources/js/router/routes.ts @@ -7,5 +7,5 @@ export const ROUTES = { } as const; export const MODAL_ROUTES = { - successModal: "/success-modal", + exampleModal: "/example-modal", } as const; diff --git a/resources/js/screens/Home.tsx b/resources/js/screens/Home.tsx index 1e26357ea7d..531fb3e6c3d 100644 --- a/resources/js/screens/Home.tsx +++ b/resources/js/screens/Home.tsx @@ -1,6 +1,6 @@ import { MODAL_ROUTES } from "@/router"; import { useNavigateModal } from "@/router/useNavigateModal"; -import { Button } from "@/ui/Button"; +import { Button } from "@/ui"; export function Home() { const navigateModal = useNavigateModal(); @@ -16,8 +16,8 @@ export function Home() {
  • item
  • item
  • - ); diff --git a/resources/js/ui/Button.tsx b/resources/js/ui/common/Button.tsx similarity index 100% rename from resources/js/ui/Button.tsx rename to resources/js/ui/common/Button.tsx diff --git a/resources/js/ui/Icons.tsx b/resources/js/ui/common/Icons.tsx similarity index 100% rename from resources/js/ui/Icons.tsx rename to resources/js/ui/common/Icons.tsx diff --git a/resources/js/ui/common/Modal.tsx b/resources/js/ui/common/Modal.tsx new file mode 100644 index 00000000000..ac21f7a48ab --- /dev/null +++ b/resources/js/ui/common/Modal.tsx @@ -0,0 +1,56 @@ +import type { ReactNode } from "react"; +import * as Dialog from "@radix-ui/react-dialog"; + +import { tw } from "@/utils"; +import { icons } from "./Icons"; + +interface ModalProps { + children?: ReactNode; + className?: string; + show: boolean; + title: string; + description?: string; + onClose: () => void; +} + +export const Modal = ({ + children, + className, + show, + title, + description, + onClose, +}: ModalProps) => ( + + + + +
    + + {title} + + + {description} + +
    + + {children} + + + + +
    +
    +
    +); diff --git a/resources/js/ui/Toast/ToastMessage.tsx b/resources/js/ui/common/Toast/ToastMessage.tsx similarity index 100% rename from resources/js/ui/Toast/ToastMessage.tsx rename to resources/js/ui/common/Toast/ToastMessage.tsx diff --git a/resources/js/ui/Toast/Toasts.tsx b/resources/js/ui/common/Toast/Toasts.tsx similarity index 100% rename from resources/js/ui/Toast/Toasts.tsx rename to resources/js/ui/common/Toast/Toasts.tsx diff --git a/resources/js/ui/Toast/errorToast.ts b/resources/js/ui/common/Toast/errorToast.ts similarity index 100% rename from resources/js/ui/Toast/errorToast.ts rename to resources/js/ui/common/Toast/errorToast.ts diff --git a/resources/js/ui/Toast/index.ts b/resources/js/ui/common/Toast/index.ts similarity index 100% rename from resources/js/ui/Toast/index.ts rename to resources/js/ui/common/Toast/index.ts diff --git a/resources/js/ui/Toast/toastIcons.tsx b/resources/js/ui/common/Toast/toastIcons.tsx similarity index 100% rename from resources/js/ui/Toast/toastIcons.tsx rename to resources/js/ui/common/Toast/toastIcons.tsx diff --git a/resources/js/ui/Toast/toastStore.ts b/resources/js/ui/common/Toast/toastStore.ts similarity index 100% rename from resources/js/ui/Toast/toastStore.ts rename to resources/js/ui/common/Toast/toastStore.ts diff --git a/resources/js/ui/common/index.ts b/resources/js/ui/common/index.ts new file mode 100644 index 00000000000..40d21671848 --- /dev/null +++ b/resources/js/ui/common/index.ts @@ -0,0 +1,4 @@ +export * from "./Button"; +export * from "./Icons"; +export * from "./Modal"; +export * from "./Toast"; diff --git a/resources/js/ui/index.ts b/resources/js/ui/index.ts index e3354e8a416..6b571608924 100644 --- a/resources/js/ui/index.ts +++ b/resources/js/ui/index.ts @@ -1,2 +1 @@ -export * from "./Icons"; -export * from "./Toast"; +export * from "./common"; diff --git a/tailwind.config.js b/tailwind.config.js index 0d2eeacb1be..4022865cddc 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,7 +1,7 @@ /** @type {import('tailwindcss').Config} */ module.exports = { content: ["./resources/**/*.{js,ts,jsx,tsx}"], - plugins: [require("@tailwindcss/typography")], + plugins: [require("@tailwindcss/typography"), require("tailwindcss-animate")], theme: { fontFamily: { sans: ["Inter", "sans-serif"], From e80f801f017e4c27272bc79530537d1151d500b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lin=20=28Light-it=29?= Date: Thu, 16 Nov 2023 14:25:00 -0300 Subject: [PATCH 3/4] feat: user CRUD example --- .env.example | 2 +- package-lock.json | 15 +++ package.json | 1 + resources/js/api/index.ts | 1 + resources/js/api/users.ts | 11 +- resources/js/modals/ExampleModal.tsx | 8 +- resources/js/modals/User/UserForm.tsx | 119 +++++++++++++++++++++ resources/js/modals/User/UserModal.tsx | 16 +++ resources/js/modals/User/index.tsx | 1 + resources/js/modals/index.ts | 1 + resources/js/router/ModalRouter.tsx | 6 +- resources/js/router/routes.ts | 1 + resources/js/screens/{ => Users}/Users.tsx | 83 +++++++++++++- resources/js/screens/Users/index.ts | 1 + resources/js/shared.types.ts | 7 +- resources/js/ui/common/Button.tsx | 2 +- resources/js/ui/common/Modal.tsx | 2 +- resources/js/ui/form/Input.tsx | 68 ++++++++++++ resources/js/ui/form/Label.tsx | 31 ++++++ resources/js/ui/form/Message.tsx | 21 ++++ resources/js/ui/form/index.ts | 3 + resources/js/ui/index.ts | 1 + 22 files changed, 379 insertions(+), 22 deletions(-) create mode 100644 resources/js/modals/User/UserForm.tsx create mode 100644 resources/js/modals/User/UserModal.tsx create mode 100644 resources/js/modals/User/index.tsx rename resources/js/screens/{ => Users}/Users.tsx (72%) create mode 100644 resources/js/screens/Users/index.ts create mode 100644 resources/js/ui/form/Input.tsx create mode 100644 resources/js/ui/form/Label.tsx create mode 100644 resources/js/ui/form/Message.tsx create mode 100644 resources/js/ui/form/index.ts diff --git a/.env.example b/.env.example index e84397609f0..a69880ac8c5 100644 --- a/.env.example +++ b/.env.example @@ -54,7 +54,7 @@ PUSHER_APP_CLUSTER=mt1 VITE_APP_NAME="${APP_NAME}" VITE_APP_ENV="${APP_ENV}" VITE_APP_URL="${APP_URL}" -VITE_API_URL="${APP_URL}" +VITE_API_URL="${APP_URL}/api" VITE_GOOGLE_AUTH_SSO_CLIENT_ID="" VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}" diff --git a/package-lock.json b/package-lock.json index ef868f5c83e..0e58ad44822 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "dependencies": { "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.0.18", + "@hookform/resolvers": "^3.3.2", "@radix-ui/react-dialog": "^1.0.5", "@tanstack/react-query": "^5.8.2", "axios": "^1.4.0", @@ -1305,6 +1306,14 @@ "react": ">= 16" } }, + "node_modules/@hookform/resolvers": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.2.tgz", + "integrity": "sha512-Tw+GGPnBp+5DOsSg4ek3LCPgkBOuOgS5DsDV7qsWNH9LZc433kgsWICjlsh2J9p04H2K66hsXPPb9qn9ILdUtA==", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -8746,6 +8755,12 @@ "integrity": "sha512-7TyMjRrZZMBPa+/5Y8lN0iyvUU/01PeMGX2+RE7cQWpEUIcb4QotzUObFkJDejj/HUH4qjP/eQ0gzzKs2f+6Yw==", "requires": {} }, + "@hookform/resolvers": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.2.tgz", + "integrity": "sha512-Tw+GGPnBp+5DOsSg4ek3LCPgkBOuOgS5DsDV7qsWNH9LZc433kgsWICjlsh2J9p04H2K66hsXPPb9qn9ILdUtA==", + "requires": {} + }, "@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", diff --git a/package.json b/package.json index 788205b9bed..7f50f5b1238 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "dependencies": { "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.0.18", + "@hookform/resolvers": "^3.3.2", "@radix-ui/react-dialog": "^1.0.5", "@tanstack/react-query": "^5.8.2", "axios": "^1.4.0", diff --git a/resources/js/api/index.ts b/resources/js/api/index.ts index 947dc273290..6e9437061a3 100644 --- a/resources/js/api/index.ts +++ b/resources/js/api/index.ts @@ -1 +1,2 @@ export * from "./authentication"; +export * from "./users"; diff --git a/resources/js/api/users.ts b/resources/js/api/users.ts index ff19adcf201..bb8a270e515 100644 --- a/resources/js/api/users.ts +++ b/resources/js/api/users.ts @@ -40,11 +40,12 @@ interface CreateUserParams { } export const createUser = { - mutation: async (user: CreateUserParams) => { - const response = await privateAPI.post>( - "/users", - user, - ); + mutation: async (params: CreateUserParams) => { + const { passwordConfirmation, ...rest } = params; + const response = await privateAPI.post>("/users", { + ...rest, + password_confirmation: passwordConfirmation, + }); return response.data.data; }, diff --git a/resources/js/modals/ExampleModal.tsx b/resources/js/modals/ExampleModal.tsx index 2666d2a3272..f941565f57d 100644 --- a/resources/js/modals/ExampleModal.tsx +++ b/resources/js/modals/ExampleModal.tsx @@ -1,11 +1,7 @@ +import type { ModalProps } from "@/shared.types"; import { Modal } from "@/ui"; -interface ExampleModalProps { - show: boolean; - onClose: () => void; -} - -export const ExampleModal = ({ show, onClose }: ExampleModalProps) => { +export const ExampleModal = ({ show, onClose }: ModalProps) => { return ( data.password === data.passwordConfirmation, { + message: "Passwords must match", + path: ["passwordConfirmation"], + }); +type UserFormValues = z.infer; + +export const UserForm = ({ onClose }: { onClose: () => void }) => { + const { + formState: { errors, isDirty }, + handleSubmit, + register, + setError, + } = useForm({ + resolver: zodResolver(userSchema), + }); + + const { pushToast } = useToastStore(); + const queryClient = useQueryClient(); + + const { mutate: createUserMutation, isPending: isPendingCreateUserMutation } = + useMutation({ + mutationFn: createUser.mutation, + onSuccess: (data) => { + createUser.invalidates(queryClient); + void pushToast({ + type: "success", + title: "Success", + message: `User "${data.name}" successfully created!`, + }); + onClose(); + }, + onError: (err) => { + errorToast(err); + handleAxiosFieldErrors(err, setError); + }, + }); + + return ( +
    { + void handleSubmit((value) => createUserMutation(value))(e); + }} + className="flex flex-col gap-7" + > +
    + + + + +
    + +
    + + +
    +
    + ); +}; diff --git a/resources/js/modals/User/UserModal.tsx b/resources/js/modals/User/UserModal.tsx new file mode 100644 index 00000000000..65b7d887a94 --- /dev/null +++ b/resources/js/modals/User/UserModal.tsx @@ -0,0 +1,16 @@ +import type { ModalProps } from "@/shared.types"; +import { Modal } from "@/ui"; +import { UserForm } from "./UserForm"; + +export const UserModal = ({ show, onClose }: ModalProps) => { + return ( + + + + ); +}; diff --git a/resources/js/modals/User/index.tsx b/resources/js/modals/User/index.tsx new file mode 100644 index 00000000000..9496ba438c7 --- /dev/null +++ b/resources/js/modals/User/index.tsx @@ -0,0 +1 @@ +export * from "./UserModal"; diff --git a/resources/js/modals/index.ts b/resources/js/modals/index.ts index 7464e31fa50..a3200300692 100644 --- a/resources/js/modals/index.ts +++ b/resources/js/modals/index.ts @@ -1 +1,2 @@ export * from "./ExampleModal"; +export * from "./User"; diff --git a/resources/js/router/ModalRouter.tsx b/resources/js/router/ModalRouter.tsx index a375bb576e2..dfb311f9c97 100644 --- a/resources/js/router/ModalRouter.tsx +++ b/resources/js/router/ModalRouter.tsx @@ -2,7 +2,7 @@ import { useRef } from "react"; import { Route, Routes, useLocation, useNavigate } from "react-router-dom"; import { Transition, TransitionGroup } from "react-transition-group"; -import { ExampleModal } from "@/modals"; +import { ExampleModal, UserModal } from "@/modals"; import { MODAL_ROUTES } from "./routes"; export const ModalRouter = ({ showModal }: { showModal: boolean }) => { @@ -29,6 +29,10 @@ export const ModalRouter = ({ showModal }: { showModal: boolean }) => { path={`${MODAL_ROUTES.exampleModal}`} element={} /> + } + /> ); diff --git a/resources/js/router/routes.ts b/resources/js/router/routes.ts index 4145c3d5177..462fce0ffcc 100644 --- a/resources/js/router/routes.ts +++ b/resources/js/router/routes.ts @@ -8,4 +8,5 @@ export const ROUTES = { export const MODAL_ROUTES = { exampleModal: "/example-modal", + userForm: "/user-form", } as const; diff --git a/resources/js/screens/Users.tsx b/resources/js/screens/Users/Users.tsx similarity index 72% rename from resources/js/screens/Users.tsx rename to resources/js/screens/Users/Users.tsx index 31c604d24ad..d84574423cf 100644 --- a/resources/js/screens/Users.tsx +++ b/resources/js/screens/Users/Users.tsx @@ -1,3 +1,9 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + +import { deleteUser, getUsersQuery } from "@/api"; +import { MODAL_ROUTES } from "@/router"; +import { useNavigateModal } from "@/router/useNavigateModal"; +import { Button, errorToast, icons, useToastStore } from "@/ui"; import { tw } from "@/utils"; const statuses = { @@ -109,21 +115,65 @@ const activityItems = [ date: "2 weeks ago", dateTime: "2023-01-09T08:45", }, -]; +] as const; export const Users = () => { + const { pushToast } = useToastStore(); + const queryClient = useQueryClient(); + + const { data: users, isLoading: isLoadingUsers } = useQuery({ + ...getUsersQuery(), + select: (users) => + users.map((user, idx) => { + const selectedItem = + activityItems[idx % activityItems.length] ?? activityItems[0]; + + return { + ...selectedItem, + + user: { + imageUrl: selectedItem.user.imageUrl, + name: user.name, + id: user.id, + }, + }; + }), + }); + + const { mutate: deleteUserMutation } = useMutation({ + mutationFn: deleteUser.mutation, + onSuccess: (_, requestedId) => { + deleteUser.invalidates(queryClient, { userId: requestedId }); + void pushToast({ + type: "success", + title: "Success", + message: "User successfully deleted!", + }); + }, + onError: errorToast, + }); + + const navigateModal = useNavigateModal(); + return ( -
    -

    +
    +

    Latest activity +

    - +
    + @@ -157,10 +207,25 @@ export const Users = () => { > Deployed at + - {activityItems.map((item) => ( + {isLoadingUsers && ( + + + + )} + {users?.map((item) => ( + ))} diff --git a/resources/js/screens/Users/index.ts b/resources/js/screens/Users/index.ts new file mode 100644 index 00000000000..05078a4b076 --- /dev/null +++ b/resources/js/screens/Users/index.ts @@ -0,0 +1 @@ +export * from "./Users"; diff --git a/resources/js/shared.types.ts b/resources/js/shared.types.ts index 2b3a24b80e4..2e2c518f4c5 100644 --- a/resources/js/shared.types.ts +++ b/resources/js/shared.types.ts @@ -1,5 +1,8 @@ import type { ComponentPropsWithoutRef } from "react"; -export type UserState = "loggedIn" | "loggedOut"; - export type SVGProps = ComponentPropsWithoutRef<"svg">; + +export interface ModalProps { + show: boolean; + onClose: () => void; +} diff --git a/resources/js/ui/common/Button.tsx b/resources/js/ui/common/Button.tsx index ce15761ffa7..5bd205fc181 100644 --- a/resources/js/ui/common/Button.tsx +++ b/resources/js/ui/common/Button.tsx @@ -47,7 +47,7 @@ export const Button = forwardRef( variant === BUTTON_VARIANT.SECONDARY && "bg-blue-100 text-blue-800 hover:bg-blue-200", variant === BUTTON_VARIANT.OUTLINE && - "border-gray-300 text-gray-700 hover:border-gray-400 hover:text-gray-800", + "border-gray-300 text-gray-300 hover:border-gray-400 hover:text-gray-800", variant === BUTTON_VARIANT.TERTIARY && "font-normal text-gray-500 hover:text-gray-600", ], diff --git a/resources/js/ui/common/Modal.tsx b/resources/js/ui/common/Modal.tsx index ac21f7a48ab..1c9f401f014 100644 --- a/resources/js/ui/common/Modal.tsx +++ b/resources/js/ui/common/Modal.tsx @@ -23,7 +23,7 @@ export const Modal = ({ }: ModalProps) => ( - + { + compact?: boolean; + containerClassName?: string; + error?: string | boolean; + iconClassName?: string; + id: string; + label?: ReactNode; + left?: ReactNode; + message?: string; +} + +export const Input = forwardRef( + ( + { + className, + compact, + containerClassName, + error, + id, + label, + left, + message, + ...rest + }: InputProps, + ref: ForwardedRef, + ) => ( +
    + {!!label &&
    + ), +); diff --git a/resources/js/ui/form/Label.tsx b/resources/js/ui/form/Label.tsx new file mode 100644 index 00000000000..6fece441321 --- /dev/null +++ b/resources/js/ui/form/Label.tsx @@ -0,0 +1,31 @@ +import type { ComponentPropsWithoutRef, FC, ReactNode } from "react"; + +import { tw } from "@/utils"; + +export interface LabelProps extends ComponentPropsWithoutRef<"label"> { + label: ReactNode; + containerClassName?: string; +} + +export const Label: FC = ({ + label, + containerClassName, + className, + ...props +}) => ( +
    + {typeof label !== "string" ? ( + label + ) : ( + + )} +
    +); diff --git a/resources/js/ui/form/Message.tsx b/resources/js/ui/form/Message.tsx new file mode 100644 index 00000000000..4d53f69ffff --- /dev/null +++ b/resources/js/ui/form/Message.tsx @@ -0,0 +1,21 @@ +import type { ComponentPropsWithoutRef } from "react"; + +import { tw } from "@/utils"; + +export interface MessageProps extends ComponentPropsWithoutRef<"p"> { + message?: string; + error?: string | boolean; +} + +export const Message = ({ message, error, className }: MessageProps) => ( +

    + {error === true ? "\u200b" : !error ? message ?? "\u200b" : error} +

    +); diff --git a/resources/js/ui/form/index.ts b/resources/js/ui/form/index.ts new file mode 100644 index 00000000000..7e0a4883f88 --- /dev/null +++ b/resources/js/ui/form/index.ts @@ -0,0 +1,3 @@ +export * from "./Input"; +export * from "./Label"; +export * from "./Message"; diff --git a/resources/js/ui/index.ts b/resources/js/ui/index.ts index 6b571608924..ee9558a04da 100644 --- a/resources/js/ui/index.ts +++ b/resources/js/ui/index.ts @@ -1 +1,2 @@ export * from "./common"; +export * from "./form"; From 9eae5acb1caac8f630751f09614d58eae6f6b572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lin=20=28Light-it=29?= Date: Thu, 16 Nov 2023 15:57:20 -0300 Subject: [PATCH 4/4] feat: use radix --- cspell.json | 1 - package-lock.json | 34 -------- package.json | 1 - .../js/layout/Navbar/ResponsiveSidebar.tsx | 75 +++------------- resources/js/ui/common/Toast/ToastMessage.tsx | 87 ++++++++----------- resources/js/ui/common/Toast/toastStore.ts | 2 +- 6 files changed, 53 insertions(+), 147 deletions(-) diff --git a/cspell.json b/cspell.json index f318ad86ea6..434512f76e1 100644 --- a/cspell.json +++ b/cspell.json @@ -27,7 +27,6 @@ "fieldsets", "filesize", "Flugg", - "headlessui", "heroicons", "ianvs", "larastan", diff --git a/package-lock.json b/package-lock.json index 0e58ad44822..08e899bdfa3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,6 @@ "packages": { "": { "dependencies": { - "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.0.18", "@hookform/resolvers": "^3.3.2", "@radix-ui/react-dialog": "^1.0.5", @@ -1283,21 +1282,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@headlessui/react": { - "version": "1.7.17", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.17.tgz", - "integrity": "sha512-4am+tzvkqDSSgiwrsEpGWqgGo9dz8qU5M3znCkC4PgkpY4HcCZzEDEvozltGGGHIKl9jbXbZPSH5TWn4sWJdow==", - "dependencies": { - "client-only": "^0.0.1" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": "^16 || ^17 || ^18", - "react-dom": "^16 || ^17 || ^18" - } - }, "node_modules/@heroicons/react": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.0.18.tgz", @@ -2767,11 +2751,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -8741,14 +8720,6 @@ "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true }, - "@headlessui/react": { - "version": "1.7.17", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.17.tgz", - "integrity": "sha512-4am+tzvkqDSSgiwrsEpGWqgGo9dz8qU5M3znCkC4PgkpY4HcCZzEDEvozltGGGHIKl9jbXbZPSH5TWn4sWJdow==", - "requires": { - "client-only": "^0.0.1" - } - }, "@heroicons/react": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.0.18.tgz", @@ -9705,11 +9676,6 @@ "resolve-from": "^5.0.0" } }, - "client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" - }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", diff --git a/package.json b/package.json index 7f50f5b1238..c04c3b0abaf 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "vite-plugin-checker": "^0.6.0" }, "dependencies": { - "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.0.18", "@hookform/resolvers": "^3.3.2", "@radix-ui/react-dialog": "^1.0.5", diff --git a/resources/js/layout/Navbar/ResponsiveSidebar.tsx b/resources/js/layout/Navbar/ResponsiveSidebar.tsx index 25f3cd7d292..0eeb309da2b 100644 --- a/resources/js/layout/Navbar/ResponsiveSidebar.tsx +++ b/resources/js/layout/Navbar/ResponsiveSidebar.tsx @@ -1,5 +1,5 @@ -import { Fragment, useState } from "react"; -import { Dialog, Transition } from "@headlessui/react"; +import { useState } from "react"; +import * as Dialog from "@radix-ui/react-dialog"; import { icons } from "@/ui"; import { Sidebar } from "./Sidebar"; @@ -8,65 +8,18 @@ export const ResponsiveSidebar = () => { const [sidebarOpen, setSidebarOpen] = useState(false); return ( <> - - - -
    - - -
    - - - -
    - -
    -
    - - -
    -
    -
    -
    -
    + + + + + setSidebarOpen(false)} /> + + + Close + + + +
    -
    +
    + {toast.title && ( +

    {toast.title}

    + )} + {toast.message && ( +

    {toast.message}

    + )} +
    +
    +
    - + ); }; diff --git a/resources/js/ui/common/Toast/toastStore.ts b/resources/js/ui/common/Toast/toastStore.ts index 98814239492..63c2356f910 100644 --- a/resources/js/ui/common/Toast/toastStore.ts +++ b/resources/js/ui/common/Toast/toastStore.ts @@ -63,7 +63,7 @@ export const useToastStore = create((set, get) => ({ return state; }); - await asyncTimeout(600); + await asyncTimeout(500); set((state) => ({ toasts: state.toasts.filter((toast) => toast.id !== id),
    + Action +
    +
    + +
    +
    @@ -211,6 +276,14 @@ export const Users = () => {
    + +