add role checkbox

This commit is contained in:
vchikalkin 2025-01-06 19:34:21 +03:00
parent c8282e8c04
commit 7a3ad37bf3
6 changed files with 228 additions and 23 deletions

View File

@ -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');
}

View File

@ -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 ?? ''}
/>
<ProfileField disabled id="phone" label="Телефон" value={user?.phone ?? ''} />
<ProfileField
disabled
id="status"
label="Статус"
value={user?.role === 'client' ? 'Клиент' : 'Мастер'}
<CheckboxWithText
checked={user.role !== 'client'}
description="Разрешить другим пользователям записываться к вам"
onChange={updateRole}
text="Быть мастером"
/>
{user?.role === 'client' && (
<form action={becomeMaster}>
<Button
className="w-full bg-gradient-to-r from-purple-500 to-purple-700 text-white hover:from-purple-600 hover:to-purple-800
dark:from-purple-700 dark:to-purple-900 dark:hover:from-purple-800 dark:hover:to-purple-950"
type="submit"
>
Стать мастером
</Button>
</form>
)}
</CardContent>
</Card>
</div>

View File

@ -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<CheckboxProps, 'checked'> & {
readonly description?: string;
readonly onChange?: (value: Enum_Customer_Role) => Promise<void> | 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 (
<div className="flex items-start space-x-2">
<Checkbox checked={checked} disabled={isPending} id="terms1" onCheckedChange={handleChange} />
<div className="grid gap-1.5 leading-none">
<label
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
htmlFor="terms1"
>
{text}
</label>
{description ? <p className="text-sm text-muted-foreground">{description}</p> : false}
</div>
</div>
);
}
function useDebouncedOnChangeCallback(
callback: ((value: Enum_Customer_Role) => Promise<void> | 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,
};
}

View File

@ -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:"

View File

@ -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<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
className={cn(
'peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
className,
)}
ref={ref}
{...props}
>
<CheckboxPrimitive.Indicator className={cn('flex items-center justify-center text-current')}>
<Check className="size-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
export { Checkbox };
export { type CheckboxProps } from '@radix-ui/react-checkbox';

107
pnpm-lock.yaml generated
View File

@ -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': {}