Vitron-Front/app/routes/profile.location._index.tsx
2026-04-29 01:44:16 +03:30

291 lines
9.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Link, useNavigate } from "@remix-run/react";
import { MapPin, Menu, Pencil, Plus, Trash, X } from "lucide-react";
import { useEffect, useState } from "react";
import ProfilePagesHeader from "../components/ProfilePagesHeader";
import { MetaFunction } from "@remix-run/node";
import {
useServiceDeleteAddress,
useServiceGetAddresses,
} from "../utils/RequestHandler";
import { Button } from "../components/ui/button";
import { Drawer, DrawerContent, DrawerHeader } from "../components/ui/drawer";
import { Dialog, DialogContent, DialogOverlay } from "../components/ui/dialog";
import { useToast } from "../hooks/use-toast";
import { Address } from "src/api/types";
import UiProvider from "~/components/UiProvider";
export const meta: MetaFunction = () => {
const title = "آدرس‌های من | ویترون";
const description = "مدیریت آدرس‌های ارسال سفارشات در ویترون";
const imageUrl =
"https://vitrownstatics.s3.ir-thr-at1.arvanstorage.ir/SVG-06.svg?versionId=";
return [
{ title },
{ name: "description", content: description },
{
name: "keywords",
content: "آدرس، مدیریت آدرس، آدرس ارسال، ویترون",
},
{ name: "robots", content: "noindex, follow" },
// Open Graph
{ property: "og:type", content: "website" },
{ property: "og:title", content: title },
{ property: "og:description", content: description },
{ property: "og:image", content: imageUrl },
{ property: "og:site_name", content: "ویترون" },
{ property: "og:locale", content: "fa_IR" },
// Twitter Card
{ name: "twitter:card", content: "summary_large_image" },
{ name: "twitter:title", content: title },
{ name: "twitter:description", content: description },
{ name: "twitter:image", content: imageUrl },
];
};
export default function Locaton() {
return (
<div className="flex flex-col gap-4 w-full items-center">
<ProfilePagesHeader title="آدرس‌های من" />
<MainContent />
<Link to={"/profile/location/add"} className="fixed bottom-16 z-10">
<Button size={"xl"} variant="dark">
<Plus className="text-WHITE size-6" />
افزودن آدرس جدید
</Button>
</Link>
</div>
);
}
const MainContent = () => {
const {
data: dataAddress,
isFetching: isFetchingAddress,
isError,
refetch,
} = useServiceGetAddresses();
return (
<UiProvider
isLoading={isFetchingAddress}
isEmpty={!!(dataAddress && dataAddress?.length === 0)}
isError={isError}
onRetry={refetch}
type="location"
>
<div className="flex flex-col gap-0 items-center px-4 w-full">
{dataAddress?.map((address: Address, index: number) => {
return (
<AddressRow
address={address}
key={index}
isLastAddress={index !== dataAddress?.length - 1}
/>
);
})}
</div>
</UiProvider>
);
};
const AddressRow = ({
address,
isLastAddress,
}: {
address: Address;
isLastAddress: boolean;
}) => {
const [isOpen, setIsOpen] = useState(false);
const toggleDrawer = () => {
setIsOpen(!isOpen);
};
return (
<>
<div
onClick={() => toggleDrawer()}
onKeyDown={() => {}}
role="button"
tabIndex={0}
className={`w-full flex gap-2 items-center py-4 ${isLastAddress ? "border-b" : ""}`}
>
<MapPin className="text-GRAY text-6" />
<div className="flex flex-col gap-0 w-full">
<p className="text-R14">{address?.title}</p>
<p className="text-R12 line-clamp-1">{address?.mainAddress}</p>
</div>
<Menu className="text-6" />
</div>
<AdderssSettingDrawer
address={address}
isOpen={isOpen}
setIsOpen={setIsOpen}
/>
</>
);
};
export const AdderssSettingDrawer = ({
isOpen,
setIsOpen,
address,
}: {
isOpen: boolean;
setIsOpen: (open: boolean) => void;
address: Address | null;
}) => {
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
const navigate = useNavigate();
return (
<>
<Drawer open={isOpen} onOpenChange={setIsOpen}>
<DrawerContent className="max-h-[85vh] overflow-hidden">
<DrawerHeader className="sticky top-0 z-10 bg-WHITE">
<div className="flex flex-row gap-0 w-full relative items-center justify-center">
<X
onClick={() => setIsOpen(false)}
className="absolute size-6 top-2 right-3"
/>
<p className={"text-B12 font-bold pt-3"}>تنظیمات آدرس</p>
</div>
</DrawerHeader>
<div className="overflow-y-auto max-h-[calc(85vh-60px)] pb-4">
<div className="flex flex-col gap-6 w-full px-4 py-6">
<div className="flex flex-col gap-2 w-full">
<p className="text-B14">{address?.title}</p>
<p className="text-R14 line-clamp-1">{address?.mainAddress}</p>
</div>
<div className="flex flex-wrap gap-3 w-full">
<Button
className="w-[calc(50%-6px)]"
size={"sm"}
variant="dark"
onClick={() => {
setIsOpen(false);
navigate(`/profile/location/edit/${address?.id}`);
}}
>
<Pencil className="text-WHITE size-6" />
ویرایش آدرس
</Button>
<Button
onClick={() => {
setDeleteModalOpen(true);
setIsOpen(false);
}}
className="w-[calc(50%-6px)]"
size={"sm"}
variant="primary"
>
<Trash className=" size-6" />
حذف آدرس
</Button>
</div>
</div>
</div>
</DrawerContent>
</Drawer>
<DeleteAddressModal
address={address}
open={deleteModalOpen}
onOpenChange={setDeleteModalOpen}
/>
</>
);
};
const DeleteAddressModal = ({
address,
open,
onOpenChange,
}: {
address: Address | null;
open: boolean;
onOpenChange: (open: boolean) => void;
}) => {
const { toast } = useToast();
const deleteAddressMutation = useServiceDeleteAddress(
address?.id || "",
address?.title || "",
toast
);
useEffect(() => {
if (deleteAddressMutation.data) {
onOpenChange(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [deleteAddressMutation.data, deleteAddressMutation.error]);
const onDelete = async () => {
if (!deleteAddressMutation.isPending) {
try {
onOpenChange(false);
await deleteAddressMutation.mutateAsync();
toast({
title: "آدرس با موفقیت حذف شد",
description: "آدرس شما با موفقیت حذف شد.",
});
} catch (error) {
toast({
title: "خطا در حذف آدرس",
description:
"متاسفانه مشکلی در حذف آدرس پیش آمده است. لطفا دوباره تلاش کنید.",
variant: "destructive",
});
}
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogOverlay />
<DialogContent className="p-4 bg-WHITE rounded-[15px] w-[calc(100%-40px)]">
<div className="flex flex-col gap-10">
<div className={"gap-3 items-center w-full"}>
<Trash className="text-RED text-6" />
<p className="text-B12 font-bold mt-1">حذف آدرس</p>
<p className="text-R12">
مطمئن هستید که آدرس &apos;&apos;{address?.title}&apos;&apos; حذف
شود؟
</p>
</div>
<div className="flex flex-row gap-6 w-full">
<Button
className="w-[100%]"
size={"sm"}
variant="dark"
onClick={() => onOpenChange(false)}
>
انصراف
</Button>
<Button
disabled={deleteAddressMutation.isPending}
className="w-[100%]"
size={"sm"}
variant="primary"
onClick={() => onDelete()}
>
{deleteAddressMutation.isPending ? (
<>
<div className="relative w-5 h-5 mr-2">
<div className="absolute top-0 left-0 w-full h-full border-2 border-t-RED border-r-transparent border-b-transparent border-l-transparent rounded-full animate-spin"></div>
<div
className="absolute top-0.5 left-0.5 w-4 h-4 border-2 border-t-transparent border-r-RED border-b-transparent border-l-transparent rounded-full animate-spin"
style={{
animationDirection: "reverse",
animationDuration: "0.7s",
}}
></div>
</div>
درحال حذف...
</>
) : (
"حذف آدرس"
)}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
};