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