* apps/bot: add feature add contact * apps/bot: check role 'master' before add contact * apps/bot: rename createCustomer -> createUser * remove ';' * app/bot: add contact define name & phone * apps/bot: check user already exists w/o telegramId (invited) * Чтобы добавить контакт, сначала поделитесь своим номером телефона. * apps/bot: create or update functions * apps/bot: remove api.ts -> move getCustomer to packages/graphql/api * packages/graphql: add api/customer tests * tests for createOrUpdateClient * fix(apps/web): user is undefined * fix(apps/web): actions getCustomer * feat(apps/web): update user photo on app launch * rename page 'masters' -> 'contacts' * feat(apps/web): add basic /contacts page * fix app layout * refactor customer queries * add action getProfile * get customer contacts * use zustand for contacts * add loading spinner * rename filteredContacts -> contacts * replace zustand with @tanstack/react-query * profile: use react-query * refactor updateRole function * move updateRole closer to profile-card * beautify actions * add page 'profile/[telegramId]' * profile: add button "message to telegram" * profile: add call feature * app/bot: normalize phone before register * do not open keyboard on page load * contacts: loading spinner * telegram login: customer.active=true * update name on telegram first login
62 lines
1.8 KiB
TypeScript
62 lines
1.8 KiB
TypeScript
/* eslint-disable promise/prefer-await-to-then */
|
|
'use client';
|
|
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: boolean) => 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: boolean) => Promise<void> | void) | undefined,
|
|
) {
|
|
const [isPending, setIsPending] = useState(false);
|
|
|
|
const debouncedCallback = useDebouncedCallback((checked: boolean) => {
|
|
if (!callback) return;
|
|
|
|
setIsPending(true);
|
|
const result = callback(checked);
|
|
|
|
if (result instanceof Promise) {
|
|
result.finally(() => setIsPending(false));
|
|
} else {
|
|
setIsPending(false);
|
|
}
|
|
}, 300);
|
|
|
|
return {
|
|
debouncedCallback,
|
|
isPending,
|
|
};
|
|
}
|