This commit is contained in:
Samandar Turgunboyev
2025-11-01 19:12:38 +05:00
parent 4e9b2f3bd8
commit 193d01ed51
27 changed files with 1300 additions and 120 deletions

View File

@@ -52,6 +52,12 @@ const MENU_ITEMS = [
path: "/user",
roles: ["moderator", "admin", "superuser", "operator"],
},
{
label: "Profile",
icon: Users,
path: "/profile",
roles: ["tour_admin"],
},
{
label: "Tur firmalar",
icon: Building2,
@@ -251,9 +257,10 @@ export function Sidebar({ role }: SidebarProps) {
</li>
);
})}
<LangToggle />
</ul>
<div className="border-t border-gray-700 mt-2 pt-3 px-2">
<LangToggle />
</div>
<div className="border-t border-gray-700 mt-2 pt-3 px-2">
<Button
onClick={handleLogout}

View File

@@ -0,0 +1,50 @@
import httpClient from "@/shared/config/api/httpClient";
import { GET_ME } from "@/shared/config/api/URLs";
interface GetMe {
status: boolean;
data: {
id: number;
last_login: string;
is_superuser: boolean;
first_name: string;
last_name: string;
is_staff: boolean;
is_active: boolean;
date_joined: string;
phone: string;
email: string;
username: string;
avatar: string;
validated_at: string;
role: string;
travel_agency: number;
};
}
export interface GetAllParticipantData {
status: boolean;
data: {
links: {
previous: string;
next: string;
};
total_items: number;
total_pages: number;
page_size: number;
current_page: number;
results: {
id: number;
first_name: string;
gender: "male" | "female";
last_name: string;
}[];
};
}
export const User_Api = {
async getMe() {
const res = await httpClient.get<GetMe>(GET_ME);
return res;
},
};

View File

