Connect the contact us to api and connect the validations
This commit is contained in:
@@ -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",
|
delete_attachment: "/tickets/attachment/delete",
|
||||||
get_one: "/tickets",
|
get_one: "/tickets",
|
||||||
create_message: "/tickets/message/create",
|
create_message: "/tickets/message/create",
|
||||||
|
contact_us_ticket: "/tickets/contact-us/create",
|
||||||
},
|
},
|
||||||
orders: {
|
orders: {
|
||||||
get_all: "/order/all",
|
get_all: "/order/all",
|
||||||
|
|||||||
+186
-24
@@ -1,19 +1,54 @@
|
|||||||
<script setup lang="ts">
|
<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
|
// state
|
||||||
|
|
||||||
useSeoMeta({
|
useSeoMeta({
|
||||||
title: "ارتباط با ما",
|
title: "ارتباط با ما",
|
||||||
});
|
});
|
||||||
|
|
||||||
const contactInfo = ref({
|
const { addToast } = useToast();
|
||||||
name: "",
|
|
||||||
|
const contactInfo = ref<ContactInfoForm>({
|
||||||
|
full_name: "",
|
||||||
email: "",
|
email: "",
|
||||||
phone: "",
|
phone: "",
|
||||||
requestType: undefined,
|
type: undefined,
|
||||||
message: "",
|
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 }[] }[]>([
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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 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="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="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
|
<Input
|
||||||
class="w-full"
|
class="w-full"
|
||||||
|
v-model="contactInfo.full_name"
|
||||||
placeholder="نام و نام خانوادگی"
|
placeholder="نام و نام خانوادگی"
|
||||||
|
:error="formValidator$.full_name.$error"
|
||||||
/>
|
/>
|
||||||
</div>
|
</DataField>
|
||||||
<div class="flex flex-col items-start w-full">
|
|
||||||
|
<DataField
|
||||||
|
label="پست الکترونیکی"
|
||||||
|
:required="true"
|
||||||
|
:error="formValidator$.email"
|
||||||
|
>
|
||||||
<Input
|
<Input
|
||||||
class="w-full"
|
class="w-full"
|
||||||
|
v-model="contactInfo.email"
|
||||||
placeholder="پست الکترونیکی"
|
placeholder="پست الکترونیکی"
|
||||||
|
:error="formValidator$.email.$error"
|
||||||
/>
|
/>
|
||||||
</div>
|
</DataField>
|
||||||
<div class="flex flex-col items-start w-full">
|
|
||||||
|
<DataField
|
||||||
|
label="شماره تلفن"
|
||||||
|
:required="true"
|
||||||
|
:error="formValidator$.phone"
|
||||||
|
>
|
||||||
<Input
|
<Input
|
||||||
class="w-full"
|
class="w-full"
|
||||||
|
v-model="contactInfo.phone"
|
||||||
placeholder="شماره تلفن"
|
placeholder="شماره تلفن"
|
||||||
|
dir="ltr"
|
||||||
|
:error="formValidator$.phone.$error"
|
||||||
/>
|
/>
|
||||||
</div>
|
</DataField>
|
||||||
<div class="flex flex-col items-start w-full relative">
|
|
||||||
|
<DataField
|
||||||
|
label="نوع درخواست"
|
||||||
|
:required="true"
|
||||||
|
:error="formValidator$.type"
|
||||||
|
>
|
||||||
<Select
|
<Select
|
||||||
v-model="contactInfo.requestType"
|
v-model="contactInfo.type"
|
||||||
:options="requestTypes"
|
|
||||||
placeholder="نوع درخواست"
|
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>
|
</DataField>
|
||||||
<div class="flex flex-col items-start col-span-1 md:col-span-2 h-[10rem] max-h-[12rem]">
|
|
||||||
|
<DataField
|
||||||
|
label="پیغام شما"
|
||||||
|
:required="true"
|
||||||
|
:error="formValidator$.message"
|
||||||
|
class="col-span-1 md:col-span-2"
|
||||||
|
>
|
||||||
<textarea
|
<textarea
|
||||||
|
v-model="contactInfo.message"
|
||||||
placeholder="پیغام شما"
|
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>
|
></textarea>
|
||||||
</div>
|
</DataField>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full flex-center pb-10 border-b border-slate-200">
|
<div class="w-full flex-center pb-10 border-b border-slate-200">
|
||||||
<!-- @click="handleSubmit"
|
|
||||||
:loading="createTicketIsPending || uploadAttachmentIsPending" -->
|
|
||||||
<Button
|
<Button
|
||||||
class="rounded-full w-[14rem] h-11"
|
class="rounded-full w-[14rem] h-11"
|
||||||
size="md"
|
size="md"
|
||||||
|
:loading="createTicketIsPending"
|
||||||
|
@click="handleSubmit"
|
||||||
>
|
>
|
||||||
<!-- <Icon
|
|
||||||
v-if="createTicketIsPending"
|
|
||||||
:name="createTicketIsPending ? 'svg-spinners:3-dots-bounce' : 'bi:send'"
|
|
||||||
/> -->
|
|
||||||
<span>ارسال پیغام</span>
|
<span>ارسال پیغام</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -123,7 +285,7 @@ const contactWays = ref<{ title: string; ways: { type: "text" | "link"; title: s
|
|||||||
<div
|
<div
|
||||||
v-for="(way, index) in contactWays"
|
v-for="(way, index) in contactWays"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="flex flex-col gap-3"
|
class="flex flex-col gap-3"
|
||||||
>
|
>
|
||||||
<span class="text-slate-500 max-lg:text-sm">
|
<span class="text-slate-500 max-lg:text-sm">
|
||||||
{{ way.title }}
|
{{ way.title }}
|
||||||
|
|||||||
Reference in New Issue
Block a user