Files
S. EsmaeiliandCursor 9ad88af20f feat(admin): editable contact fields in sections and larger logos
Allow address, phone, map, and related site chrome to be edited from contact/settings for all editors, and enlarge brand marks on login, header, and preloader.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-12 18:42:59 +03:30

195 lines
8.1 KiB
Vue
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { Button as AButton, Form as AForm, FormItem as AFormItem, Input as AInput, Textarea as ATextarea } from "ant-design-vue";
import type { Rule } from "ant-design-vue/es/form";
import VuesaxIcon from "~/components/ui/VuesaxIcon.vue";
import Lines from "~/components/ui/Lines.vue";
import Reveal from "~/components/ui/Reveal.vue";
import SectionHead from "~/components/ui/SectionHead.vue";
const site = useSiteStore();
const content = computed(() => site.sections.contact);
const INFO = computed(() => {
const s = site.settings;
return [
{
icon: "location",
label: "نشانی",
value: `${s.address.locality}، ${s.address.street}`,
href: s.mapUrl || undefined,
external: Boolean(s.mapUrl),
},
{ icon: "call", label: "تلفن تماس", value: s.phoneDisplay, ltr: true, href: `tel:${s.phone}` },
{ icon: "sms", label: "ایمیل", value: s.email, ltr: true, href: `mailto:${s.email}` },
{ icon: "clock", label: "ساعات کاری", value: s.workingHours },
];
});
const required = (message: string): Rule[] => [
{ required: true, whitespace: true, message, trigger: "blur" },
];
const rules: Record<string, Rule[]> = {
name: required("لطفاً نام و نام خانوادگی را وارد کنید"),
org: required("لطفاً نام سازمان را وارد کنید"),
phone: required("لطفاً شماره تماس را وارد کنید"),
};
const contact = useContactStore();
</script>
<template>
<section
id="contact"
aria-labelledby="contact-title"
class="relative overflow-hidden border-t border-line py-24 md:py-32"
>
<div class="absolute inset-0 bg-grid-fine opacity-40" />
<div class="relative mx-auto max-w-[1480px] px-5 md:px-8">
<div class="grid gap-14 lg:grid-cols-2">
<!-- ---- info ---- -->
<div>
<SectionHead heading-id="contact-title" v-bind="content.head" />
<div class="mt-10 space-y-1">
<Reveal v-for="(item, i) in INFO" :key="item.label" :delay="0.15 + i * 0.06">
<div class="group flex items-center gap-4 border-b border-line/70 py-4">
<span
class="flex size-11 shrink-0 items-center justify-center rounded-lg border border-line text-accent transition-colors duration-300 group-hover:border-accent/50"
>
<VuesaxIcon :name="item.icon" class="size-5" />
</span>
<div class="min-w-0">
<div class="text-xs text-muted">{{ item.label }}</div>
<component
:is="item.href ? 'a' : 'div'"
:href="item.href"
:target="item.external ? '_blank' : undefined"
:rel="item.external ? 'noopener' : undefined"
:class="`mt-1 block text-sm font-medium ${item.ltr ? 'font-mono tracking-wider' : ''}`"
:dir="item.ltr ? 'ltr' : 'rtl'"
>
{{ item.value }}
</component>
</div>
</div>
</Reveal>
</div>
<Reveal :delay="0.45">
<div class="mt-8 flex items-start gap-4 rounded-xl border border-hazard/30 bg-hazard/5 p-5">
<VuesaxIcon name="lamp-on" class="size-6 shrink-0 text-hazard" />
<div>
<div class="font-bold text-hazard">{{ content.emergencyTitle }}</div>
<p class="mt-1.5 text-sm leading-7 text-muted">
{{ content.emergencyText }}
</p>
</div>
</div>
</Reveal>
</div>
<!-- ---- form ---- -->
<Reveal :delay="0.15">
<div class="rounded-2xl border border-line bg-panel/60 p-6 backdrop-blur md:p-9">
<div v-if="content.formTag" class="font-mono text-xs tracking-[0.25em] text-accent">
{{ content.formTag }}
</div>
<h3 class="mt-3 text-2xl font-black leading-snug">
<Lines :text="content.formTitle" />
</h3>
<div
v-if="contact.status === 'success'"
class="mt-10 flex flex-col items-center rounded-xl border border-emerald-500/30 bg-emerald-500/5 p-10 text-center"
>
<VuesaxIcon name="tick-circle" class="size-14 text-emerald-400" />
<div class="mt-5 text-xl font-black">{{ content.successTitle }}</div>
<p class="mt-2.5 max-w-sm text-sm leading-7 text-muted">
{{ content.successText }}
</p>
<button
type="button"
class="mt-7 rounded-full border border-line px-7 py-3 text-sm transition-colors hover:border-accent hover:text-accent"
@click="contact.reset"
>
{{ content.resetLabel }}
</button>
</div>
<AForm
v-else
class="contact-form"
layout="vertical"
:model="contact.form"
:rules="rules"
hide-required-mark
@finish="contact.submit"
>
<!-- honeypot: hidden from people, bots tend to fill it -->
<div class="absolute -start-[9999px] h-px w-px overflow-hidden" aria-hidden="true">
<label for="f-website">وب‌سایت</label>
<input id="f-website" v-model="contact.form.website" type="text" tabindex="-1" autocomplete="off" />
</div>
<div class="mt-8 grid gap-5 sm:grid-cols-2">
<AFormItem name="name" label="نام و نام خانوادگی *" html-for="f-name">
<AInput id="f-name" v-model:value="contact.form.name" placeholder="مثلاً: علی رضایی" />
</AFormItem>
<AFormItem name="org" label="نام سازمان *" html-for="f-org">
<AInput
id="f-org"
v-model:value="contact.form.org"
placeholder="مثلاً: شرکت پتروشیمی خلیج"
/>
</AFormItem>
<AFormItem class="sm:col-span-2" name="phone" label="شماره تماس *" html-for="f-phone">
<AInput
id="f-phone"
v-model:value="contact.form.phone"
type="tel"
dir="ltr"
placeholder="0912 000 0000"
/>
</AFormItem>
<AFormItem class="sm:col-span-2" name="message" label="شرح درخواست" html-for="f-msg">
<ATextarea
id="f-msg"
v-model:value="contact.form.message"
:rows="4"
placeholder="تجهیزات موردنیاز، تعداد پرسنل یا هر نکته‌ای که لازم است بدانیم..."
/>
</AFormItem>
<p
v-if="contact.status === 'error'"
class="rounded-lg border border-accent/40 bg-accent/10 px-4 py-3 text-sm leading-7 text-accent sm:col-span-2"
role="alert"
>
{{ contact.error }}
</p>
<div class="sm:col-span-2">
<AButton
type="primary"
html-type="submit"
class="contact-submit"
:disabled="contact.status === 'loading'"
>
<template v-if="contact.status === 'loading'">
<VuesaxIcon name="refresh-circle" class="size-5 animate-spin" />
در حال ارسال...
</template>
<template v-else>
<VuesaxIcon name="send-2" class="size-5" />
{{ content.submitLabel }}
</template>
</AButton>
</div>
</div>
</AForm>
</div>
</Reveal>
</div>
</div>
</section>
</template>