@@ -0,0 +1,28 @@
import httpClient from "@/shared/config/api/httpClient";
import { UPDATE_USERS } from "@/shared/config/api/URLs";
export const Auth_Api = {
async updateUser({
first_name,
last_name,
avatar,
}: {
first_name: string;
last_name: string;
avatar?: File;
}) {
const formData = new FormData();
formData.append("first_name", first_name);
formData.append("last_name", last_name);
if (avatar) {
formData.append("avatar", avatar);
}
const res = await httpClient.patch(UPDATE_USERS, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
return res;
},
};

View File

@@ -0,0 +1,11 @@
import z from "zod";
export const welcomeForm = z.object({
firstName: z.string().min(1, { message: "Majburiy maydon" }),
lastName: z.string().min(1, { message: "Majburiy maydon" }),
});
export const editUserName = z.object({
firstName: z.string().min(1, { message: "Majburiy maydon" }),
lastName: z.string().min(1, { message: "Majburiy maydon" }),
});

View File

@@ -0,0 +1,15 @@
import { create } from 'zustand';
interface WelcomeState {
openModal: boolean;
setOpenModal: (openModal: boolean) => void;
openModalMobile: boolean;
setOpenModalMobile: (openModal: boolean) => void;
}
export const useWelcomeStore = create<WelcomeState>((set) => ({
openModal: false,
setOpenModal: (openModal) => set({ openModal }),
openModalMobile: false,
setOpenModalMobile: (openModalMobile) => set({ openModalMobile }),
}));

View File

@@ -1,26 +1,210 @@
import LangToggle from "@/widgets/lang-toggle/ui/lang-toggle";
import ModeToggle from "@/widgets/theme-toggle/ui/theme-toggle";
import GitHubButton from "react-github-btn";
"use client";
import { Button } from "@/shared/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/shared/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from "@/shared/ui/form";
import { Input } from "@/shared/ui/input";
import { Label } from "@/shared/ui/label";
import { Auth_Api } from "@/widgets/welcome/lib/data";
import { welcomeForm } from "@/widgets/welcome/lib/form";
import { useWelcomeStore } from "@/widgets/welcome/lib/hook";
import { zodResolver } from "@hookform/resolvers/zod";
import Drawer from "@mui/material/Drawer";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { AxiosError } from "axios";
import clsx from "clsx";
import { LoaderCircle, XIcon } from "lucide-react";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import z from "zod";
const Welcome = () => {
return (
<div className="custom-container h-screen rounded-2xl flex items-center justify-center">
<div className="flex flex-col gap-2 items-center">
<GitHubButton
href="https://github.com/fiasuz/fias-ui"
data-color-scheme="no-preference: light; light: light; dark: dark;"
data-size="large"
data-show-count="true"
aria-label="Star fiasuz/fias-ui on GitHub"
const { openModal: open, setOpenModal: setOpen } = useWelcomeStore();
const queryClient = useQueryClient();
const { t } = useTranslation();
const form = useForm<z.infer<typeof welcomeForm>>({
resolver: zodResolver(welcomeForm),
defaultValues: {
firstName: "",
lastName: "",
},
});
const isMobile = useMediaQuery("(max-width:1024px)");
const { mutate, isPending } = useMutation({
mutationFn: ({
first_name,
last_name,
}: {
first_name: string;
last_name: string;
}) => {
return Auth_Api.updateUser({ first_name, last_name });
},
onSuccess() {
queryClient.invalidateQueries();
setOpen(false);
},
onError(error: AxiosError<{ non_field_errors: [string] }>) {
toast.error(t("Xatolik yuz berdi"), {
icon: null,
description: error.name,
position: "bottom-right",
});
},
});
function onSubmit(values: z.infer<typeof welcomeForm>) {
mutate({
first_name: values.firstName,
last_name: values.lastName,
});
}
const formContent = (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 w-full">
<FormField
control={form.control}
name="firstName"
render={({ field }) => (
<FormItem>
<Label className="text-xl font-semibold text-[#212122]">
{t("Имя")}
</Label>
<FormControl>
<Input
{...field}
placeholder={t("Введите имя")}
className="h-[60px] px-4 font-medium !text-lg rounded-xl text-black"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="lastName"
render={({ field }) => (
<FormItem>
<Label className="text-xl font-semibold text-[#212122]">
{t("Фамилия")}
</Label>
<FormControl>
<Input
{...field}
placeholder={t("Введите фамилию")}
className="h-[60px] px-4 font-medium !text-lg rounded-xl text-black max-lg:w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="submit"
className="px-14 py-8 rounded-4xl text-lg font-medium cursor-pointer bg-[#1764FC] hover:bg-[#1764FC] max-lg:w-full max-lg:mt-10"
>
Star
</GitHubButton>
<div className="flex flex-row gap-2">
<ModeToggle />
<LangToggle />
</div>
</div>
</div>
{isPending ? (
<LoaderCircle className="animate-spin" />
) : (
t("Сохранить")
)}
</Button>
</form>
</Form>
);
return (
<>
{!isMobile ? (
<Dialog open={open}>
<DialogContent
className="rounded-4xl !max-w-3xl"
showCloseButton={false}
>
<DialogHeader>
<DialogTitle
className={clsx("flex justify-between w-full items-center")}
>
<div className="flex flex-col gap-4">
<p className="text-2xl">{t("Давайте познакомимся!")}</p>
<p className="w-[80%] text-[#646465] font-medium">
{t(
"Чтобы завершить регистрацию, пожалуйста, укажите ваше имя",
)}
</p>
</div>
<DialogClose asChild>
<Button
variant={"outline"}
disabled
className="rounded-full p-6 h-12 w-12 cursor-pointer"
>
<XIcon className="w-26 h-26" />
</Button>
</DialogClose>
</DialogTitle>
<DialogDescription className="flex flex-col justify-center items-center gap-8 mt-5">
{formContent}
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
) : (
<Drawer
anchor="bottom"
open={open}
onClose={() => setOpen(false)}
PaperProps={{
sx: {
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
width: "100vw",
height: "auto",
display: "flex",
flexDirection: "column",
p: 2,
},
}}
>
<div className="flex justify-between">
<div className="flex flex-col gap-2">
<p className="text-3xl font-semibold">
{t("Давайте познакомимся!")}
</p>
<p className="w-[80%] text-[#646465] font-medium">
{t("Чтобы завершить регистрацию, пожалуйста, укажите ваше имя")}
</p>
</div>
<Button
variant={"outline"}
disabled
className="rounded-full p-6 h-12 w-12 cursor-pointer"
>
<XIcon className="w-26 h-26" />
</Button>
</div>
<div className="mt-5">{formContent}</div>
</Drawer>
)}
</>
);
};