merge
This commit is contained in:
@@ -1,20 +1,30 @@
|
||||
<script lang="ts" setup>
|
||||
// imports
|
||||
|
||||
import useGetProduct from "~/composables/api/product/useGetProduct";
|
||||
|
||||
// states
|
||||
|
||||
const route = useRoute();
|
||||
const id = route.params.id as string | undefined;
|
||||
|
||||
const { data: product } = useGetProduct(id);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center justify-between w-full bg-slate-100 border-[1.5px] border-slate-200 rounded-100 p-6">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex p-1 items-center justify-center rounded-full bg-success-500">
|
||||
<Icon name="ci:check" class="size-4 **:stroke-white"/>
|
||||
<Icon
|
||||
name="ci:check"
|
||||
class="size-4 **:stroke-white"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="typo-label-sm whitespace-nowrap">دریافت حضوری فروشگاه</span>
|
||||
<span class="typo-p-sm whitespace-nowrap">معمولا طی ۲ ساعت اماده میشود</span>
|
||||
<span class="typo-label-sm whitespace-nowrap">{{ product?.customer_pickup_title }}</span>
|
||||
<span class="typo-p-sm whitespace-nowrap">{{ product?.customer_pickup_description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="typo-p-xs max-sm:hidden">
|
||||
برسی موجودی در فروشگاه های دیگر
|
||||
</span>
|
||||
<span class="typo-p-xs max-sm:hidden">فروشگاه هیملز</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,33 @@
|
||||
// imports
|
||||
|
||||
import { useMutation } from "@tanstack/vue-query";
|
||||
import { API_ENDPOINTS } from "~/constants";
|
||||
|
||||
// types
|
||||
|
||||
export type CreateContactUsTicketRequest = {
|
||||
full_name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
type: "ORDER_FOLLOW_UP" | "SUGGESTION" | "COMPLAINT";
|
||||
message: string;
|
||||
};
|
||||
|
||||
const useCreateContactUsTicket = () => {
|
||||
// state
|
||||
|
||||
const { $axios: axios } = useNuxtApp();
|
||||
|
||||
// methods
|
||||
|
||||
const handleCreateContactUsTicket = async (params: CreateContactUsTicketRequest) => {
|
||||
const { data } = await axios.post(API_ENDPOINTS.tickets.contact_us_ticket, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (messageData: CreateContactUsTicketRequest) => handleCreateContactUsTicket(messageData),
|
||||
});
|
||||
};
|
||||
|
||||
export default useCreateContactUsTicket;
|
||||
@@ -51,6 +51,7 @@ export const API_ENDPOINTS = {
|
||||
delete_attachment: "/tickets/attachment/delete",
|
||||
get_one: "/tickets",
|
||||
create_message: "/tickets/message/create",
|
||||
contact_us_ticket: "/tickets/contact-us/create",
|
||||
},
|
||||
orders: {
|
||||
get_all: "/order/all",
|
||||
|
||||
+186
-24
@@ -1,19 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
// imports
|
||||
|
||||
import useVuelidate from "@vuelidate/core";
|
||||
import { email, helpers, minLength, required } from "@vuelidate/validators";
|
||||
import useCreateContactUsTicket, {
|
||||
type CreateContactUsTicketRequest,
|
||||
} from "~/composables/api/tickets/useCreateContactUsTicket";
|
||||
import { useToast } from "~/composables/global/useToast";
|
||||
|
||||
// types
|
||||
|
||||
type ContactInfoForm = Omit<CreateContactUsTicketRequest, "type"> & {
|
||||
type: string | undefined;
|
||||
};
|
||||
|
||||
type RequestTypeOption = {
|
||||
title: string;
|
||||
value: CreateContactUsTicketRequest["type"];
|
||||
};
|
||||
|
||||
// state
|
||||
|
||||
useSeoMeta({
|
||||
title: "ارتباط با ما",
|
||||
});
|
||||
|
||||
const contactInfo = ref({
|
||||
name: "",
|
||||
const { addToast } = useToast();
|
||||
|
||||
const contactInfo = ref<ContactInfoForm>({
|
||||
full_name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
requestType: undefined,
|
||||
type: undefined,
|
||||
message: "",
|
||||
});
|
||||
|
||||
const requestTypes = ref(["انتقادات", "پیشنهادات", "پیگیری سفارش"]);
|
||||
const requestTypes = ref<RequestTypeOption[]>([
|
||||
{
|
||||
title: "انتقادات",
|
||||
value: "COMPLAINT",
|
||||
},
|
||||
{
|
||||
title: "پیشنهادات",
|
||||
value: "SUGGESTION",
|
||||
},
|
||||
{
|
||||
title: "پیگیری سفارش",
|
||||
value: "ORDER_FOLLOW_UP",
|
||||
},
|
||||
]);
|
||||
|
||||
const contactWays = ref<{ title: string; ways: { type: "text" | "link"; title: string; path?: string }[] }[]>([
|
||||
{
|
||||
@@ -56,6 +91,97 @@ const contactWays = ref<{ title: string; ways: { type: "text" | "link"; title: s
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const { mutateAsync: createContactUsTicket, isPending: createTicketIsPending } = useCreateContactUsTicket();
|
||||
|
||||
// computed
|
||||
|
||||
const requestTypeOptions = computed(() => requestTypes.value.map((item) => item.title));
|
||||
|
||||
const formRules = computed(() => {
|
||||
return {
|
||||
full_name: {
|
||||
required: helpers.withMessage("فیلد نام و نام خانوادگی الزامی می باشد", required),
|
||||
minLength: helpers.withMessage("فیلد نام و نام خانوادگی حداقل ۳ کرکتر می باشد", minLength(3)),
|
||||
},
|
||||
email: {
|
||||
required: helpers.withMessage("فیلد پست الکترونیکی الزامی می باشد", required),
|
||||
email: helpers.withMessage("پست الکترونیکی وارد شده معتبر نمی باشد", email),
|
||||
},
|
||||
phone: {
|
||||
required: helpers.withMessage("فیلد شماره تلفن الزامی می باشد", required),
|
||||
},
|
||||
type: {
|
||||
required: helpers.withMessage("فیلد نوع درخواست الزامی می باشد", required),
|
||||
},
|
||||
message: {
|
||||
required: helpers.withMessage("فیلد پیغام شما الزامی می باشد", required),
|
||||
minLength: helpers.withMessage("فیلد پیغام شما حداقل ۵ کرکتر می باشد", minLength(5)),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const formValidator$ = useVuelidate(formRules, contactInfo);
|
||||
|
||||
// methods
|
||||
|
||||
const resetForm = () => {
|
||||
contactInfo.value = {
|
||||
full_name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
type: undefined,
|
||||
message: "",
|
||||
};
|
||||
formValidator$.value.$reset();
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await formValidator$.value.$validate();
|
||||
|
||||
if (!formValidator$.value.$errors.length && contactInfo.value.type) {
|
||||
const selectedType = requestTypes.value.find((item) => item.title === contactInfo.value.type)?.value;
|
||||
|
||||
if (!selectedType) {
|
||||
addToast({
|
||||
message: "نوع درخواست معتبر نیست",
|
||||
options: {
|
||||
status: "error",
|
||||
description: "لطفا نوع درخواست را مجدد انتخاب کنید",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await createContactUsTicket({
|
||||
full_name: contactInfo.value.full_name,
|
||||
email: contactInfo.value.email,
|
||||
phone: contactInfo.value.phone,
|
||||
type: selectedType,
|
||||
message: contactInfo.value.message,
|
||||
});
|
||||
|
||||
addToast({
|
||||
message: "پیغام شما با موفقیت ارسال شد",
|
||||
options: {
|
||||
status: "success",
|
||||
description: "پس از بررسی با شما تماس می گیریم",
|
||||
},
|
||||
});
|
||||
|
||||
resetForm();
|
||||
} catch (error) {
|
||||
addToast({
|
||||
message: "خطایی در ارسال پیغام رخ داد",
|
||||
options: {
|
||||
status: "error",
|
||||
description: "لطفا مجدد تلاش کنید",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -71,50 +197,86 @@ const contactWays = ref<{ title: string; ways: { type: "text" | "link"; title: s
|
||||
<div class="w-full flex flex-col-reverse max-lg:-mt-14 lg:flex-row items-start justify-between">
|
||||
<div class="w-full lg:w-8/12 flex flex-col items-start gap-10">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-[.6rem] w-full">
|
||||
<div class="flex flex-col items-start w-full">
|
||||
<DataField
|
||||
label="نام و نام خانوادگی"
|
||||
:required="true"
|
||||
:error="formValidator$.full_name"
|
||||
>
|
||||
<Input
|
||||
class="w-full"
|
||||
v-model="contactInfo.full_name"
|
||||
placeholder="نام و نام خانوادگی"
|
||||
:error="formValidator$.full_name.$error"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col items-start w-full">
|
||||
</DataField>
|
||||
|
||||
<DataField
|
||||
label="پست الکترونیکی"
|
||||
:required="true"
|
||||
:error="formValidator$.email"
|
||||
>
|
||||
<Input
|
||||
class="w-full"
|
||||
v-model="contactInfo.email"
|
||||
placeholder="پست الکترونیکی"
|
||||
:error="formValidator$.email.$error"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col items-start w-full">
|
||||
</DataField>
|
||||
|
||||
<DataField
|
||||
label="شماره تلفن"
|
||||
:required="true"
|
||||
:error="formValidator$.phone"
|
||||
>
|
||||
<Input
|
||||
class="w-full"
|
||||
v-model="contactInfo.phone"
|
||||
placeholder="شماره تلفن"
|
||||
dir="ltr"
|
||||
:error="formValidator$.phone.$error"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col items-start w-full relative">
|
||||
</DataField>
|
||||
|
||||
<DataField
|
||||
label="نوع درخواست"
|
||||
:required="true"
|
||||
:error="formValidator$.type"
|
||||
>
|
||||
<Select
|
||||
v-model="contactInfo.requestType"
|
||||
:options="requestTypes"
|
||||
v-model="contactInfo.type"
|
||||
placeholder="نوع درخواست"
|
||||
:options="requestTypeOptions"
|
||||
class="shrink-0 max-lg:w-[5rem] lg:w-[6.5rem] py-0.5"
|
||||
triggerRootClass="!rounded-xl whitespace-nowrap max-sm:w-full shrink-0 "
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col items-start col-span-1 md:col-span-2 h-[10rem] max-h-[12rem]">
|
||||
</DataField>
|
||||
|
||||
<DataField
|
||||
label="پیغام شما"
|
||||
:required="true"
|
||||
:error="formValidator$.message"
|
||||
class="col-span-1 md:col-span-2"
|
||||
>
|
||||
<textarea
|
||||
v-model="contactInfo.message"
|
||||
placeholder="پیغام شما"
|
||||
class="w-full flex items-center resize-none bg-slate-50 border-slate-200 hover:border-black focus:border-black h-[10rem] max-h-[12rem] text-black justify-between cursor-text transition-all border-[1.5px] gap-3 typo-label-md px-4 py-3.5 selection:bg-slate-100 rounded-100 outline-none flex-1 !text-sm placeholder-slate-400 placeholder:text-xs lg:placeholder:text-sm placeholder:font-normal"
|
||||
:class="[
|
||||
'w-full flex items-center resize-none bg-slate-50 h-[10rem] max-h-[12rem] text-black justify-between cursor-text transition-all border-[1.5px] gap-3 typo-label-md px-4 py-3.5 selection:bg-slate-100 rounded-100 outline-none flex-1 !text-sm placeholder-slate-400 placeholder:text-xs lg:placeholder:text-sm placeholder:font-normal',
|
||||
formValidator$.message.$error
|
||||
? 'border-danger-600'
|
||||
: 'border-slate-200 hover:border-black focus:border-black',
|
||||
]"
|
||||
></textarea>
|
||||
</div>
|
||||
</DataField>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex-center pb-10 border-b border-slate-200">
|
||||
<!-- @click="handleSubmit"
|
||||
:loading="createTicketIsPending || uploadAttachmentIsPending" -->
|
||||
<Button
|
||||
class="rounded-full w-[14rem] h-11"
|
||||
size="md"
|
||||
:loading="createTicketIsPending"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
<!-- <Icon
|
||||
v-if="createTicketIsPending"
|
||||
:name="createTicketIsPending ? 'svg-spinners:3-dots-bounce' : 'bi:send'"
|
||||
/> -->
|
||||
<span>ارسال پیغام</span>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -123,7 +285,7 @@ const contactWays = ref<{ title: string; ways: { type: "text" | "link"; title: s
|
||||
<div
|
||||
v-for="(way, index) in contactWays"
|
||||
:key="index"
|
||||
class="flex flex-col gap-3"
|
||||
class="flex flex-col gap-3"
|
||||
>
|
||||
<span class="text-slate-500 max-lg:text-sm">
|
||||
{{ way.title }}
|
||||
|
||||
Vendored
+2
@@ -112,6 +112,8 @@ declare global {
|
||||
best_deal_price_before_discount: string;
|
||||
best_deal_price_after_discount: string;
|
||||
best_deal_discount: number;
|
||||
customer_pickup_title: null | string;
|
||||
customer_pickup_description: null | string;
|
||||
};
|
||||
|
||||
type ProductListItem = Pick<
|
||||
|
||||
Reference in New Issue
Block a user