feat: invite code

This commit is contained in:
2025-03-05 18:38:56 +03:00
parent be666c5158
commit 6cc61dbf08
7 changed files with 140 additions and 47 deletions

View File

@@ -200,6 +200,7 @@ export type { FinishPauseByShiftIdResponse } from './models/FinishPauseByShiftId
export type { FinishPauseByUserIdResponse } from './models/FinishPauseByUserIdResponse';
export type { FinishShiftByIdResponse } from './models/FinishShiftByIdResponse';
export type { FinishShiftResponse } from './models/FinishShiftResponse';
export type { GenerateInviteCodeResponse } from './models/GenerateInviteCodeResponse';
export type { GetAllBarcodeTemplateAttributesResponse } from './models/GetAllBarcodeTemplateAttributesResponse';
export type { GetAllBarcodeTemplateSizesResponse } from './models/GetAllBarcodeTemplateSizesResponse';
export type { GetAllBarcodeTemplatesResponse } from './models/GetAllBarcodeTemplatesResponse';

View File

@@ -8,5 +8,6 @@ export type AuthLoginRequest = {
hash: string;
id: number;
photo_url?: (string | null);
invite_code?: (string | null);
};

View File

@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type GenerateInviteCodeResponse = {
ok: boolean;
message: string;
inviteCode?: (string | null);
};

View File

@@ -5,6 +5,7 @@
import type { Body_upload_passport_image } from '../models/Body_upload_passport_image';
import type { CreateUserRequest } from '../models/CreateUserRequest';
import type { CreateUserResponse } from '../models/CreateUserResponse';
import type { GenerateInviteCodeResponse } from '../models/GenerateInviteCodeResponse';
import type { GetAllUsersResponse } from '../models/GetAllUsersResponse';
import type { GetManagersResponse } from '../models/GetManagersResponse';
import type { UpdateUserDepartmentSectionsRequest } from '../models/UpdateUserDepartmentSectionsRequest';
@@ -128,4 +129,15 @@ export class UserService {
},
});
}
/**
* Generate Invite Code
* @returns GenerateInviteCodeResponse Successful Response
* @throws ApiError
*/
public static generateInviteCode(): CancelablePromise<GenerateInviteCodeResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/user/generate-invite-code',
});
}
}

View File

