api ulandi
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { addNews } from "@/pages/news/lib/api";
|
||||
import { useNewsStore } from "@/pages/news/lib/data";
|
||||
import { newsPostForm, type NewsPostFormType } from "@/pages/news/lib/form";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import {
|
||||
Form,
|
||||
@@ -11,180 +15,275 @@ import { Input } from "@/shared/ui/input";
|
||||
import { Label } from "@/shared/ui/label";
|
||||
import { Textarea } from "@/shared/ui/textarea";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PlusCircle, Trash2, XIcon } from "lucide-react";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { ImagePlus, PlusCircle, Trash2 } from "lucide-react";
|
||||
import { useFieldArray, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import z from "zod";
|
||||
import { toast } from "sonner";
|
||||
|
||||
const newsItemSchema = z.object({
|
||||
desc: z.string().min(2, {
|
||||
message: "Yangilik matni kamida 2 belgidan iborat bo‘lishi kerak",
|
||||
}),
|
||||
banner: z.string().min(1, { message: "Banner rasmi majburiy" }),
|
||||
});
|
||||
const StepTwo = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { stepOneData } = useNewsStore();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const newsListSchema = z.object({
|
||||
items: z
|
||||
.array(newsItemSchema)
|
||||
.min(1, { message: "Kamida 1 ta yangilik kerak" }),
|
||||
});
|
||||
|
||||
type NewsFormType = z.infer<typeof newsListSchema>;
|
||||
|
||||
const StepTwo = ({
|
||||
setStep,
|
||||
isEditMode,
|
||||
}: {
|
||||
setStep: Dispatch<SetStateAction<number>>;
|
||||
isEditMode: boolean;
|
||||
}) => {
|
||||
const form = useForm<NewsFormType>({
|
||||
resolver: zodResolver(newsListSchema),
|
||||
const form = useForm<NewsPostFormType>({
|
||||
resolver: zodResolver(newsPostForm),
|
||||
defaultValues: {
|
||||
items: [{ desc: "", banner: "" }],
|
||||
desc: "",
|
||||
desc_ru: "",
|
||||
is_public: "yes",
|
||||
sections: [{ image: undefined as any, text: "", text_ru: "" }],
|
||||
post_tags: [""],
|
||||
},
|
||||
});
|
||||
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control: form.control,
|
||||
name: "items",
|
||||
name: "sections",
|
||||
});
|
||||
const navigator = useNavigate();
|
||||
const { watch, setValue } = form;
|
||||
const postTags = watch("post_tags");
|
||||
const addTag = () => setValue("post_tags", [...postTags, ""]);
|
||||
const removeTag = (i: number) =>
|
||||
setValue(
|
||||
"post_tags",
|
||||
postTags.filter((_, idx) => idx !== i),
|
||||
);
|
||||
|
||||
function onSubmit() {
|
||||
navigator("/news");
|
||||
}
|
||||
const { mutate: added } = useMutation({
|
||||
mutationFn: (body: FormData) => addNews(body),
|
||||
onSuccess: () => {
|
||||
queryClient.refetchQueries({ queryKey: ["all_news"] });
|
||||
navigate("/news");
|
||||
},
|
||||
onError: () => {
|
||||
toast.error(t("Xatolik yuz berdi"), {
|
||||
richColors: true,
|
||||
position: "top-center",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handleImageChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
index: number,
|
||||
) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) form.setValue(`sections.${index}.image`, file);
|
||||
};
|
||||
|
||||
const onSubmit = (values: NewsPostFormType) => {
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append("title", stepOneData.title);
|
||||
formData.append("title_ru", stepOneData.title_ru);
|
||||
formData.append("image", stepOneData.banner ?? "");
|
||||
formData.append("text", stepOneData.desc);
|
||||
formData.append("text_ru", stepOneData.desc_ru);
|
||||
formData.append("is_public", values.is_public === "no" ? "false" : "true");
|
||||
formData.append("category", stepOneData.category);
|
||||
|
||||
values.sections.forEach((section, i) => {
|
||||
formData.append(`post_images[${i}]`, section.image);
|
||||
formData.append(`post_text[${i}]`, section.text);
|
||||
formData.append(`post_text_ru[${i}]`, section.text_ru);
|
||||
});
|
||||
|
||||
values.post_tags.forEach((tag, i) => {
|
||||
formData.append(`post_tags[${i}]`, tag);
|
||||
});
|
||||
|
||||
added(formData);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-8 bg-gray-900 p-6 rounded-2xl text-white"
|
||||
className="space-y-6 bg-gray-900 p-6 rounded-2xl text-white"
|
||||
>
|
||||
<h2 className="text-2xl font-semibold">Yangiliklar ro‘yxati</h2>
|
||||
<Label className="text-lg">{t("Yangilik bo‘limlari")}</Label>
|
||||
|
||||
{/* DESC (UZ) */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="desc"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<Label>{t("Qisqacha ta'rif (UZ)")}</Label>
|
||||
<FormControl>
|
||||
<Textarea {...field} placeholder={t("Qisqacha ta'rif")} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* DESC (RU) */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="desc_ru"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<Label>{t("Qisqacha ta'rif (RU)")}</Label>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
{...field}
|
||||
placeholder={t("Qisqacha ta'rif (rus tilida)")}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Label>{t("Teglar")}</Label>
|
||||
{postTags.map((__, i) => (
|
||||
<FormField
|
||||
key={i}
|
||||
control={form.control}
|
||||
name={`post_tags.${i}`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="relative">
|
||||
{postTags.length > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeTag(i)}
|
||||
className="absolute top-1 right-1 text-red-400 hover:text-red-500"
|
||||
>
|
||||
<Trash2 className="size-4" />
|
||||
</button>
|
||||
)}
|
||||
<FormControl>
|
||||
<Input {...field} placeholder={t("Masalan: sport")} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
type="button"
|
||||
onClick={addTag}
|
||||
className="bg-gray-600 hover:bg-gray-700"
|
||||
>
|
||||
<PlusCircle className="size-5 mr-2" />
|
||||
{t("Teg qo'shish")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{fields.map((field, index) => (
|
||||
<div
|
||||
key={field.id}
|
||||
className="relative border border-gray-700 bg-gray-800 rounded-xl p-4 space-y-4"
|
||||
className="border border-gray-700 rounded-lg p-4 space-y-4"
|
||||
>
|
||||
{/* O'chirish tugmasi */}
|
||||
{fields.length > 1 && (
|
||||
<div className="flex justify-between items-center">
|
||||
<p className="text-sm text-gray-300">
|
||||
{t("Bo‘lim")} #{index + 1}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => remove(index)}
|
||||
className="absolute top-3 right-3 text-red-400 hover:text-red-500"
|
||||
className="text-red-400 hover:text-red-500"
|
||||
>
|
||||
<Trash2 className="size-5" />
|
||||
<Trash2 className="size-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* DESC FIELD */}
|
||||
{/* Image */}
|
||||
<div>
|
||||
<Label>{t("Rasm")}</Label>
|
||||
{form.watch(`sections.${index}.image`) ? (
|
||||
<div className="relative mt-2 w-48">
|
||||
<img
|
||||
src={URL.createObjectURL(
|
||||
form.watch(`sections.${index}.image`),
|
||||
)}
|
||||
alt="preview"
|
||||
className="rounded-lg border border-gray-700 object-cover h-40 w-full"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
form.setValue(`sections.${index}.image`, undefined as any)
|
||||
}
|
||||
className="absolute top-1 right-1 bg-black/70 rounded-full p-1 hover:bg-black/90"
|
||||
>
|
||||
<Trash2 className="size-4 text-red-400" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<input
|
||||
id={`section-img-${index}`}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={(e) => handleImageChange(e, index)}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`section-img-${index}`}
|
||||
className="inline-flex items-center cursor-pointer bg-gray-700 hover:bg-gray-600 text-white px-4 py-2 rounded-lg mt-2"
|
||||
>
|
||||
<ImagePlus className="size-5 mr-2" />
|
||||
{t("Rasm tanlash")}
|
||||
</label>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Text (UZ) */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`items.${index}.desc`}
|
||||
name={`sections.${index}.text`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<Label className="text-md">Yangilik haqida</Label>
|
||||
<Label>{t("Matn (UZ)")}</Label>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="Yangilik haqida"
|
||||
{...field}
|
||||
className="min-h-48 max-h-56 !text-md bg-gray-800 border-gray-700 text-white"
|
||||
/>
|
||||
<Textarea {...field} placeholder={t("Matn kiriting")} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* BANNER FIELD */}
|
||||
{/* Text (RU) */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`items.${index}.banner`}
|
||||
render={() => (
|
||||
name={`sections.${index}.text_ru`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<Label className="text-md">Banner rasmi</Label>
|
||||
<Label>{t("Matn (RU)")}</Label>
|
||||
<FormControl>
|
||||
<div className="flex flex-col gap-3 w-full">
|
||||
<Input
|
||||
type="file"
|
||||
id={`file-${index}`}
|
||||
accept="image/*"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
const url = URL.createObjectURL(file);
|
||||
form.setValue(`items.${index}.banner`, url);
|
||||
}
|
||||
}}
|
||||
className="hidden"
|
||||
/>
|
||||
|
||||
<label
|
||||
htmlFor={`file-${index}`}
|
||||
className="w-full border-2 border-dashed h-40 border-gray-600 hover:border-gray-500 transition-all flex flex-col items-center gap-2 justify-center py-4 rounded-2xl cursor-pointer"
|
||||
>
|
||||
<p className="font-semibold text-xl text-white">
|
||||
Drag or select files
|
||||
</p>
|
||||
<p className="text-gray-300 text-sm">
|
||||
Drop files here or click to browse
|
||||
</p>
|
||||
</label>
|
||||
|
||||
{form.watch(`items.${index}.banner`) && (
|
||||
<div className="relative size-24 rounded-md overflow-hidden border border-gray-700">
|
||||
<img
|
||||
src={form.watch(`items.${index}.banner`)}
|
||||
alt="Banner preview"
|
||||
className="object-cover w-full h-full"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
form.setValue(`items.${index}.banner`, "")
|
||||
}
|
||||
className="absolute top-1 right-1 bg-white/80 rounded-full p-1 shadow hover:bg-white"
|
||||
>
|
||||
<XIcon className="size-4 text-destructive" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Textarea {...field} placeholder={t("Matn (rus tilida)")} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
append({ image: undefined as any, text: "", text_ru: "" })
|
||||
}
|
||||
className="bg-gray-700 hover:bg-gray-600"
|
||||
>
|
||||
<PlusCircle className="size-5 mr-2" />
|
||||
{t("Bo‘lim qo‘shish")}
|
||||
</Button>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => append({ desc: "", banner: "" })}
|
||||
className="flex items-center px-6 py-5 text-lg gap-2 bg-gray-600 hover:bg-gray-700 text-white cursor-pointer"
|
||||
>
|
||||
<PlusCircle className="size-5" />
|
||||
Qo‘shish
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Navigatsiya tugmalari */}
|
||||
<div className="w-full flex justify-between pt-4">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => setStep(1)}
|
||||
className="bg-gray-600 hover:bg-gray-700 text-white"
|
||||
>
|
||||
Orqaga
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white"
|
||||
className="mt-6 px-8 py-3 bg-blue-600 text-white rounded-md hover:bg-blue-700 cursor-pointer"
|
||||
>
|
||||
{isEditMode ? "Yangiliklarni saqlash" : "Saqlash"}
|
||||
{t("Saqlash")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user