From 7a3ad37bf3a094381b2ec2b53c4f4ee3d1bacc1d Mon Sep 17 00:00:00 2001 From: vchikalkin Date: Mon, 6 Jan 2025 19:34:21 +0300 Subject: [PATCH] add role checkbox --- apps/web/actions/profile.ts | 26 ++++- apps/web/app/(main)/profile/page.tsx | 25 ++-- .../components/profile/checkbox-with-text.tsx | 63 +++++++++++ packages/ui/package.json | 1 + packages/ui/src/components/ui/checkbox.tsx | 29 +++++ pnpm-lock.yaml | 107 ++++++++++++++++++ 6 files changed, 228 insertions(+), 23 deletions(-) create mode 100644 apps/web/components/profile/checkbox-with-text.tsx create mode 100644 packages/ui/src/components/ui/checkbox.tsx diff --git a/apps/web/actions/profile.ts b/apps/web/actions/profile.ts index 4d9cd4f..93bd5f4 100644 --- a/apps/web/actions/profile.ts +++ b/apps/web/actions/profile.ts @@ -1,14 +1,10 @@ 'use server'; import { authOptions } from '@/config/auth'; import { getCustomer, updateCustomerProfile } from '@repo/graphql/api'; -import { type CustomerInput } from '@repo/graphql/types'; +import { type CustomerInput, type Enum_Customer_Role } from '@repo/graphql/types'; import { getServerSession } from 'next-auth/next'; import { revalidatePath } from 'next/cache'; -export async function becomeMaster() { - revalidatePath('/profile'); -} - export async function updateProfile(input: CustomerInput) { const session = await getServerSession(authOptions); @@ -26,3 +22,23 @@ export async function updateProfile(input: CustomerInput) { revalidatePath('/profile'); } + +export async function updateRole(role: Enum_Customer_Role) { + const session = await getServerSession(authOptions); + + if (session) { + const { user } = session; + const getCustomerResponse = await getCustomer({ telegramId: user?.telegramId }); + const customer = getCustomerResponse.data.customers.at(0); + if (customer) { + await updateCustomerProfile({ + data: { + role, + }, + documentId: customer.documentId, + }); + } + } + + revalidatePath('/profile'); +} diff --git a/apps/web/app/(main)/profile/page.tsx b/apps/web/app/(main)/profile/page.tsx index 396368e..cf1567c 100644 --- a/apps/web/app/(main)/profile/page.tsx +++ b/apps/web/app/(main)/profile/page.tsx @@ -1,9 +1,9 @@ -import { becomeMaster, updateProfile } from '@/actions/profile'; +import { updateProfile, updateRole } from '@/actions/profile'; +import { CheckboxWithText } from '@/components/profile/checkbox-with-text'; import { ProfileField } from '@/components/profile/profile-field'; import { authOptions } from '@/config/auth'; import { getCustomer } from '@repo/graphql/api'; import { Avatar, AvatarFallback, AvatarImage } from '@repo/ui/components/ui/avatar'; -import { Button } from '@repo/ui/components/ui/button'; import { Card, CardContent, CardHeader } from '@repo/ui/components/ui/card'; import { getServerSession } from 'next-auth/next'; @@ -34,23 +34,12 @@ export default async function ProfilePage() { value={user?.name ?? ''} /> - - {user?.role === 'client' && ( -
- -
- )} diff --git a/apps/web/components/profile/checkbox-with-text.tsx b/apps/web/components/profile/checkbox-with-text.tsx new file mode 100644 index 0000000..91ac194 --- /dev/null +++ b/apps/web/components/profile/checkbox-with-text.tsx @@ -0,0 +1,63 @@ +/* eslint-disable canonical/id-match */ +/* eslint-disable promise/prefer-await-to-then */ +'use client'; +import { Enum_Customer_Role } from '@repo/graphql/types'; +import { Checkbox, type CheckboxProps } from '@repo/ui/components/ui/checkbox'; +import { useState } from 'react'; +import { useDebouncedCallback } from 'use-debounce'; + +type Props = Pick & { + readonly description?: string; + readonly onChange?: (value: Enum_Customer_Role) => Promise | void; + readonly text: string; +}; + +export function CheckboxWithText({ checked: initialValue, description, onChange, text }: Props) { + const [checked, setChecked] = useState(initialValue); + const { debouncedCallback, isPending } = useDebouncedOnChangeCallback(onChange); + + const handleChange = () => { + const newValue = !checked; + setChecked(newValue); + debouncedCallback(newValue); + }; + + return ( +
+ +
+ + {description ?

{description}

: false} +
+
+ ); +} + +function useDebouncedOnChangeCallback( + callback: ((value: Enum_Customer_Role) => Promise | void) | undefined, +) { + const [isPending, setIsPending] = useState(false); + + const debouncedCallback = useDebouncedCallback((checked: boolean) => { + if (!callback) return; + + setIsPending(true); + const result = callback(checked ? Enum_Customer_Role.Master : Enum_Customer_Role.Client); + + if (result instanceof Promise) { + result.finally(() => setIsPending(false)); + } else { + setIsPending(false); + } + }, 300); + + return { + debouncedCallback, + isPending, + }; +} diff --git a/packages/ui/package.json b/packages/ui/package.json index 7b869d5..2d2feb2 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -45,6 +45,7 @@ }, "dependencies": { "@radix-ui/react-avatar": "catalog:", + "@radix-ui/react-checkbox": "^1.1.3", "@radix-ui/react-label": "^2.1.1", "react": "catalog:", "react-dom": "catalog:" diff --git a/packages/ui/src/components/ui/checkbox.tsx b/packages/ui/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..1aa3ef5 --- /dev/null +++ b/packages/ui/src/components/ui/checkbox.tsx @@ -0,0 +1,29 @@ +'use client'; + +import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; +import { cn } from '@repo/ui/lib/utils'; +import { Check } from 'lucide-react'; +import * as React from 'react'; + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox }; + +export { type CheckboxProps } from '@radix-ui/react-checkbox'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 53005d3..c3cdf04 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -378,6 +378,9 @@ importers: '@radix-ui/react-avatar': specifier: 'catalog:' version: 1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-checkbox': + specifier: ^1.1.3 + version: 1.1.3(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-label': specifier: ^2.1.1 version: 2.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -2022,6 +2025,9 @@ packages: engines: {node: '>=18'} hasBin: true + '@radix-ui/primitive@1.1.1': + resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} + '@radix-ui/react-avatar@1.1.2': resolution: {integrity: sha512-GaC7bXQZ5VgZvVvsJ5mu/AEbjYLnhhkoidOboC50Z6FFlLA03wG2ianUoH+zgDQ31/9gCF59bE4+2bBgTyMiig==} peerDependencies: @@ -2035,6 +2041,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-checkbox@1.1.3': + resolution: {integrity: sha512-HD7/ocp8f1B3e6OHygH0n7ZKjONkhciy1Nh0yuBgObqThc3oyx+vuMfFHKAknXRHHWVE9XvXStxJFyjUmB8PIw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-compose-refs@1.1.1': resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} peerDependencies: @@ -2066,6 +2085,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-presence@1.1.2': + resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@2.0.1': resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==} peerDependencies: @@ -2097,6 +2129,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-controllable-state@1.1.0': + resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-layout-effect@1.1.0': resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} peerDependencies: @@ -2106,6 +2147,24 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-previous@1.1.0': + resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.0': + resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@repeaterjs/repeater@3.0.4': resolution: {integrity: sha512-AW8PKd6iX3vAZ0vA43nOUOnbq/X5ihgU+mSXXqunMkeQADGiqw/PY0JNeYtD5sr0PAy51YPgAPbDoeapv9r8WA==} @@ -7977,6 +8036,8 @@ snapshots: dependencies: playwright: 1.49.1 + '@radix-ui/primitive@1.1.1': {} + '@radix-ui/react-avatar@1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0) @@ -7989,6 +8050,22 @@ snapshots: '@types/react': 19.0.1 '@types/react-dom': 19.0.1 + '@radix-ui/react-checkbox@1.1.3(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-use-previous': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@19.0.1)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.1 + '@types/react-dom': 19.0.1 + '@radix-ui/react-compose-refs@1.1.1(@types/react@19.0.1)(react@19.0.0)': dependencies: react: 19.0.0 @@ -8010,6 +8087,16 @@ snapshots: '@types/react': 19.0.1 '@types/react-dom': 19.0.1 + '@radix-ui/react-presence@1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.1 + '@types/react-dom': 19.0.1 + '@radix-ui/react-primitive@2.0.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/react-slot': 1.1.1(@types/react@19.0.1)(react@19.0.0) @@ -8032,12 +8119,32 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.0.1)(react@19.0.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.1 + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.0.1)(react@19.0.0)': dependencies: react: 19.0.0 optionalDependencies: '@types/react': 19.0.1 + '@radix-ui/react-use-previous@1.1.0(@types/react@19.0.1)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.1 + + '@radix-ui/react-use-size@1.1.0(@types/react@19.0.1)(react@19.0.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.1 + '@repeaterjs/repeater@3.0.4': {} '@repeaterjs/repeater@3.0.6': {}