@@ -1,16 +1,18 @@
import { UserSchema } from "../../../../client";
import { UserSchema, UserService } from "../../../../client";
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
import { ActionIcon, Button, Flex, rem, Text, Tooltip } from "@mantine/core";
import { ActionIcon, Badge, Button, Flex, Input, rem, Text, Tooltip } from "@mantine/core";
import { useUsersTableColumns } from "./columns.tsx";
import { IconEdit, IconQrcode, IconTrash } from "@tabler/icons-react";
import { modals } from "@mantine/modals";
import { MRT_TableOptions } from "mantine-react-table";
import { useUsersTabContext } from "../../tabs/Users/contexts/UsersTabContext.tsx";
import { notifications } from "../../../../shared/lib/notifications.ts";
import { useClipboard } from "@mantine/hooks";
const UsersTable = () => {
const columns = useUsersTableColumns();
const clipboard = useClipboard();
const {
usersCrud: {
items,
@@ -71,6 +73,33 @@ const UsersTable = () => {
if (!pdfWindow) return;
pdfWindow.print();
};
const onCreateInviteCodeClick = async () => {
const { inviteCode, ok, message } = await UserService.generateInviteCode();
if (!ok || !inviteCode) {
notifications.error({ message });
return;
}
modals.openConfirmModal({
withCloseButton: false,
children: (
<Flex gap={rem(10)} direction={"column"} justify={"center"} align={"center"}>
<Text>
Ваш код приглашения!
</Text>
<Badge variant={"default"} style={{cursor:"text"}} radius={"sm"} size={"xl"}>{inviteCode}</Badge>
<Input.Description>
Код действителен в течении 30 минут
</Input.Description>
</Flex>
),
labels: { confirm: "Скопировать", cancel: "Закрыть" },
onConfirm: () => {
clipboard.copy(inviteCode);
},
});
};
return (
<BaseTable
data={items}
@@ -82,11 +111,20 @@ const UsersTable = () => {
enableTopToolbar: true,
renderTopToolbar: (
<Flex p={rem(10)}>
<Button
variant={"default"}
onClick={() => onCreateClick()}>
Создать пользователя
</Button>
<Flex gap={rem(10)}>
<Button
variant={"default"}
onClick={() => onCreateClick()}>
Создать пользователя
</Button>
<Button
onClick={() => onCreateInviteCodeClick()}
variant={"default"}
>
Создать код приглашения
</Button>
</Flex>
</Flex>
),
enableRowActions: true,

View File

@@ -1,20 +1,20 @@
import { Button, Container, Paper, Title } from "@mantine/core";
import { Button, Checkbox, Container, Paper, Stack, TextInput, Title } from "@mantine/core";
import classes from "./LoginPage.module.scss";
import { RootState, useAppDispatch } from "../../redux/store.ts";
import { AuthService } from "../../client";
import TelegramLoginButton, {
TelegramUser,
} from "../../components/TelegramAuthButton/TelegramAuthButton.tsx";
import { ApiError, AuthService } from "../../client";
import TelegramLoginButton, { TelegramUser } from "../../components/TelegramAuthButton/TelegramAuthButton.tsx";
import { notifications } from "../../shared/lib/notifications.ts";
import { login } from "../../features/authSlice.ts";
import { Navigate, useNavigate } from "@tanstack/react-router";
import { useSelector } from "react-redux";
import { useEffect } from "react";
import { useEffect, useState } from "react";
const LoginPage = () => {
const dispatch = useAppDispatch();
const authState = useSelector((state: RootState) => state.auth);
const navigate = useNavigate();
const [hasInviteCode, setHasInviteCode] = useState(false);
const [inviteCode, setInviteCode] = useState("");
useEffect(() => {
if (authState.isAuthorized)
// ???????????
@@ -45,38 +45,55 @@ const LoginPage = () => {
radius="md">
<TelegramLoginButton
botName={"DencoFulfillmentTestBot"}
dataOnauth={() => {}}
dataOnauth={() => {
}}
wrapperProps={{ style: { display: "none" } }}
/>
<Button
fullWidth
onClick={() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
window.Telegram.Login.auth(
{
bot_id: import.meta.env.VITE_BOT_ID,
request_access: true,
},
(data: TelegramUser) => {
AuthService.loginAuthLoginPost({
requestBody: data,
})
.then(({ accessToken }) => {
dispatch(
login({ accessToken: accessToken })
);
<Stack>
<Button
fullWidth
onClick={() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
window.Telegram.Login.auth(
{
bot_id: import.meta.env.VITE_BOT_ID,
request_access: true,
},
(data: TelegramUser) => {
AuthService.loginAuthLoginPost({
requestBody: {...data, invite_code: inviteCode.trim() ? inviteCode : undefined},
})
.catch(() => {
notifications.error({
message: "Неудалось войти!",
.then(({ accessToken }) => {
dispatch(
login({ accessToken: accessToken }),
);
})
.catch((reason: ApiError) => {
const message = reason?.body?.detail.toString() || "Ошибка авторизации";
notifications.error({
message,
});
});
});
}
);
}}>
Войти через Telegram
</Button>
},
);
}}>
Войти через Telegram
</Button>
{!hasInviteCode ? (
<Checkbox label={"У меня есть код подключения"} checked={hasInviteCode}
onChange={event => setHasInviteCode(event.target.checked)} />) : (
<TextInput
value={inviteCode}
onChange={event => setInviteCode(event.currentTarget.value)}
placeholder={"Введите код подключения"}
/>
)}
</Stack>
</Paper>
</Container>
);

View File

@@ -1,12 +1,12 @@
/* prettier-ignore-start */
/* eslint-disable */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// This file is auto-generated by TanStack Router
// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { createFileRoute } from '@tanstack/react-router'
@@ -36,16 +36,19 @@ const IndexLazyImport = createFileRoute('/')()
// Create/Update Routes
const TestLazyRoute = TestLazyImport.update({
id: '/test',
path: '/test',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/test.lazy').then((d) => d.Route))
const StatisticsLazyRoute = StatisticsLazyImport.update({
id: '/statistics',
path: '/statistics',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/statistics.lazy').then((d) => d.Route))
const ShippingwarehousesLazyRoute = ShippingwarehousesLazyImport.update({
id: '/shipping_warehouses',
path: '/shipping_warehouses',
getParentRoute: () => rootRoute,
} as any).lazy(() =>
@@ -53,66 +56,79 @@ const ShippingwarehousesLazyRoute = ShippingwarehousesLazyImport.update({
)
const ServicesLazyRoute = ServicesLazyImport.update({
id: '/services',
path: '/services',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/services.lazy').then((d) => d.Route))
const ResiduesLazyRoute = ResiduesLazyImport.update({
id: '/residues',
path: '/residues',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/residues.lazy').then((d) => d.Route))
const ReceiptLazyRoute = ReceiptLazyImport.update({
id: '/receipt',
path: '/receipt',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/receipt.lazy').then((d) => d.Route))
const ProductsLazyRoute = ProductsLazyImport.update({
id: '/products',
path: '/products',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/products.lazy').then((d) => d.Route))
const MarketplacesLazyRoute = MarketplacesLazyImport.update({
id: '/marketplaces',
path: '/marketplaces',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/marketplaces.lazy').then((d) => d.Route))
const LoginLazyRoute = LoginLazyImport.update({
id: '/login',
path: '/login',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/login.lazy').then((d) => d.Route))
const LeadsLazyRoute = LeadsLazyImport.update({
id: '/leads',
path: '/leads',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/leads.lazy').then((d) => d.Route))
const ClientsLazyRoute = ClientsLazyImport.update({
id: '/clients',
path: '/clients',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/clients.lazy').then((d) => d.Route))
const BarcodeLazyRoute = BarcodeLazyImport.update({
id: '/barcode',
path: '/barcode',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/barcode.lazy').then((d) => d.Route))
const AdminLazyRoute = AdminLazyImport.update({
id: '/admin',
path: '/admin',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/admin.lazy').then((d) => d.Route))
const IndexLazyRoute = IndexLazyImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/index.lazy').then((d) => d.Route))
const LeadsDealIdRoute = LeadsDealIdImport.update({
id: '/$dealId',
path: '/$dealId',
getParentRoute: () => LeadsLazyRoute,
} as any)
const DealsDealIdRoute = DealsDealIdImport.update({
id: '/deals/$dealId',
path: '/deals/$dealId',
getParentRoute: () => rootRoute,
} as any)
@@ -406,8 +422,6 @@ export const routeTree = rootRoute
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
/* prettier-ignore-end */
/* ROUTE_MANIFEST_START
{
"routes": {