Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ad88af20f | ||
|
|
cad1a8652e | ||
|
|
a8184ed59c | ||
|
|
3dc5f4ae91 | ||
|
|
f64b18aafb |
@@ -1,2 +1,2 @@
|
||||
# loaded by `vite build` (the pre-built dist/ the admin image serves); dev uses http://localhost:3000
|
||||
VITE_SITE_URL=https://imenpishgaman.ir
|
||||
VITE_SITE_URL=http://imenpishgaman.ir
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
import{b as e}from"./VuesaxIcon-B2OgulS6.js";import{Et as t,It as n,Nt as r,Ot as i,Tt as a,_t as o,dt as s,i as c,kt as l,lt as u,ot as d,r as f,yt as p}from"./index-BuGK6ptM.js";var m=Symbol(`form-errors`),h=e=>Object.fromEntries(e.map(e=>[e.path,e.message]));function g(r,i){let a=n(structuredClone(i)),o=n({});t(m,o);function s(){let t=r.safeParse(a.value);return t.success?(o.value={},t.data):(o.value=h(e(t.error)),d.error(`لطفاً خطاهای فرم را برطرف کنید`),null)}function l(e){let t=f(e).issues;t?.length&&(o.value=h(t)),d.error(c(e))}function u(e){a.value=structuredClone(e),o.value={}}return{model:a,errors:o,validate:s,handleError:l,reset:u}}var _=o({__name:`FormField`,props:{path:{},label:{},hint:{},required:{type:Boolean}},setup(e){let t=e,n=p(m,null),o=u(()=>n?.value[t.path]);return(t,n)=>{let c=l(`a-form-item`);return a(),s(c,{label:e.label,required:e.required,"validate-status":o.value?`error`:void 0,help:o.value||e.hint},{default:r(()=>[i(t.$slots,`default`)]),_:3},8,[`label`,`required`,`validate-status`,`help`])}}});export{g as n,_ as t};
|
||||
import{b as e}from"./VuesaxIcon-UCFGkXBv.js";import{Et as t,It as n,Nt as r,Ot as i,Tt as a,_t as o,dt as s,i as c,kt as l,lt as u,ot as d,r as f,yt as p}from"./index-1U5l_fLl.js";var m=Symbol(`form-errors`),h=e=>Object.fromEntries(e.map(e=>[e.path,e.message]));function g(r,i){let a=n(structuredClone(i)),o=n({});t(m,o);function s(){let t=r.safeParse(a.value);return t.success?(o.value={},t.data):(o.value=h(e(t.error)),d.error(`لطفاً خطاهای فرم را برطرف کنید`),null)}function l(e){let t=f(e).issues;t?.length&&(o.value=h(t)),d.error(c(e))}function u(e){a.value=structuredClone(e),o.value={}}return{model:a,errors:o,validate:s,handleError:l,reset:u}}var _=o({__name:`FormField`,props:{path:{},label:{},hint:{},required:{type:Boolean}},setup(e){let t=e,n=p(m,null),o=u(()=>n?.value[t.path]);return(t,n)=>{let c=l(`a-form-item`);return a(),s(c,{label:e.label,required:e.required,"validate-status":o.value?`error`:void 0,help:o.value||e.hint},{default:r(()=>[i(t.$slots,`default`)]),_:3},8,[`label`,`required`,`validate-status`,`help`])}}});export{g as n,_ as t};
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
import{t as e,y as t}from"./VuesaxIcon-B2OgulS6.js";import{Bt as n,Lt as r,Nt as i,Tt as a,_t as o,bt as s,dt as c,gt as l,jt as u,kt as d,pt as f,ut as p}from"./index-BuGK6ptM.js";var m={class:`flex items-center gap-2`},h={class:`flex size-9 shrink-0 items-center justify-center rounded-lg border border-gray-200 bg-gray-50 text-[#ff5a00]`},g={key:1,class:`text-xs text-gray-400`},_={class:`flex items-center gap-2.5 py-0.5`,dir:`ltr`},v={class:`text-xs font-mono`},y=o({__name:`IconSelect`,props:s({allowClear:{type:Boolean}},{modelValue:{required:!0},modelModifiers:{}}),emits:[`update:modelValue`],setup(o){let s=u(o,`modelValue`),y=t.map(e=>({value:e,label:e}));return(t,u)=>{let b=d(`a-select`);return a(),f(`div`,m,[p(`span`,h,[s.value?(a(),c(e,{key:0,name:s.value,size:20},null,8,[`name`])):(a(),f(`span`,g,`---`))]),l(b,{value:s.value,"onUpdate:value":u[0]||=e=>s.value=e,class:`min-w-0 flex-1`,"show-search":``,"allow-clear":o.allowClear,options:r(y),placeholder:`انتخاب آیکون`},{option:i(({value:t})=>[p(`span`,_,[l(e,{name:t,size:18,class:`text-orange-600`},null,8,[`name`]),p(`span`,v,n(t),1)])]),_:1},8,[`value`,`allow-clear`,`options`])])}}});export{y as t};
|
||||
import{t as e,y as t}from"./VuesaxIcon-UCFGkXBv.js";import{Bt as n,Lt as r,Nt as i,Tt as a,_t as o,bt as s,dt as c,gt as l,jt as u,kt as d,pt as f,ut as p}from"./index-1U5l_fLl.js";var m={class:`flex items-center gap-2`},h={class:`flex size-9 shrink-0 items-center justify-center rounded-lg border border-gray-200 bg-gray-50 text-[#ff5a00]`},g={key:1,class:`text-xs text-gray-400`},_={class:`flex items-center gap-2.5 py-0.5`,dir:`ltr`},v={class:`text-xs font-mono`},y=o({__name:`IconSelect`,props:s({allowClear:{type:Boolean}},{modelValue:{required:!0},modelModifiers:{}}),emits:[`update:modelValue`],setup(o){let s=u(o,`modelValue`),y=t.map(e=>({value:e,label:e}));return(t,u)=>{let b=d(`a-select`);return a(),f(`div`,m,[p(`span`,h,[s.value?(a(),c(e,{key:0,name:s.value,size:20},null,8,[`name`])):(a(),f(`span`,g,`---`))]),l(b,{value:s.value,"onUpdate:value":u[0]||=e=>s.value=e,class:`min-w-0 flex-1`,"show-search":``,"allow-clear":o.allowClear,options:r(y),placeholder:`انتخاب آیکون`},{option:i(({value:t})=>[p(`span`,_,[l(e,{name:t,size:18,class:`text-orange-600`},null,8,[`name`]),p(`span`,v,n(t),1)])]),_:1},8,[`value`,`allow-clear`,`options`])])}}});export{y as t};
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
import{c as e,n as t,t as n}from"./VuesaxIcon-B2OgulS6.js";import{It as r,Lt as i,Nt as a,Tt as o,_t as s,a as c,gt as l,ht as u,kt as d,mt as f,o as p,pt as m,st as h,t as g,ut as _}from"./index-BuGK6ptM.js";import"./logo-BuUh917n.js";import{n as v,t as y}from"./FormField-CiVT7mqC.js";var b={class:`login-page relative flex min-h-screen overflow-hidden text-[#edede6]`},x={class:`relative z-10 mx-auto grid w-full max-w-[1120px] flex-1 items-center gap-10 px-5 py-12 md:px-8 lg:grid-cols-12 lg:gap-16`},S={class:`login-fade-3 lg:col-span-6`},C={class:`login-panel w-full max-w-md border border-[#2a3d63]/70 bg-[#1a2744]/80 p-7 backdrop-blur-md md:p-8 lg:me-auto`},w=t(s({__name:`LoginPage`,setup(t){let s=g(),w=p(),T=c(),{model:E,validate:D,handleError:O}=v(e,{phone:``,password:``}),k=r(!1);async function A(){let e=D();if(e){k.value=!0;try{await s.login(e);let t=typeof T.query.redirect==`string`?T.query.redirect:`/`;await w.replace(t.startsWith(`/`)?t:`/`)}catch(e){O(e)}finally{k.value=!1}}}return(e,t)=>{let r=d(`a-input`),s=d(`a-input-password`),c=d(`a-button`),p=d(`a-form`);return o(),m(`div`,b,[t[5]||=f(`<div class="pointer-events-none absolute inset-0 bg-[#0b1224]" data-v-2d323d25></div><div class="login-grid pointer-events-none absolute inset-0 opacity-50" data-v-2d323d25></div><div class="pointer-events-none absolute -start-24 top-1/4 size-[420px] rounded-full bg-[#ff5a00]/15 blur-[120px]" data-v-2d323d25></div><div class="pointer-events-none absolute -end-16 bottom-0 size-[360px] rounded-full bg-[#ffb800]/10 blur-[100px]" data-v-2d323d25></div><div class="login-tape pointer-events-none absolute bottom-0 end-0 z-10 h-48 w-1.5 opacity-70" data-v-2d323d25></div><div class="login-noise pointer-events-none absolute inset-0 z-[5]" data-v-2d323d25></div>`,6),_(`div`,x,[_(`div`,S,[_(`div`,C,[t[3]||=_(`div`,{class:`mb-7 text-center`},[_(`div`,{class:`font-mono text-[10px] tracking-[0.28em] text-[#8e9ab0]`},`SIGN IN`),_(`h2`,{class:`mt-2 text-xl font-bold text-[#edede6]`},`ورود به پنل`),_(`p`,{class:`mt-1.5 text-sm text-[#8e9ab0]`},`شماره موبایل و رمز عبور خود را وارد کنید`)],-1),l(p,{class:`login-form`,layout:`vertical`,onSubmit:h(A,[`prevent`])},{default:a(()=>[l(y,{path:`phone`,label:`شماره موبایل`},{default:a(()=>[l(r,{value:i(E).phone,"onUpdate:value":t[0]||=e=>i(E).phone=e,size:`large`,class:`ltr-input`,autocomplete:`tel`,autofocus:``,inputmode:`tel`,placeholder:`09121234567`},{prefix:a(()=>[l(n,{name:`mobile`,size:16,class:`text-[#8e9ab0]`})]),_:1},8,[`value`])]),_:1}),l(y,{path:`password`,label:`رمز عبور`},{default:a(()=>[l(s,{value:i(E).password,"onUpdate:value":t[1]||=e=>i(E).password=e,size:`large`,class:`ltr-input`,autocomplete:`current-password`,placeholder:`••••••••`},{prefix:a(()=>[l(n,{name:`lock`,size:16,class:`text-[#8e9ab0]`})]),_:1},8,[`value`])]),_:1}),l(c,{type:`primary`,"html-type":`submit`,size:`large`,block:``,loading:k.value,class:`!mt-2 !h-12 !rounded-full !font-bold !shadow-[0_8px_32px_rgba(255,90,0,0.35)]`},{default:a(()=>[...t[2]||=[u(` ورود به پنل `,-1)]]),_:1},8,[`loading`])]),_:1})])]),t[4]||=f(`<div class="login-fade-1 lg:col-span-6" data-v-2d323d25><div class="inline-flex items-center gap-2.5 rounded-full border border-[#ff5a00]/30 bg-[#ff5a00]/10 px-4 py-2 backdrop-blur" data-v-2d323d25><span class="size-1.5 animate-pulse rounded-full bg-[#ff5a00]" data-v-2d323d25></span><span class="text-xs font-medium text-[#ff5a00]" data-v-2d323d25>پنل مدیریت ایمن پیشگامان</span></div><div class="mt-8 flex items-center gap-4" data-v-2d323d25><span class="flex size-14 items-center justify-center rounded-xl bg-white shadow-[0_0_28px_rgba(10,30,92,0.45)]" data-v-2d323d25><img src="/logo.png" alt="ایمن پیشگامان" class="size-9 object-contain" data-v-2d323d25></span><div class="leading-tight" data-v-2d323d25><div class="text-2xl font-black tracking-tight md:text-3xl" data-v-2d323d25>ایمن پیشگامان</div><div class="mt-1 font-mono text-[10px] tracking-[0.32em] text-[#8e9ab0]" data-v-2d323d25> IMEN PISHGAMAN CO. </div></div></div><h1 class="mt-8 text-3xl font-black leading-[1.25] md:text-5xl md:leading-[1.2]" data-v-2d323d25> ورود امن <span class="mt-2 inline-block -rotate-1 rounded-lg bg-[#ffb800] px-3 py-1 text-[#0b1224] shadow-[0_10px_40px_rgba(255,184,0,0.22)]" data-v-2d323d25> به سامانه </span></h1><p class="mt-6 max-w-md text-sm leading-8 text-[#edede6]/70 md:text-base md:leading-9" data-v-2d323d25> مدیریت محصولات، محتوا و پیامهای سایت — فقط برای کاربران مجاز. </p></div>`,1)])])}}}),[[`__scopeId`,`data-v-2d323d25`]]);export{w as default};
|
||||
import{c as e,n as t,t as n}from"./VuesaxIcon-UCFGkXBv.js";import{It as r,Lt as i,Nt as a,Tt as o,_t as s,a as c,gt as l,ht as u,kt as d,mt as f,o as p,pt as m,st as h,t as g,ut as _}from"./index-1U5l_fLl.js";import"./logo-BuUh917n.js";import{n as v,t as y}from"./FormField-DbkYgDsg.js";var b={class:`login-page relative flex min-h-screen overflow-hidden text-[#edede6]`},x={class:`relative z-10 mx-auto grid w-full max-w-[1120px] flex-1 items-center gap-10 px-5 py-12 md:px-8 lg:grid-cols-12 lg:gap-16`},S={class:`login-fade-3 lg:col-span-6`},C={class:`login-panel w-full max-w-md border border-[#2a3d63]/70 bg-[#1a2744]/80 p-7 backdrop-blur-md md:p-8 lg:me-auto`},w=t(s({__name:`LoginPage`,setup(t){let s=g(),w=p(),T=c(),{model:E,validate:D,handleError:O}=v(e,{phone:``,password:``}),k=r(!1);async function A(){let e=D();if(e){k.value=!0;try{await s.login(e);let t=typeof T.query.redirect==`string`?T.query.redirect:`/`;await w.replace(t.startsWith(`/`)?t:`/`)}catch(e){O(e)}finally{k.value=!1}}}return(e,t)=>{let r=d(`a-input`),s=d(`a-input-password`),c=d(`a-button`),p=d(`a-form`);return o(),m(`div`,b,[t[5]||=f(`<div class="pointer-events-none absolute inset-0 bg-[#0b1224]" data-v-2d323d25></div><div class="login-grid pointer-events-none absolute inset-0 opacity-50" data-v-2d323d25></div><div class="pointer-events-none absolute -start-24 top-1/4 size-[420px] rounded-full bg-[#ff5a00]/15 blur-[120px]" data-v-2d323d25></div><div class="pointer-events-none absolute -end-16 bottom-0 size-[360px] rounded-full bg-[#ffb800]/10 blur-[100px]" data-v-2d323d25></div><div class="login-tape pointer-events-none absolute bottom-0 end-0 z-10 h-48 w-1.5 opacity-70" data-v-2d323d25></div><div class="login-noise pointer-events-none absolute inset-0 z-[5]" data-v-2d323d25></div>`,6),_(`div`,x,[_(`div`,S,[_(`div`,C,[t[3]||=_(`div`,{class:`mb-7 text-center`},[_(`div`,{class:`font-mono text-[10px] tracking-[0.28em] text-[#8e9ab0]`},`SIGN IN`),_(`h2`,{class:`mt-2 text-xl font-bold text-[#edede6]`},`ورود به پنل`),_(`p`,{class:`mt-1.5 text-sm text-[#8e9ab0]`},`شماره موبایل و رمز عبور خود را وارد کنید`)],-1),l(p,{class:`login-form`,layout:`vertical`,onSubmit:h(A,[`prevent`])},{default:a(()=>[l(y,{path:`phone`,label:`شماره موبایل`},{default:a(()=>[l(r,{value:i(E).phone,"onUpdate:value":t[0]||=e=>i(E).phone=e,size:`large`,class:`ltr-input`,autocomplete:`tel`,autofocus:``,inputmode:`tel`,placeholder:`09121234567`},{prefix:a(()=>[l(n,{name:`mobile`,size:16,class:`text-[#8e9ab0]`})]),_:1},8,[`value`])]),_:1}),l(y,{path:`password`,label:`رمز عبور`},{default:a(()=>[l(s,{value:i(E).password,"onUpdate:value":t[1]||=e=>i(E).password=e,size:`large`,class:`ltr-input`,autocomplete:`current-password`,placeholder:`••••••••`},{prefix:a(()=>[l(n,{name:`lock`,size:16,class:`text-[#8e9ab0]`})]),_:1},8,[`value`])]),_:1}),l(c,{type:`primary`,"html-type":`submit`,size:`large`,block:``,loading:k.value,class:`!mt-2 !h-12 !rounded-full !font-bold !shadow-[0_8px_32px_rgba(255,90,0,0.35)]`},{default:a(()=>[...t[2]||=[u(` ورود به پنل `,-1)]]),_:1},8,[`loading`])]),_:1})])]),t[4]||=f(`<div class="login-fade-1 lg:col-span-6" data-v-2d323d25><div class="inline-flex items-center gap-2.5 rounded-full border border-[#ff5a00]/30 bg-[#ff5a00]/10 px-4 py-2 backdrop-blur" data-v-2d323d25><span class="size-1.5 animate-pulse rounded-full bg-[#ff5a00]" data-v-2d323d25></span><span class="text-xs font-medium text-[#ff5a00]" data-v-2d323d25>پنل مدیریت ایمن پیشگامان</span></div><div class="mt-8 flex items-center gap-4" data-v-2d323d25><span class="flex size-14 items-center justify-center rounded-xl bg-white shadow-[0_0_28px_rgba(10,30,92,0.45)]" data-v-2d323d25><img src="/logo.png" alt="ایمن پیشگامان" class="size-9 object-contain" data-v-2d323d25></span><div class="leading-tight" data-v-2d323d25><div class="text-2xl font-black tracking-tight md:text-3xl" data-v-2d323d25>ایمن پیشگامان</div><div class="mt-1 font-mono text-[10px] tracking-[0.32em] text-[#8e9ab0]" data-v-2d323d25> IMEN PISHGAMAN CO. </div></div></div><h1 class="mt-8 text-3xl font-black leading-[1.25] md:text-5xl md:leading-[1.2]" data-v-2d323d25> ورود امن <span class="mt-2 inline-block -rotate-1 rounded-lg bg-[#ffb800] px-3 py-1 text-[#0b1224] shadow-[0_10px_40px_rgba(255,184,0,0.22)]" data-v-2d323d25> به سامانه </span></h1><p class="mt-6 max-w-md text-sm leading-8 text-[#edede6]/70 md:text-base md:leading-9" data-v-2d323d25> مدیریت محصولات، محتوا و پیامهای سایت — فقط برای کاربران مجاز. </p></div>`,1)])])}}}),[[`__scopeId`,`data-v-2d323d25`]]);export{w as default};
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
import{n as e,t}from"./VuesaxIcon-B2OgulS6.js";import{Bt as n,Nt as r,Ot as i,Tt as a,_t as o,dt as s,ft as c,gt as l,kt as u,pt as d,ut as f}from"./index-BuGK6ptM.js";var p={class:`page-header`},m={class:`page-header__main`},h={class:`min-w-0`},g={class:`page-header__title`},_={key:0,class:`page-header__subtitle`},v={key:0,class:`page-header__actions`},y=e(o({__name:`PageHeader`,props:{title:{},subtitle:{},back:{}},setup(e){return(o,y)=>{let b=u(`RouterLink`);return a(),d(`div`,p,[f(`div`,m,[e.back?(a(),s(b,{key:0,to:e.back,class:`page-header__back`,title:`بازگشت`},{default:r(()=>[l(t,{name:`arrow-right`,size:18})]),_:1},8,[`to`])):c(``,!0),f(`div`,h,[f(`h1`,g,n(e.title),1),e.subtitle?(a(),d(`p`,_,n(e.subtitle),1)):c(``,!0)])]),o.$slots.default?(a(),d(`div`,v,[i(o.$slots,`default`,{},void 0,!0)])):c(``,!0)])}}}),[[`__scopeId`,`data-v-c0b72af8`]]);export{y as t};
|
||||
import{n as e,t}from"./VuesaxIcon-UCFGkXBv.js";import{Bt as n,Nt as r,Ot as i,Tt as a,_t as o,dt as s,ft as c,gt as l,kt as u,pt as d,ut as f}from"./index-1U5l_fLl.js";var p={class:`page-header`},m={class:`page-header__main`},h={class:`min-w-0`},g={class:`page-header__title`},_={key:0,class:`page-header__subtitle`},v={key:0,class:`page-header__actions`},y=e(o({__name:`PageHeader`,props:{title:{},subtitle:{},back:{}},setup(e){return(o,y)=>{let b=u(`RouterLink`);return a(),d(`div`,p,[f(`div`,m,[e.back?(a(),s(b,{key:0,to:e.back,class:`page-header__back`,title:`بازگشت`},{default:r(()=>[l(t,{name:`arrow-right`,size:18})]),_:1},8,[`to`])):c(``,!0),f(`div`,h,[f(`h1`,g,n(e.title),1),e.subtitle?(a(),d(`p`,_,n(e.subtitle),1)):c(``,!0)])]),o.$slots.default?(a(),d(`div`,v,[i(o.$slots,`default`,{},void 0,!0)])):c(``,!0)])}}}),[[`__scopeId`,`data-v-c0b72af8`]]);export{y as t};
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{t as e,v as t}from"./VuesaxIcon-B2OgulS6.js";import{Bt as n,Ct as r,Dt as i,It as a,Lt as o,Mt as s,Nt as c,Rt as l,Tt as u,_t as d,a as f,bt as p,ct as m,dt as h,ft as g,gt as _,ht as v,i as y,jt as b,kt as x,lt as S,n as C,o as ee,ot as w,pt as T,st as te,ut as E}from"./index-BuGK6ptM.js";import{t as D}from"./format-B9MYDQJN.js";import{t as ne}from"./PageHeader-3Ryvg2HD.js";import{n as re,t as O}from"./FormField-CiVT7mqC.js";import{n as ie,r as ae,t as oe}from"./ListEditor-C8F91lSJ.js";import{t as se}from"./slug-Flyc3fOh.js";var ce=d({__name:`LinesInput`,props:p({placeholder:{},rows:{}},{modelValue:{required:!0},modelModifiers:{}}),emits:[`update:modelValue`],setup(e){let t=b(e,`modelValue`),n=e=>e.split(`
|
||||
import{t as e,v as t}from"./VuesaxIcon-UCFGkXBv.js";import{Bt as n,Ct as r,Dt as i,It as a,Lt as o,Mt as s,Nt as c,Rt as l,Tt as u,_t as d,a as f,bt as p,ct as m,dt as h,ft as g,gt as _,ht as v,i as y,jt as b,kt as x,lt as S,n as C,o as ee,ot as w,pt as T,st as te,ut as E}from"./index-1U5l_fLl.js";import{t as D}from"./format-BwvWvVfG.js";import{t as ne}from"./PageHeader-C9oHYjmE.js";import{n as re,t as O}from"./FormField-DbkYgDsg.js";import{n as ie,r as ae,t as oe}from"./ListEditor-xwdh5hs-.js";import{t as se}from"./slug-Flyc3fOh.js";var ce=d({__name:`LinesInput`,props:p({placeholder:{},rows:{}},{modelValue:{required:!0},modelModifiers:{}}),emits:[`update:modelValue`],setup(e){let t=b(e,`modelValue`),n=e=>e.split(`
|
||||
`).map(e=>e.trim()).filter(Boolean),r=a(t.value.join(`
|
||||
`));s(t,e=>{n(r.value).join(`
|
||||
`)!==e.join(`
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
import{o as e,s as t}from"./VuesaxIcon-B2OgulS6.js";import{Bt as n,It as r,Lt as i,Nt as a,Tt as o,_t as s,ct as c,gt as l,ht as u,kt as d,n as f,ot as p,pt as m,st as h,t as g,ut as _}from"./index-BuGK6ptM.js";import{t as v}from"./PageHeader-3Ryvg2HD.js";import{n as y,t as b}from"./FormField-CiVT7mqC.js";var x={class:`grid max-w-4xl gap-4 md:gap-6 md:grid-cols-2`},S={class:`flex items-center gap-3.5 mb-4 pb-4 border-b border-gray-100`},C={class:`flex size-14 items-center justify-center rounded-2xl bg-gradient-to-tr from-[#ff5a00] to-amber-500 text-xl font-black text-white shadow-md shadow-orange-500/20`},w={class:`text-base font-bold text-gray-900`},T={class:`mt-0.5 text-xs font-mono text-gray-500`,dir:`ltr`},E={dir:`ltr`,class:`font-mono`},D=s({__name:`ProfilePage`,setup(s){let D=g(),{model:O,validate:k,handleError:A,reset:j}=y(t,{currentPassword:``,newPassword:``}),M=r(!1);async function N(){let e=k();if(e){M.value=!0;try{await f(`/admin/auth/change-password`,{method:`POST`,body:e}),p.success(`رمز عبور تغییر کرد`),j({currentPassword:``,newPassword:``})}catch(e){A(e)}finally{M.value=!1}}}return(t,r)=>{let s=d(`a-descriptions-item`),f=d(`a-tag`),p=d(`a-descriptions`),g=d(`a-card`),y=d(`a-input-password`),k=d(`a-button`),A=d(`a-form`);return o(),m(c,null,[l(v,{title:`حساب کاربری`,subtitle:`مشخصات فردی، نقش دسترسی و تغییر رمز عبور ورود به سامانه`}),_(`div`,x,[l(g,{title:`مشخصات کاربری`},{default:a(()=>[_(`div`,S,[_(`div`,C,n(i(D).user?.name?.slice(0,1)||`U`),1),_(`div`,null,[_(`div`,w,n(i(D).user?.name),1),_(`div`,T,n(i(D).user?.phone),1)])]),l(p,{column:1,size:`small`},{default:a(()=>[l(s,{label:`نام`},{default:a(()=>[u(n(i(D).user?.name),1)]),_:1}),l(s,{label:`موبایل`},{default:a(()=>[_(`span`,E,n(i(D).user?.phone),1)]),_:1}),l(s,{label:`نقش دسترسی`},{default:a(()=>[l(f,{color:i(D).user?.role===`ADMIN`?`orange`:`blue`},{default:a(()=>[u(n(i(D).user&&i(e)[i(D).user.role]),1)]),_:1},8,[`color`])]),_:1})]),_:1})]),_:1}),l(g,{title:`تغییر رمز عبور`},{default:a(()=>[l(A,{layout:`vertical`,onSubmit:h(N,[`prevent`])},{default:a(()=>[l(b,{path:`currentPassword`,label:`رمز فعلی`,required:``},{default:a(()=>[l(y,{value:i(O).currentPassword,"onUpdate:value":r[0]||=e=>i(O).currentPassword=e,class:`ltr-input h-10`,autocomplete:`current-password`},null,8,[`value`])]),_:1}),l(b,{path:`newPassword`,label:`رمز جدید`,hint:`حداقل ۸ کاراکتر`,required:``},{default:a(()=>[l(y,{value:i(O).newPassword,"onUpdate:value":r[1]||=e=>i(O).newPassword=e,class:`ltr-input h-10`,autocomplete:`new-password`},null,8,[`value`])]),_:1}),l(k,{type:`primary`,"html-type":`submit`,loading:M.value,class:`w-full md:w-auto h-10 !font-bold`},{default:a(()=>[...r[2]||=[u(` تغییر رمز ورود `,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1})])],64)}}});export{D as default};
|
||||
import{o as e,s as t}from"./VuesaxIcon-UCFGkXBv.js";import{Bt as n,It as r,Lt as i,Nt as a,Tt as o,_t as s,ct as c,gt as l,ht as u,kt as d,n as f,ot as p,pt as m,st as h,t as g,ut as _}from"./index-1U5l_fLl.js";import{t as v}from"./PageHeader-C9oHYjmE.js";import{n as y,t as b}from"./FormField-DbkYgDsg.js";var x={class:`grid max-w-4xl gap-4 md:gap-6 md:grid-cols-2`},S={class:`flex items-center gap-3.5 mb-4 pb-4 border-b border-gray-100`},C={class:`flex size-14 items-center justify-center rounded-2xl bg-gradient-to-tr from-[#ff5a00] to-amber-500 text-xl font-black text-white shadow-md shadow-orange-500/20`},w={class:`text-base font-bold text-gray-900`},T={class:`mt-0.5 text-xs font-mono text-gray-500`,dir:`ltr`},E={dir:`ltr`,class:`font-mono`},D=s({__name:`ProfilePage`,setup(s){let D=g(),{model:O,validate:k,handleError:A,reset:j}=y(t,{currentPassword:``,newPassword:``}),M=r(!1);async function N(){let e=k();if(e){M.value=!0;try{await f(`/admin/auth/change-password`,{method:`POST`,body:e}),p.success(`رمز عبور تغییر کرد`),j({currentPassword:``,newPassword:``})}catch(e){A(e)}finally{M.value=!1}}}return(t,r)=>{let s=d(`a-descriptions-item`),f=d(`a-tag`),p=d(`a-descriptions`),g=d(`a-card`),y=d(`a-input-password`),k=d(`a-button`),A=d(`a-form`);return o(),m(c,null,[l(v,{title:`حساب کاربری`,subtitle:`مشخصات فردی، نقش دسترسی و تغییر رمز عبور ورود به سامانه`}),_(`div`,x,[l(g,{title:`مشخصات کاربری`},{default:a(()=>[_(`div`,S,[_(`div`,C,n(i(D).user?.name?.slice(0,1)||`U`),1),_(`div`,null,[_(`div`,w,n(i(D).user?.name),1),_(`div`,T,n(i(D).user?.phone),1)])]),l(p,{column:1,size:`small`},{default:a(()=>[l(s,{label:`نام`},{default:a(()=>[u(n(i(D).user?.name),1)]),_:1}),l(s,{label:`موبایل`},{default:a(()=>[_(`span`,E,n(i(D).user?.phone),1)]),_:1}),l(s,{label:`نقش دسترسی`},{default:a(()=>[l(f,{color:i(D).user?.role===`ADMIN`?`orange`:`blue`},{default:a(()=>[u(n(i(D).user&&i(e)[i(D).user.role]),1)]),_:1},8,[`color`])]),_:1})]),_:1})]),_:1}),l(g,{title:`تغییر رمز عبور`},{default:a(()=>[l(A,{layout:`vertical`,onSubmit:h(N,[`prevent`])},{default:a(()=>[l(b,{path:`currentPassword`,label:`رمز فعلی`,required:``},{default:a(()=>[l(y,{value:i(O).currentPassword,"onUpdate:value":r[0]||=e=>i(O).currentPassword=e,class:`ltr-input h-10`,autocomplete:`current-password`},null,8,[`value`])]),_:1}),l(b,{path:`newPassword`,label:`رمز جدید`,hint:`حداقل ۸ کاراکتر`,required:``},{default:a(()=>[l(y,{value:i(O).newPassword,"onUpdate:value":r[1]||=e=>i(O).newPassword=e,class:`ltr-input h-10`,autocomplete:`new-password`},null,8,[`value`])]),_:1}),l(k,{type:`primary`,"html-type":`submit`,loading:M.value,class:`w-full md:w-auto h-10 !font-bold`},{default:a(()=>[...r[2]||=[u(` تغییر رمز ورود `,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1})])],64)}}});export{D as default};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
import{d as e,f as t}from"./VuesaxIcon-B2OgulS6.js";import{Bt as n,Ct as r,Dt as i,Ft as a,It as o,Lt as s,Nt as c,Rt as l,Tt as u,_t as d,ct as f,dt as p,ft as m,gt as h,ht as g,i as _,kt as v,n as y,o as b,ot as x,pt as S,t as C,ut as w}from"./index-BuGK6ptM.js";import{a as T}from"./format-B9MYDQJN.js";import{t as E}from"./PageHeader-3Ryvg2HD.js";var D={class:`grid gap-4 sm:grid-cols-2 xl:grid-cols-3`},O={class:`flex items-center justify-between gap-2`},k=[`onClick`],A={class:`font-bold text-gray-800 group-hover:text-orange-600 transition-colors`},j={key:0,class:`flex items-center flex-shrink-0`},M=[`onClick`],N=[`onClick`],P={key:0},F=d({__name:`SectionsPage`,setup(d){let F=b(),I=C(),L=e.filter(e=>e!==`settings`||I.isAdmin),R=o(!0),z=a({hero:!0,tape:!0,stats:!0,productsIntro:!0,about:!0,services:!0,standards:!0,industries:!0,testimonials:!0,contact:!0,footer:!0}),B=a({}),V=a({});function H(e){return z[e]!==!1}function U(e){return B[e]??null}async function W(){R.value=!0;try{let e=await y(`/admin/sections`);for(let t of e)z[t.key]=t.enabled!==!1,B[t.key]=t.updatedAt}catch(e){x.error(_(e))}finally{R.value=!1}}async function G(e,n){if(e===`settings`)return;let r=z[e]!==!1;z[e]=n,V[e]=!0;try{let r=await y(`/admin/sections/${e}/visibility`,{method:`PATCH`,body:{enabled:n}});z[e]=r.enabled!==!1,B[e]=r.updatedAt,x.success(`وضعیت «${t[e].title}» به ${n?`نمایش`:`عدم نمایش`} تغییر یافت`)}catch(t){z[e]=r,x.error(_(t))}finally{V[e]=!1}}function K(e){F.push({name:`section-edit`,params:{key:e}})}return r(()=>{W()}),(e,r)=>{let a=v(`a-tag`),o=v(`a-switch`),d=v(`a-card`),_=v(`a-spin`);return u(),S(f,null,[h(E,{title:`بخشهای سایت`,subtitle:`متنها، تصاویر و فهرستهای صفحه اصلی؛ تنظیم وضعیت نمایش و ویرایش محتوا`}),h(_,{spinning:R.value},{default:c(()=>[w(`div`,D,[(u(!0),S(f,null,i(s(L),e=>(u(),S(`div`,{key:e,class:`block group`},[h(d,{hoverable:``,class:l([`h-full flex flex-col justify-between transition-all`,{"opacity-75 bg-gray-50/60":e!==`settings`&&!H(e)}])},{default:c(()=>[w(`div`,null,[w(`div`,O,[w(`div`,{class:`flex items-center gap-2 cursor-pointer flex-1`,onClick:t=>K(e)},[w(`span`,A,n(s(t)[e].title),1),e===`settings`?(u(),p(a,{key:0,color:`blue`},{default:c(()=>[...r[0]||=[g(`تنظیمات پایه`,-1)]]),_:1})):H(e)?m(``,!0):(u(),p(a,{key:1,color:`error`},{default:c(()=>[...r[1]||=[g(`مخفی`,-1)]]),_:1}))],8,k),e===`settings`?m(``,!0):(u(),S(`div`,j,[h(o,{checked:H(e),loading:V[e],"checked-children":`نمایش`,"un-checked-children":`مخفی`,onChange:t=>G(e,!!t)},null,8,[`checked`,`loading`,`onChange`])]))]),w(`p`,{class:`mt-2 text-sm leading-6 text-gray-500 cursor-pointer`,onClick:t=>K(e)},n(s(t)[e].desc),9,M)]),w(`div`,{class:`mt-4 flex items-center justify-between border-t border-gray-100 pt-3 text-xs text-gray-400 cursor-pointer`,onClick:t=>K(e)},[r[2]||=w(`span`,{class:`text-primary font-medium group-hover:underline`},`ویرایش بخش ←`,-1),U(e)?(u(),S(`span`,P,`آخرین تغییر: `+n(s(T)(U(e))),1)):m(``,!0)],8,N)]),_:2},1032,[`class`])]))),128))])]),_:1},8,[`spinning`])],64)}}});export{F as default};
|
||||
import{d as e,f as t}from"./VuesaxIcon-UCFGkXBv.js";import{Bt as n,Ct as r,Dt as i,Ft as a,It as o,Lt as s,Nt as c,Rt as l,Tt as u,_t as d,ct as f,dt as p,ft as m,gt as h,ht as g,i as _,kt as v,n as y,o as b,ot as x,pt as S,t as C,ut as w}from"./index-1U5l_fLl.js";import{a as T}from"./format-BwvWvVfG.js";import{t as E}from"./PageHeader-C9oHYjmE.js";var D={class:`grid gap-4 sm:grid-cols-2 xl:grid-cols-3`},O={class:`flex items-center justify-between gap-2`},k=[`onClick`],A={class:`font-bold text-gray-800 group-hover:text-orange-600 transition-colors`},j={key:0,class:`flex items-center flex-shrink-0`},M=[`onClick`],N=[`onClick`],P={key:0},F=d({__name:`SectionsPage`,setup(d){let F=b(),I=C(),L=e.filter(e=>e!==`settings`||I.isAdmin),R=o(!0),z=a({hero:!0,tape:!0,stats:!0,productsIntro:!0,about:!0,services:!0,standards:!0,industries:!0,testimonials:!0,contact:!0,footer:!0}),B=a({}),V=a({});function H(e){return z[e]!==!1}function U(e){return B[e]??null}async function W(){R.value=!0;try{let e=await y(`/admin/sections`);for(let t of e)z[t.key]=t.enabled!==!1,B[t.key]=t.updatedAt}catch(e){x.error(_(e))}finally{R.value=!1}}async function G(e,n){if(e===`settings`)return;let r=z[e]!==!1;z[e]=n,V[e]=!0;try{let r=await y(`/admin/sections/${e}/visibility`,{method:`PATCH`,body:{enabled:n}});z[e]=r.enabled!==!1,B[e]=r.updatedAt,x.success(`وضعیت «${t[e].title}» به ${n?`نمایش`:`عدم نمایش`} تغییر یافت`)}catch(t){z[e]=r,x.error(_(t))}finally{V[e]=!1}}function K(e){F.push({name:`section-edit`,params:{key:e}})}return r(()=>{W()}),(e,r)=>{let a=v(`a-tag`),o=v(`a-switch`),d=v(`a-card`),_=v(`a-spin`);return u(),S(f,null,[h(E,{title:`بخشهای سایت`,subtitle:`متنها، تصاویر و فهرستهای صفحه اصلی؛ تنظیم وضعیت نمایش و ویرایش محتوا`}),h(_,{spinning:R.value},{default:c(()=>[w(`div`,D,[(u(!0),S(f,null,i(s(L),e=>(u(),S(`div`,{key:e,class:`block group`},[h(d,{hoverable:``,class:l([`h-full flex flex-col justify-between transition-all`,{"opacity-75 bg-gray-50/60":e!==`settings`&&!H(e)}])},{default:c(()=>[w(`div`,null,[w(`div`,O,[w(`div`,{class:`flex items-center gap-2 cursor-pointer flex-1`,onClick:t=>K(e)},[w(`span`,A,n(s(t)[e].title),1),e===`settings`?(u(),p(a,{key:0,color:`blue`},{default:c(()=>[...r[0]||=[g(`تنظیمات پایه`,-1)]]),_:1})):H(e)?m(``,!0):(u(),p(a,{key:1,color:`error`},{default:c(()=>[...r[1]||=[g(`مخفی`,-1)]]),_:1}))],8,k),e===`settings`?m(``,!0):(u(),S(`div`,j,[h(o,{checked:H(e),loading:V[e],"checked-children":`نمایش`,"un-checked-children":`مخفی`,onChange:t=>G(e,!!t)},null,8,[`checked`,`loading`,`onChange`])]))]),w(`p`,{class:`mt-2 text-sm leading-6 text-gray-500 cursor-pointer`,onClick:t=>K(e)},n(s(t)[e].desc),9,M)]),w(`div`,{class:`mt-4 flex items-center justify-between border-t border-gray-100 pt-3 text-xs text-gray-400 cursor-pointer`,onClick:t=>K(e)},[r[2]||=w(`span`,{class:`text-primary font-medium group-hover:underline`},`ویرایش بخش ←`,-1),U(e)?(u(),S(`span`,P,`آخرین تغییر: `+n(s(T)(U(e))),1)):m(``,!0)],8,N)]),_:2},1032,[`class`])]))),128))])]),_:1},8,[`spinning`])],64)}}});export{F as default};
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
-1
@@ -1 +0,0 @@
|
||||
var e=new Intl.DateTimeFormat(`fa-IR-u-ca-persian`,{year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`}),t=t=>t?e.format(new Date(t)):`—`,n=e=>e<1048576?`${(e/1024).toLocaleString(`fa-IR`,{maximumFractionDigits:0})} کیلوبایت`:`${(e/1024/1024).toLocaleString(`fa-IR`,{maximumFractionDigits:1})} مگابایت`,r=e=>e.toLocaleString(`fa-IR`),i=`https://imenpishgaman.ir`,a={create:`ایجاد`,update:`ویرایش`,delete:`حذف`,upload:`آپلود`,login:`ورود`,publish:`انتشار`,unpublish:`عدم انتشار`,duplicate:`کپی`,reorder:`مرتبسازی`,"change-password":`تغییر رمز`},o={product:`محصول`,category:`دسته`,section:`بخش سایت`,media:`رسانه`,user:`کاربر`,message:`پیام`},s=(e,t)=>`${a[e]??e} ${o[t]??t}`;export{t as a,n as i,s as n,r,i as t};
|
||||
+1
@@ -0,0 +1 @@
|
||||
var e=new Intl.DateTimeFormat(`fa-IR-u-ca-persian`,{year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`}),t=t=>t?e.format(new Date(t)):`—`,n=e=>e<1048576?`${(e/1024).toLocaleString(`fa-IR`,{maximumFractionDigits:0})} کیلوبایت`:`${(e/1024/1024).toLocaleString(`fa-IR`,{maximumFractionDigits:1})} مگابایت`,r=e=>e.toLocaleString(`fa-IR`),i=`http://imenpishgaman.ir`,a={create:`ایجاد`,update:`ویرایش`,delete:`حذف`,upload:`آپلود`,login:`ورود`,publish:`انتشار`,unpublish:`عدم انتشار`,duplicate:`کپی`,reorder:`مرتبسازی`,"change-password":`تغییر رمز`},o={product:`محصول`,category:`دسته`,section:`بخش سایت`,media:`رسانه`,user:`کاربر`,message:`پیام`},s=(e,t)=>`${a[e]??e} ${o[t]??t}`;export{t as a,n as i,s as n,r,i as t};
|
||||
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -6,7 +6,7 @@
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>پنل مدیریت ایمن پیشگامان</title>
|
||||
<script type="module" crossorigin src="/assets/index-BuGK6ptM.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-1U5l_fLl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DjFpBvci.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
import type { SectionData } from "@imen/shared";
|
||||
import FormField from "@/components/FormField.vue";
|
||||
|
||||
/** Shared address / phone / email / hours fields from the settings section. */
|
||||
const data = defineModel<SectionData<"settings">>({ required: true });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** Zod path prefix when nested under another form context */
|
||||
pathPrefix?: string;
|
||||
}>(),
|
||||
{ pathPrefix: "" },
|
||||
);
|
||||
|
||||
function path(field: string) {
|
||||
return props.pathPrefix ? `${props.pathPrefix}.${field}` : field;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid gap-x-4 md:grid-cols-2">
|
||||
<FormField :path="path('phoneDisplay')" label="تلفن (نمایشی)" required>
|
||||
<a-input v-model:value="data.phoneDisplay" />
|
||||
</FormField>
|
||||
<FormField :path="path('phone')" label="تلفن (برای لینک و گوگل)" hint="با کد کشور، مثلاً +98-21-91008800" required>
|
||||
<a-input v-model:value="data.phone" class="ltr-input" />
|
||||
</FormField>
|
||||
<FormField :path="path('email')" label="ایمیل" required class="md:col-span-2">
|
||||
<a-input v-model:value="data.email" class="ltr-input" />
|
||||
</FormField>
|
||||
<FormField :path="path('address.locality')" label="شهر" required>
|
||||
<a-input v-model:value="data.address.locality" />
|
||||
</FormField>
|
||||
<FormField :path="path('address.country')" label="کد کشور" required>
|
||||
<a-input v-model:value="data.address.country" class="ltr-input" :maxlength="2" />
|
||||
</FormField>
|
||||
<FormField :path="path('address.street')" label="نشانی" required class="md:col-span-2">
|
||||
<a-input v-model:value="data.address.street" />
|
||||
</FormField>
|
||||
<FormField
|
||||
:path="path('mapUrl')"
|
||||
label="لینک نقشه"
|
||||
hint="اختیاری — لینک گوگلمپ، نشان و …"
|
||||
class="md:col-span-2"
|
||||
>
|
||||
<a-input v-model:value="data.mapUrl" class="ltr-input" placeholder="https://maps.google.com/..." />
|
||||
</FormField>
|
||||
<FormField :path="path('workingHours')" label="ساعات کاری (متن نمایشی)" required class="md:col-span-2">
|
||||
<a-input v-model:value="data.workingHours" />
|
||||
</FormField>
|
||||
</div>
|
||||
</template>
|
||||
@@ -107,9 +107,9 @@ async function submit() {
|
||||
|
||||
<div class="mt-8 flex items-center gap-4">
|
||||
<span
|
||||
class="flex size-14 items-center justify-center rounded-xl bg-white shadow-[0_0_28px_rgba(10,30,92,0.45)]"
|
||||
class="flex size-20 items-center justify-center overflow-hidden rounded-2xl bg-white shadow-[0_0_28px_rgba(10,30,92,0.45)]"
|
||||
>
|
||||
<img src="/logo.png" alt="ایمن پیشگامان" class="size-9 object-contain" />
|
||||
<img src="/logo.png" alt="ایمن پیشگامان" class="size-full scale-110 object-contain object-center" />
|
||||
</span>
|
||||
<div class="leading-tight">
|
||||
<div class="text-2xl font-black tracking-tight md:text-3xl">ایمن پیشگامان</div>
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { isSectionKey, SECTION_LABELS, type SectionKey, sectionSchemas } from "@imen/shared";
|
||||
import {
|
||||
isSectionKey,
|
||||
SECTION_LABELS,
|
||||
type SectionData,
|
||||
type SectionKey,
|
||||
sectionSchemas,
|
||||
settingsSectionSchema,
|
||||
} from "@imen/shared";
|
||||
import { message } from "ant-design-vue";
|
||||
import { type Component, computed, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
@@ -8,7 +15,6 @@ import VuesaxIcon from "@/components/VuesaxIcon.vue";
|
||||
import type { SectionResponse } from "@/api/types";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
import { useZodForm } from "@/composables/useZodForm";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { jalali, SITE_URL } from "@/utils/format";
|
||||
import AboutForm from "@/sections/AboutForm.vue";
|
||||
import ContactForm from "@/sections/ContactForm.vue";
|
||||
@@ -38,7 +44,6 @@ const FORMS: Record<SectionKey, Component> = {
|
||||
footer: FooterForm,
|
||||
};
|
||||
|
||||
const auth = useAuthStore();
|
||||
const route = useRoute();
|
||||
const key = String(route.params.key);
|
||||
const valid = isSectionKey(key);
|
||||
@@ -49,6 +54,14 @@ const { model, validate, handleError, reset } = useZodForm(
|
||||
sectionSchemas[sectionKey] as (typeof sectionSchemas)["hero"],
|
||||
{} as never,
|
||||
);
|
||||
/** Contact page also edits site-wide contact info from settings */
|
||||
const settingsModel = ref<SectionData<"settings"> | null>(null);
|
||||
const contactModel = computed({
|
||||
get: () => model.value as unknown as SectionData<"contact">,
|
||||
set: (v: SectionData<"contact">) => {
|
||||
model.value = v as never;
|
||||
},
|
||||
});
|
||||
const loaded = ref(false);
|
||||
const saving = ref(false);
|
||||
const updatedAt = ref<string | null>(null);
|
||||
@@ -57,12 +70,20 @@ const label = computed(() => (valid ? SECTION_LABELS[sectionKey] : null));
|
||||
async function load() {
|
||||
try {
|
||||
const res = await api<SectionResponse>(`/admin/sections/${sectionKey}`);
|
||||
const data = res.data as any;
|
||||
const data = res.data as Record<string, unknown>;
|
||||
if (sectionKey !== "settings" && data.enabled === undefined) {
|
||||
data.enabled = true;
|
||||
}
|
||||
reset(data as never);
|
||||
updatedAt.value = res.updatedAt;
|
||||
|
||||
if (sectionKey === "contact") {
|
||||
const settingsRes = await api<SectionResponse>("/admin/sections/settings");
|
||||
settingsModel.value = settingsSectionSchema.parse(settingsRes.data);
|
||||
} else {
|
||||
settingsModel.value = null;
|
||||
}
|
||||
|
||||
loaded.value = true;
|
||||
} catch (e) {
|
||||
void message.error(errorMessage(e));
|
||||
@@ -72,8 +93,25 @@ async function load() {
|
||||
async function save() {
|
||||
const data = validate();
|
||||
if (!data) return;
|
||||
|
||||
if (sectionKey === "contact" && settingsModel.value) {
|
||||
const settingsParsed = settingsSectionSchema.safeParse(settingsModel.value);
|
||||
if (!settingsParsed.success) {
|
||||
void message.error("اطلاعات تماس معتبر نیست — نشانی، تلفن یا ایمیل را بررسی کنید");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
saving.value = true;
|
||||
try {
|
||||
if (sectionKey === "contact" && settingsModel.value) {
|
||||
const settingsRes = await api<SectionResponse>("/admin/sections/settings", {
|
||||
method: "PUT",
|
||||
body: settingsSectionSchema.parse(settingsModel.value),
|
||||
});
|
||||
settingsModel.value = settingsSectionSchema.parse(settingsRes.data);
|
||||
}
|
||||
|
||||
const res = await api<SectionResponse>(`/admin/sections/${sectionKey}`, { method: "PUT", body: data });
|
||||
reset(res.data as never);
|
||||
updatedAt.value = res.updatedAt;
|
||||
@@ -87,9 +125,9 @@ async function save() {
|
||||
|
||||
const togglingVisibility = ref(false);
|
||||
|
||||
async function onVisibilityChange(val: any) {
|
||||
async function onVisibilityChange(val: unknown) {
|
||||
const nextVal = Boolean(val);
|
||||
(model.value as any).enabled = nextVal;
|
||||
(model.value as { enabled?: boolean }).enabled = nextVal;
|
||||
togglingVisibility.value = true;
|
||||
try {
|
||||
const res = await api<{ key: string; enabled: boolean; updatedAt: string }>(
|
||||
@@ -99,13 +137,13 @@ async function onVisibilityChange(val: any) {
|
||||
body: { enabled: nextVal },
|
||||
},
|
||||
);
|
||||
(model.value as any).enabled = res.enabled !== false;
|
||||
(model.value as { enabled?: boolean }).enabled = res.enabled !== false;
|
||||
updatedAt.value = res.updatedAt;
|
||||
void message.success(
|
||||
`وضعیت نمایش به ${nextVal ? "نمایش" : "عدم نمایش"} تغییر یافت`,
|
||||
);
|
||||
} catch (e) {
|
||||
(model.value as any).enabled = !nextVal;
|
||||
(model.value as { enabled?: boolean }).enabled = !nextVal;
|
||||
void message.error(errorMessage(e));
|
||||
} finally {
|
||||
togglingVisibility.value = false;
|
||||
@@ -126,7 +164,7 @@ if (valid) void load();
|
||||
<div v-if="sectionKey !== 'settings'" class="max-lg:hidden flex items-center gap-2 me-2">
|
||||
<span class="text-sm font-medium text-gray-700">نمایش در سایت:</span>
|
||||
<a-switch
|
||||
:checked="(model as any)?.enabled !== false"
|
||||
:checked="(model as { enabled?: boolean })?.enabled !== false"
|
||||
:loading="togglingVisibility"
|
||||
checked-children="نمایش"
|
||||
un-checked-children="مخفی"
|
||||
@@ -138,7 +176,7 @@ if (valid) void load();
|
||||
type="primary"
|
||||
class="max-lg:hidden"
|
||||
:loading="saving"
|
||||
:disabled="!loaded || (sectionKey === 'settings' && !auth.isAdmin)"
|
||||
:disabled="!loaded"
|
||||
@click="save"
|
||||
>
|
||||
<template #icon><VuesaxIcon name="save-2" :size="16" /></template>
|
||||
@@ -147,21 +185,19 @@ if (valid) void load();
|
||||
</PageHeader>
|
||||
<a-spin :spinning="!loaded">
|
||||
<a-alert
|
||||
v-if="loaded && sectionKey === 'settings' && !auth.isAdmin"
|
||||
type="error"
|
||||
show-icon
|
||||
message="فقط مدیر کل میتواند تنظیمات سایت را تغییر دهد."
|
||||
class="mb-4"
|
||||
/>
|
||||
<a-alert
|
||||
v-else-if="loaded && sectionKey !== 'settings' && (model as any).enabled === false"
|
||||
v-if="loaded && sectionKey !== 'settings' && (model as { enabled?: boolean }).enabled === false"
|
||||
type="warning"
|
||||
show-icon
|
||||
message="این بخش در حال حاضر در سایت نمایش داده نمیشود (مخفی است)."
|
||||
class="mb-4"
|
||||
/>
|
||||
<a-form v-if="loaded" layout="vertical" class="pb-20 lg:pb-0" @submit.prevent="save">
|
||||
<component :is="FORMS[sectionKey]" v-model="model" />
|
||||
<ContactForm
|
||||
v-if="sectionKey === 'contact'"
|
||||
v-model="contactModel"
|
||||
v-model:settings="settingsModel"
|
||||
/>
|
||||
<component v-else :is="FORMS[sectionKey]" v-model="model" />
|
||||
</a-form>
|
||||
</a-spin>
|
||||
|
||||
@@ -169,13 +205,13 @@ if (valid) void load();
|
||||
<div class="lg:hidden fixed bottom-0 inset-x-0 z-30 border-t border-gray-200 bg-white/95 px-4 py-2.5 backdrop-blur-md shadow-lg flex items-center justify-between gap-3">
|
||||
<div v-if="sectionKey !== 'settings'" class="flex items-center gap-2">
|
||||
<a-switch
|
||||
:checked="(model as any)?.enabled !== false"
|
||||
:checked="(model as { enabled?: boolean })?.enabled !== false"
|
||||
:loading="togglingVisibility"
|
||||
size="small"
|
||||
@change="onVisibilityChange"
|
||||
/>
|
||||
<span class="text-xs font-medium text-gray-700">
|
||||
{{ (model as any)?.enabled !== false ? 'نمایش در سایت' : 'مخفی' }}
|
||||
{{ (model as { enabled?: boolean })?.enabled !== false ? 'نمایش در سایت' : 'مخفی' }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-else class="text-xs text-gray-500 font-medium">تنظیمات پایه</div>
|
||||
@@ -191,7 +227,7 @@ if (valid) void load();
|
||||
size="middle"
|
||||
class="!font-bold !px-5"
|
||||
:loading="saving"
|
||||
:disabled="!loaded || (sectionKey === 'settings' && !auth.isAdmin)"
|
||||
:disabled="!loaded"
|
||||
@click="save"
|
||||
>
|
||||
<template #icon><VuesaxIcon name="save-2" :size="16" /></template>
|
||||
|
||||
@@ -6,13 +6,10 @@ import { useRouter } from "vue-router";
|
||||
import { api, errorMessage } from "@/api/http";
|
||||
import type { AdminSectionSummary } from "@/api/types";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { jalali } from "@/utils/format";
|
||||
|
||||
const router = useRouter();
|
||||
const auth = useAuthStore();
|
||||
// site-wide settings are editable by admins only
|
||||
const keys = SECTION_KEYS.filter((k) => k !== "settings" || auth.isAdmin);
|
||||
const keys = SECTION_KEYS;
|
||||
|
||||
const loading = ref(true);
|
||||
const sectionEnabled = reactive<Record<string, boolean>>({
|
||||
|
||||
@@ -103,7 +103,7 @@ onMounted(load);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageHeader title="کاربران پنل" subtitle="مدیر کل به همه بخشها دسترسی دارد؛ ویرایشگر به جز کاربران و تنظیمات سایت">
|
||||
<PageHeader title="کاربران پنل" subtitle="مدیر کل به همه بخشها دسترسی دارد؛ ویرایشگر به جز مدیریت کاربران و حذف دادهها">
|
||||
<a-button type="primary" @click="openNew">
|
||||
<template #icon><VuesaxIcon name="add" :size="16" /></template>
|
||||
کاربر جدید
|
||||
@@ -241,7 +241,6 @@ onMounted(load);
|
||||
</div>
|
||||
<ul class="mt-2 space-y-1 text-[11px] text-gray-600 list-disc list-inside">
|
||||
<li>مدیریت کاربران، نقشها و تنظیمات امنیتی</li>
|
||||
<li>تنظیمات پایه سایت، سئو، شبکهها و ساعات کاری</li>
|
||||
<li>حذف دادهها (محصولات، دستهها، پیامها، رسانهها)</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -253,7 +252,7 @@ onMounted(load);
|
||||
</div>
|
||||
<ul class="mt-2 space-y-1 text-[11px] text-gray-600 list-disc list-inside">
|
||||
<li>ایجاد، ویرایش و انتشار محصولات و دستهبندیها</li>
|
||||
<li>ویرایش محتوای بخشهای صفحه اصلی</li>
|
||||
<li>ویرایش بخشهای سایت، نشانی، تماس، سئو و شبکهها</li>
|
||||
<li>آپلود تصاویر در رسانه و مشاهده پیامهای تماس</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -269,7 +268,6 @@ onMounted(load);
|
||||
</div>
|
||||
<ul class="mt-2.5 space-y-1.5 text-xs text-gray-600 list-disc list-inside">
|
||||
<li>مدیریت کاربران، افزودن کاربر و تغییر نقشها و وضعیت حساب</li>
|
||||
<li>دسترسی کامل به تنظیمات پایه سایت، ساعات کاری، شبکهها و سئو</li>
|
||||
<li>مجوز حذف محصولات، دستهبندیها، رسانهها و پیامهای تماس</li>
|
||||
<li>مشاهده لاگهای امنیتی و رویدادهای کامل سامانه</li>
|
||||
</ul>
|
||||
@@ -282,7 +280,7 @@ onMounted(load);
|
||||
</div>
|
||||
<ul class="mt-2.5 space-y-1.5 text-xs text-gray-600 list-disc list-inside">
|
||||
<li>ایجاد، ویرایش، کپی و انتشار محصولات و گروهها</li>
|
||||
<li>ویرایش محتوای بخشهای صفحه اصلی و فعال/مخفیسازی نمایش آنها</li>
|
||||
<li>ویرایش بخشهای سایت، نشانی، تماس، سئو، شبکهها و نمایش/مخفیسازی</li>
|
||||
<li>آپلود و ویرایش اطلاعات رسانهها در کتابخانه رسانه</li>
|
||||
<li>مشاهده پیامهای تماس و بهروزرسانی وضعیت (بدون دسترسی حذف یا مدیریت کاربران)</li>
|
||||
</ul>
|
||||
@@ -319,7 +317,7 @@ onMounted(load);
|
||||
:options="ROLE_OPTIONS"
|
||||
/>
|
||||
<div class="mt-1.5 text-[11px] text-gray-500">
|
||||
{{ model.role === 'ADMIN' ? 'مدیر کل: دسترسی کامل سیستمی، حذف دادهها، تنظیمات و مدیریت کاربران' : 'ویرایشگر: ویرایش محتوا، محصولات، بخشها و پیامها (بدون حذف)' }}
|
||||
{{ model.role === 'ADMIN' ? 'مدیر کل: دسترسی کامل سیستمی، حذف دادهها و مدیریت کاربران' : 'ویرایشگر: ویرایش محتوا، محصولات، بخشهای سایت (از جمله نشانی و تماس)، پیامها (بدون حذف)' }}
|
||||
</div>
|
||||
</FormField>
|
||||
<FormField v-if="!isNew" path="isActive">
|
||||
|
||||
@@ -13,7 +13,7 @@ const data = defineModel<SectionData<"about">>({ required: true });
|
||||
<div class="grid gap-6 xl:grid-cols-3">
|
||||
<div class="space-y-6 xl:col-span-2">
|
||||
<a-card title="عنوان و متن">
|
||||
<SectionHeadFields v-model="data.head" :with-desc="false" />
|
||||
<SectionHeadFields v-model="data.head" />
|
||||
<FormField path="body" label="متن درباره ما" required>
|
||||
<a-textarea v-model:value="data.body" :auto-size="{ minRows: 4, maxRows: 10 }" />
|
||||
</FormField>
|
||||
|
||||
@@ -1,17 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import type { SectionData } from "@imen/shared";
|
||||
import ContactInfoFields from "@/components/ContactInfoFields.vue";
|
||||
import FormField from "@/components/FormField.vue";
|
||||
import SectionHeadFields from "@/components/SectionHeadFields.vue";
|
||||
|
||||
const data = defineModel<SectionData<"contact">>({ required: true });
|
||||
/** Site-wide contact info (settings); saved together with this section. */
|
||||
const settings = defineModel<SectionData<"settings"> | null>("settings", { default: null });
|
||||
|
||||
function onSettingsUpdate(value: SectionData<"settings">) {
|
||||
settings.value = value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<a-card title="عنوان بخش">
|
||||
<SectionHeadFields v-model="data.head" />
|
||||
<p class="text-sm text-gray-500">نشانی، تلفن، ایمیل و ساعات کاری از «تنظیمات سایت» خوانده میشوند.</p>
|
||||
</a-card>
|
||||
|
||||
<a-card v-if="settings" title="نشانی و اطلاعات تماس">
|
||||
<p class="mb-4 text-sm text-gray-500">
|
||||
این مقادیر در هدر، فوتر، بخش تماس و سئوی سایت مشترکاند و با ذخیرهٔ همین صفحه بهروز میشوند.
|
||||
</p>
|
||||
<ContactInfoFields :model-value="settings" @update:model-value="onSettingsUpdate" />
|
||||
</a-card>
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-2">
|
||||
<a-card title="کادر پاسخگویی اضطراری">
|
||||
<FormField path="emergencyTitle" label="عنوان" required><a-input v-model:value="data.emergencyTitle" /></FormField>
|
||||
@@ -24,10 +38,16 @@ const data = defineModel<SectionData<"contact">>({ required: true });
|
||||
<FormField path="formTitle" label="عنوان فرم" hint="برای شکستن خط Enter بزنید" required>
|
||||
<a-textarea v-model:value="data.formTitle" :auto-size="{ minRows: 2 }" />
|
||||
</FormField>
|
||||
<FormField path="submitLabel" label="متن دکمه ارسال" required>
|
||||
<a-input v-model:value="data.submitLabel" />
|
||||
</FormField>
|
||||
<FormField path="successTitle" label="عنوان پیام موفقیت" required><a-input v-model:value="data.successTitle" /></FormField>
|
||||
<FormField path="successText" label="متن پیام موفقیت" required>
|
||||
<a-textarea v-model:value="data.successText" :auto-size="{ minRows: 2 }" />
|
||||
</FormField>
|
||||
<FormField path="resetLabel" label="متن دکمه بعد از موفقیت" required>
|
||||
<a-input v-model:value="data.resetLabel" />
|
||||
</FormField>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,15 +6,30 @@ const data = defineModel<SectionData<"footer">>({ required: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-card title="فوتر">
|
||||
<FormField path="about" label="متن معرفی" required>
|
||||
<a-textarea v-model:value="data.about" :auto-size="{ minRows: 2, maxRows: 5 }" />
|
||||
</FormField>
|
||||
<div class="grid gap-x-4 md:grid-cols-3">
|
||||
<FormField path="copyright" label="متن کپیرایت" required><a-input v-model:value="data.copyright" /></FormField>
|
||||
<FormField path="certLine" label="خط گواهینامهها"><a-input v-model:value="data.certLine" class="ltr-input" /></FormField>
|
||||
<FormField path="emergencyNote" label="یادداشت پشتیبانی"><a-input v-model:value="data.emergencyNote" /></FormField>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500">لینک دستهها و اطلاعات تماس فوتر بهصورت خودکار از دستهبندیها و تنظیمات سایت ساخته میشوند.</p>
|
||||
</a-card>
|
||||
<div class="space-y-6">
|
||||
<a-card title="فوتر">
|
||||
<FormField path="about" label="متن معرفی" required>
|
||||
<a-textarea v-model:value="data.about" :auto-size="{ minRows: 2, maxRows: 5 }" />
|
||||
</FormField>
|
||||
<div class="grid gap-x-4 md:grid-cols-3">
|
||||
<FormField path="linksTitle" label="عنوان ستون لینکها" required>
|
||||
<a-input v-model:value="data.linksTitle" />
|
||||
</FormField>
|
||||
<FormField path="productsTitle" label="عنوان ستون محصولات" required>
|
||||
<a-input v-model:value="data.productsTitle" />
|
||||
</FormField>
|
||||
<FormField path="contactTitle" label="عنوان ستون ارتباط" required>
|
||||
<a-input v-model:value="data.contactTitle" />
|
||||
</FormField>
|
||||
</div>
|
||||
<div class="grid gap-x-4 md:grid-cols-3">
|
||||
<FormField path="copyright" label="متن کپیرایت" required><a-input v-model:value="data.copyright" /></FormField>
|
||||
<FormField path="certLine" label="خط گواهینامهها"><a-input v-model:value="data.certLine" class="ltr-input" /></FormField>
|
||||
<FormField path="emergencyNote" label="یادداشت پشتیبانی"><a-input v-model:value="data.emergencyNote" /></FormField>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500">
|
||||
لینک دستهها و اطلاعات تماس فوتر بهصورت خودکار از دستهبندیها و تنظیمات سایت ساخته میشوند.
|
||||
</p>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { type MediaDto, type SectionData, SOCIAL_TYPES, type SocialType, WEEK_DAYS } from "@imen/shared";
|
||||
import ContactInfoFields from "@/components/ContactInfoFields.vue";
|
||||
import FormField from "@/components/FormField.vue";
|
||||
import ImageField from "@/components/ImageField.vue";
|
||||
import ListEditor from "@/components/ListEditor.vue";
|
||||
@@ -66,33 +67,22 @@ function pickOgImage(m: MediaDto) {
|
||||
</a-card>
|
||||
|
||||
<a-card title="اطلاعات تماس">
|
||||
<ContactInfoFields v-model="data" />
|
||||
</a-card>
|
||||
|
||||
<a-card title="دکمه هدر">
|
||||
<div class="grid gap-x-4 md:grid-cols-2">
|
||||
<FormField path="phoneDisplay" label="تلفن (نمایشی)" required>
|
||||
<a-input v-model:value="data.phoneDisplay" />
|
||||
<FormField path="headerCta.label" label="متن دکمه" required>
|
||||
<a-input v-model:value="data.headerCta.label" />
|
||||
</FormField>
|
||||
<FormField path="phone" label="تلفن (برای لینک و گوگل)" hint="با کد کشور، مثلاً +98-21-91008800" required>
|
||||
<a-input v-model:value="data.phone" class="ltr-input" />
|
||||
</FormField>
|
||||
<FormField path="email" label="ایمیل" required class="md:col-span-2">
|
||||
<a-input v-model:value="data.email" class="ltr-input" />
|
||||
</FormField>
|
||||
<FormField path="address.locality" label="شهر" required>
|
||||
<a-input v-model:value="data.address.locality" />
|
||||
</FormField>
|
||||
<FormField path="address.country" label="کد کشور" required>
|
||||
<a-input v-model:value="data.address.country" class="ltr-input" :maxlength="2" />
|
||||
</FormField>
|
||||
<FormField path="address.street" label="نشانی" required class="md:col-span-2">
|
||||
<a-input v-model:value="data.address.street" />
|
||||
<FormField path="headerCta.href" label="لینک" required>
|
||||
<a-input v-model:value="data.headerCta.href" class="ltr-input" placeholder="/#contact" />
|
||||
</FormField>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card title="ساعات کاری">
|
||||
<FormField path="workingHours" label="متن نمایشی" required>
|
||||
<a-input v-model:value="data.workingHours" />
|
||||
</FormField>
|
||||
<FormField path="openingHours.days" label="روزهای کاری (برای گوگل)">
|
||||
<a-card title="ساعات کاری (برای گوگل)">
|
||||
<FormField path="openingHours.days" label="روزهای کاری">
|
||||
<a-checkbox-group
|
||||
v-model:value="data.openingHours.days"
|
||||
:options="DAY_OPTIONS"
|
||||
|
||||
@@ -10,7 +10,7 @@ const data = defineModel<SectionData<"testimonials">>({ required: true });
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<a-card title="عنوان بخش">
|
||||
<SectionHeadFields v-model="data.head" :with-highlight="false" :with-desc="false" />
|
||||
<SectionHeadFields v-model="data.head" />
|
||||
</a-card>
|
||||
<a-card title="نظرات" :body-style="{ background: '#fafafa' }">
|
||||
<ListEditor v-model="data.items" :create="() => ({ text: '', name: '', role: '' })" :max="12" add-label="افزودن نظر" :item-title="(q) => q.name">
|
||||
|
||||
+1
-4
@@ -10,7 +10,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
||||
return function (target, key) { decorator(target, key, paramIndex); }
|
||||
};
|
||||
import { BadRequestException, Body, Controller, ForbiddenException, Get, Injectable, Module, NotFoundException, Param, Patch, Put, ServiceUnavailableException, } from "@nestjs/common";
|
||||
import { BadRequestException, Body, Controller, Get, Injectable, Module, NotFoundException, Param, Patch, Put, ServiceUnavailableException, } from "@nestjs/common";
|
||||
import { isSectionKey, SECTION_KEYS, sectionSchemas, toIssues, } from "@imen/shared";
|
||||
import { AdminAuth, CurrentUser } from "../common/auth.js";
|
||||
import { AuditService, RevalidateService } from "../common/common.module.js";
|
||||
@@ -44,9 +44,6 @@ let SectionsService = class SectionsService {
|
||||
async update(key, body, user) {
|
||||
if (!isSectionKey(key))
|
||||
throw new NotFoundException("بخش پیدا نشد");
|
||||
if (key === "settings" && user.role !== "ADMIN") {
|
||||
throw new ForbiddenException("فقط مدیر کل میتواند تنظیمات سایت را تغییر دهد");
|
||||
}
|
||||
const parsed = sectionSchemas[key].safeParse(body);
|
||||
if (!parsed.success) {
|
||||
throw new BadRequestException({
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+7
@@ -12,6 +12,7 @@ export const SECTIONS = {
|
||||
locality: "تهران",
|
||||
country: "IR",
|
||||
},
|
||||
mapUrl: "",
|
||||
workingHours: "شنبه تا پنجشنبه — ۸:۰۰ تا ۱۷:۰۰",
|
||||
openingHours: {
|
||||
days: ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday"],
|
||||
@@ -19,6 +20,7 @@ export const SECTIONS = {
|
||||
closes: "17:00",
|
||||
},
|
||||
foundingDate: "2007",
|
||||
headerCta: { label: "درخواست مشاوره", href: "/#contact" },
|
||||
socials: [
|
||||
{ type: "website", url: "/" },
|
||||
{ type: "email", url: "mailto:info@imenpishgaman.ir" },
|
||||
@@ -246,6 +248,8 @@ export const SECTIONS = {
|
||||
formTitle: "در کمتر از ۲۴ ساعت\nبا شما تماس میگیریم",
|
||||
successTitle: "درخواست شما ثبت شد",
|
||||
successText: "کارشناسان ما تا ۲۴ ساعت آینده برای هماهنگی جلسه بازدید با شما تماس میگیرند.",
|
||||
submitLabel: "ارسال درخواست مشاوره",
|
||||
resetLabel: "ثبت درخواست جدید",
|
||||
},
|
||||
footer: {
|
||||
enabled: true,
|
||||
@@ -253,6 +257,9 @@ export const SECTIONS = {
|
||||
copyright: "© ۱۴۰۴ ایمن پیشگامان — تمامی حقوق محفوظ است.",
|
||||
certLine: "ISO 9001:2015 — CE CERTIFIED PPE SUPPLIER",
|
||||
emergencyNote: "پشتیبانی اضطراری: ۲۴/۷",
|
||||
linksTitle: "دسترسی",
|
||||
productsTitle: "محصولات",
|
||||
contactTitle: "ارتباط",
|
||||
},
|
||||
};
|
||||
export const CATEGORY_COVERS = {
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -2,7 +2,6 @@ import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
ForbiddenException,
|
||||
Get,
|
||||
Injectable,
|
||||
Module,
|
||||
@@ -55,10 +54,6 @@ export class SectionsService {
|
||||
|
||||
async update(key: string, body: unknown, user: AuthUser) {
|
||||
if (!isSectionKey(key)) throw new NotFoundException("بخش پیدا نشد");
|
||||
// site-wide settings (contact info, SEO defaults) are limited to admins
|
||||
if (key === "settings" && user.role !== "ADMIN") {
|
||||
throw new ForbiddenException("فقط مدیر کل میتواند تنظیمات سایت را تغییر دهد");
|
||||
}
|
||||
const parsed = sectionSchemas[key].safeParse(body);
|
||||
if (!parsed.success) {
|
||||
throw new BadRequestException({
|
||||
|
||||
@@ -19,6 +19,7 @@ export const SECTIONS: SiteSections = {
|
||||
locality: "تهران",
|
||||
country: "IR",
|
||||
},
|
||||
mapUrl: "",
|
||||
workingHours: "شنبه تا پنجشنبه — ۸:۰۰ تا ۱۷:۰۰",
|
||||
openingHours: {
|
||||
days: ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday"],
|
||||
@@ -26,6 +27,7 @@ export const SECTIONS: SiteSections = {
|
||||
closes: "17:00",
|
||||
},
|
||||
foundingDate: "2007",
|
||||
headerCta: { label: "درخواست مشاوره", href: "/#contact" },
|
||||
socials: [
|
||||
{ type: "website", url: "/" },
|
||||
{ type: "email", url: "mailto:info@imenpishgaman.ir" },
|
||||
@@ -265,6 +267,8 @@ export const SECTIONS: SiteSections = {
|
||||
formTitle: "در کمتر از ۲۴ ساعت\nبا شما تماس میگیریم",
|
||||
successTitle: "درخواست شما ثبت شد",
|
||||
successText: "کارشناسان ما تا ۲۴ ساعت آینده برای هماهنگی جلسه بازدید با شما تماس میگیرند.",
|
||||
submitLabel: "ارسال درخواست مشاوره",
|
||||
resetLabel: "ثبت درخواست جدید",
|
||||
},
|
||||
|
||||
footer: {
|
||||
@@ -274,6 +278,9 @@ export const SECTIONS: SiteSections = {
|
||||
copyright: "© ۱۴۰۴ ایمن پیشگامان — تمامی حقوق محفوظ است.",
|
||||
certLine: "ISO 9001:2015 — CE CERTIFIED PPE SUPPLIER",
|
||||
emergencyNote: "پشتیبانی اضطراری: ۲۴/۷",
|
||||
linksTitle: "دسترسی",
|
||||
productsTitle: "محصولات",
|
||||
contactTitle: "ارتباط",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"date": "2026-10-24T11:47:36.129Z",
|
||||
"date": "2026-10-24T13:11:46.671Z",
|
||||
"preset": "node-server",
|
||||
"framework": {
|
||||
"name": "nuxt",
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"id":"3e7fac48-d5e5-406c-8e09-0f6380ec2f4f","timestamp":1792842383527}
|
||||
{"id":"1fdc7d29-4179-4263-a065-71c6f514739a","timestamp":1792847411809}
|
||||
@@ -0,0 +1 @@
|
||||
{"id":"1fdc7d29-4179-4263-a065-71c6f514739a","timestamp":1792847411809,"prerendered":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"id":"3e7fac48-d5e5-406c-8e09-0f6380ec2f4f","timestamp":1792842383527,"prerendered":[]}
|
||||
@@ -4066,7 +4066,7 @@ function _expandFromEnv(value) {
|
||||
const _inlineRuntimeConfig = {
|
||||
"app": {
|
||||
"baseURL": "/",
|
||||
"buildId": "3e7fac48-d5e5-406c-8e09-0f6380ec2f4f",
|
||||
"buildId": "1fdc7d29-4179-4263-a065-71c6f514739a",
|
||||
"buildAssetsDir": "/_nuxt/",
|
||||
"cdnURL": ""
|
||||
},
|
||||
@@ -4813,6 +4813,13 @@ const assets = {
|
||||
"size": 337,
|
||||
"path": "../public/favicon.svg"
|
||||
},
|
||||
"/images/logo.png": {
|
||||
"type": "image/png",
|
||||
"etag": "\"45a79-DygL+P0onWMf3/jsNWp1BqEUPE4\"",
|
||||
"mtime": "2026-10-11T12:10:17.609Z",
|
||||
"size": 285305,
|
||||
"path": "../public/images/logo.png"
|
||||
},
|
||||
"/fonts/IRANSansWebFaNum.woff2": {
|
||||
"type": "font/woff2",
|
||||
"etag": "\"6d28-AufINCNdNZVNkWiYA6/Wp9W2wE4\"",
|
||||
@@ -4834,13 +4841,6 @@ const assets = {
|
||||
"size": 27316,
|
||||
"path": "../public/fonts/IRANSansWebFaNum_Bold.woff2"
|
||||
},
|
||||
"/fonts/IRANSansWebFaNum_Medium.woff2": {
|
||||
"type": "font/woff2",
|
||||
"etag": "\"64b8-6V1kQDm0NUXner/ubiM2u7MPIyY\"",
|
||||
"mtime": "2026-07-02T15:07:42.535Z",
|
||||
"size": 25784,
|
||||
"path": "../public/fonts/IRANSansWebFaNum_Medium.woff2"
|
||||
},
|
||||
"/fonts/IRANSansWebFaNum_Light.woff2": {
|
||||
"type": "font/woff2",
|
||||
"etag": "\"6ef0-0ZuVZfSKf1HbzDmvdDZdESRzFOs\"",
|
||||
@@ -4848,40 +4848,12 @@ const assets = {
|
||||
"size": 28400,
|
||||
"path": "../public/fonts/IRANSansWebFaNum_Light.woff2"
|
||||
},
|
||||
"/_nuxt/BHOIQrRl.js": {
|
||||
"type": "text/javascript; charset=utf-8",
|
||||
"etag": "\"1637-w7apJM7DBwq91AEy0Dz89/JwU4s\"",
|
||||
"mtime": "2026-10-24T11:47:18.110Z",
|
||||
"size": 5687,
|
||||
"path": "../public/_nuxt/BHOIQrRl.js"
|
||||
},
|
||||
"/_nuxt/DlAPF8TW.js": {
|
||||
"type": "text/javascript; charset=utf-8",
|
||||
"etag": "\"1e85-YNtOmEmkO1nMcpDM/ztRmg/rRLw\"",
|
||||
"mtime": "2026-10-24T11:47:18.112Z",
|
||||
"size": 7813,
|
||||
"path": "../public/_nuxt/DlAPF8TW.js"
|
||||
},
|
||||
"/_nuxt/Dd1GE-gU.js": {
|
||||
"type": "text/javascript; charset=utf-8",
|
||||
"etag": "\"21420-NSMMtehs6gAIMs2sZ84NfSEv/wU\"",
|
||||
"mtime": "2026-10-24T11:47:18.110Z",
|
||||
"size": 136224,
|
||||
"path": "../public/_nuxt/Dd1GE-gU.js"
|
||||
},
|
||||
"/_nuxt/D0e0LUpa.js": {
|
||||
"type": "text/javascript; charset=utf-8",
|
||||
"etag": "\"794-cNNGOqTLOp2+IDNDEbTWshwORp0\"",
|
||||
"mtime": "2026-10-24T11:47:18.110Z",
|
||||
"size": 1940,
|
||||
"path": "../public/_nuxt/D0e0LUpa.js"
|
||||
},
|
||||
"/_nuxt/DOASWvjr.js": {
|
||||
"type": "text/javascript; charset=utf-8",
|
||||
"etag": "\"3827-3BvUfZznriOnNWFcpi6aALVMpOE\"",
|
||||
"mtime": "2026-10-24T11:47:18.110Z",
|
||||
"size": 14375,
|
||||
"path": "../public/_nuxt/DOASWvjr.js"
|
||||
"/fonts/IRANSansWebFaNum_Medium.woff2": {
|
||||
"type": "font/woff2",
|
||||
"etag": "\"64b8-6V1kQDm0NUXner/ubiM2u7MPIyY\"",
|
||||
"mtime": "2026-07-02T15:07:42.535Z",
|
||||
"size": 25784,
|
||||
"path": "../public/fonts/IRANSansWebFaNum_Medium.woff2"
|
||||
},
|
||||
"/fonts/IRANSansWebFaNum_UltraLight.woff2": {
|
||||
"type": "font/woff2",
|
||||
@@ -4890,73 +4862,101 @@ const assets = {
|
||||
"size": 28208,
|
||||
"path": "../public/fonts/IRANSansWebFaNum_UltraLight.woff2"
|
||||
},
|
||||
"/_nuxt/DoONAYG5.js": {
|
||||
"/_nuxt/D0e0LUpa.js": {
|
||||
"type": "text/javascript; charset=utf-8",
|
||||
"etag": "\"3b62c-dc9OPIQbgdy0oXHYg6EoUgVy6yc\"",
|
||||
"mtime": "2026-10-24T11:47:18.112Z",
|
||||
"size": 243244,
|
||||
"path": "../public/_nuxt/DoONAYG5.js"
|
||||
"etag": "\"794-cNNGOqTLOp2+IDNDEbTWshwORp0\"",
|
||||
"mtime": "2026-10-24T13:11:27.225Z",
|
||||
"size": 1940,
|
||||
"path": "../public/_nuxt/D0e0LUpa.js"
|
||||
},
|
||||
"/_nuxt/VuesaxIcon.4cYnpGo8.css": {
|
||||
"type": "text/css; charset=utf-8",
|
||||
"etag": "\"dc-Uz1+XA31y3oabrHslWpIJp4Ls74\"",
|
||||
"mtime": "2026-10-24T11:47:18.112Z",
|
||||
"size": 220,
|
||||
"path": "../public/_nuxt/VuesaxIcon.4cYnpGo8.css"
|
||||
},
|
||||
"/_nuxt/DlDWjZRl.js": {
|
||||
"/_nuxt/BHOIQrRl.js": {
|
||||
"type": "text/javascript; charset=utf-8",
|
||||
"etag": "\"37855-Z6e8Pe/n6IqFkcSEj31zIJ7FhuQ\"",
|
||||
"mtime": "2026-10-24T11:47:18.100Z",
|
||||
"size": 227413,
|
||||
"path": "../public/_nuxt/DlDWjZRl.js"
|
||||
"etag": "\"1637-w7apJM7DBwq91AEy0Dz89/JwU4s\"",
|
||||
"mtime": "2026-10-24T13:11:27.225Z",
|
||||
"size": 5687,
|
||||
"path": "../public/_nuxt/BHOIQrRl.js"
|
||||
},
|
||||
"/_nuxt/builds/latest.json": {
|
||||
"type": "application/json",
|
||||
"etag": "\"47-JKgzcgu4mBBz7nSPUtzomjs30dc\"",
|
||||
"mtime": "2026-10-24T11:47:23.008Z",
|
||||
"size": 71,
|
||||
"path": "../public/_nuxt/builds/latest.json"
|
||||
},
|
||||
"/_nuxt/builds/meta/3e7fac48-d5e5-406c-8e09-0f6380ec2f4f.json": {
|
||||
"type": "application/json",
|
||||
"etag": "\"58-JIbrCAB+PEB8Ybvxp3RzCo2D6XU\"",
|
||||
"mtime": "2026-10-24T11:47:23.008Z",
|
||||
"size": 88,
|
||||
"path": "../public/_nuxt/builds/meta/3e7fac48-d5e5-406c-8e09-0f6380ec2f4f.json"
|
||||
},
|
||||
"/_nuxt/xGM79_uD.js": {
|
||||
"/_nuxt/Dd1GE-gU.js": {
|
||||
"type": "text/javascript; charset=utf-8",
|
||||
"etag": "\"5023-nhybJtf5A4+tqp5rdRjhnkK+QMY\"",
|
||||
"mtime": "2026-10-24T11:47:18.112Z",
|
||||
"size": 20515,
|
||||
"path": "../public/_nuxt/xGM79_uD.js"
|
||||
"etag": "\"21420-NSMMtehs6gAIMs2sZ84NfSEv/wU\"",
|
||||
"mtime": "2026-10-24T13:11:27.229Z",
|
||||
"size": 136224,
|
||||
"path": "../public/_nuxt/Dd1GE-gU.js"
|
||||
},
|
||||
"/_nuxt/r7-JdZhN.js": {
|
||||
"/_nuxt/DlAPF8TW.js": {
|
||||
"type": "text/javascript; charset=utf-8",
|
||||
"etag": "\"78c-4hmOEQGuRxrwCX/1v0DRIhjwpXE\"",
|
||||
"mtime": "2026-10-24T11:47:18.112Z",
|
||||
"size": 1932,
|
||||
"path": "../public/_nuxt/r7-JdZhN.js"
|
||||
"etag": "\"1e85-YNtOmEmkO1nMcpDM/ztRmg/rRLw\"",
|
||||
"mtime": "2026-10-24T13:11:27.230Z",
|
||||
"size": 7813,
|
||||
"path": "../public/_nuxt/DlAPF8TW.js"
|
||||
},
|
||||
"/_nuxt/DOASWvjr.js": {
|
||||
"type": "text/javascript; charset=utf-8",
|
||||
"etag": "\"3827-3BvUfZznriOnNWFcpi6aALVMpOE\"",
|
||||
"mtime": "2026-10-24T13:11:27.228Z",
|
||||
"size": 14375,
|
||||
"path": "../public/_nuxt/DOASWvjr.js"
|
||||
},
|
||||
"/_nuxt/entry.NJkjIEBJ.css": {
|
||||
"type": "text/css; charset=utf-8",
|
||||
"etag": "\"f954-gFKPvCM4WZHzb2tWqZEkEegmw5w\"",
|
||||
"mtime": "2026-10-24T11:47:18.112Z",
|
||||
"mtime": "2026-10-24T13:11:27.230Z",
|
||||
"size": 63828,
|
||||
"path": "../public/_nuxt/entry.NJkjIEBJ.css"
|
||||
},
|
||||
"/images/logo.png": {
|
||||
"type": "image/png",
|
||||
"etag": "\"45a79-DygL+P0onWMf3/jsNWp1BqEUPE4\"",
|
||||
"mtime": "2026-10-11T12:10:17.609Z",
|
||||
"size": 285305,
|
||||
"path": "../public/images/logo.png"
|
||||
"/_nuxt/r7-JdZhN.js": {
|
||||
"type": "text/javascript; charset=utf-8",
|
||||
"etag": "\"78c-4hmOEQGuRxrwCX/1v0DRIhjwpXE\"",
|
||||
"mtime": "2026-10-24T13:11:27.230Z",
|
||||
"size": 1932,
|
||||
"path": "../public/_nuxt/r7-JdZhN.js"
|
||||
},
|
||||
"/_nuxt/VuesaxIcon.4cYnpGo8.css": {
|
||||
"type": "text/css; charset=utf-8",
|
||||
"etag": "\"dc-Uz1+XA31y3oabrHslWpIJp4Ls74\"",
|
||||
"mtime": "2026-10-24T13:11:27.230Z",
|
||||
"size": 220,
|
||||
"path": "../public/_nuxt/VuesaxIcon.4cYnpGo8.css"
|
||||
},
|
||||
"/_nuxt/builds/latest.json": {
|
||||
"type": "application/json",
|
||||
"etag": "\"47-qofqu0SWsPJasrRW/9JkfJXfC/4\"",
|
||||
"mtime": "2026-10-24T13:11:32.444Z",
|
||||
"size": 71,
|
||||
"path": "../public/_nuxt/builds/latest.json"
|
||||
},
|
||||
"/_nuxt/builds/meta/1fdc7d29-4179-4263-a065-71c6f514739a.json": {
|
||||
"type": "application/json",
|
||||
"etag": "\"58-AOn2PtlkYd83L1490p8deI3f16g\"",
|
||||
"mtime": "2026-10-24T13:11:32.444Z",
|
||||
"size": 88,
|
||||
"path": "../public/_nuxt/builds/meta/1fdc7d29-4179-4263-a065-71c6f514739a.json"
|
||||
},
|
||||
"/_nuxt/DlDWjZRl.js": {
|
||||
"type": "text/javascript; charset=utf-8",
|
||||
"etag": "\"37855-Z6e8Pe/n6IqFkcSEj31zIJ7FhuQ\"",
|
||||
"mtime": "2026-10-24T13:11:27.225Z",
|
||||
"size": 227413,
|
||||
"path": "../public/_nuxt/DlDWjZRl.js"
|
||||
},
|
||||
"/_nuxt/xGM79_uD.js": {
|
||||
"type": "text/javascript; charset=utf-8",
|
||||
"etag": "\"5023-nhybJtf5A4+tqp5rdRjhnkK+QMY\"",
|
||||
"mtime": "2026-10-24T13:11:27.230Z",
|
||||
"size": 20515,
|
||||
"path": "../public/_nuxt/xGM79_uD.js"
|
||||
},
|
||||
"/_nuxt/DoONAYG5.js": {
|
||||
"type": "text/javascript; charset=utf-8",
|
||||
"etag": "\"3b62c-dc9OPIQbgdy0oXHYg6EoUgVy6yc\"",
|
||||
"mtime": "2026-10-24T13:11:27.230Z",
|
||||
"size": 243244,
|
||||
"path": "../public/_nuxt/DoONAYG5.js"
|
||||
},
|
||||
"/_nuxt/D6Ym2dQP.js": {
|
||||
"type": "text/javascript; charset=utf-8",
|
||||
"etag": "\"1241f5-OG41EU7LABNWcdBigZe253LHLTw\"",
|
||||
"mtime": "2026-10-24T11:47:18.110Z",
|
||||
"mtime": "2026-10-24T13:11:27.228Z",
|
||||
"size": 1196533,
|
||||
"path": "../public/_nuxt/D6Ym2dQP.js"
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ const SOCIALS = computed(() =>
|
||||
|
||||
<!-- quick links -->
|
||||
<div class="md:col-span-2">
|
||||
<div class="font-mono text-xs tracking-[0.25em] text-accent">// دسترسی</div>
|
||||
<div class="font-mono text-xs tracking-[0.25em] text-accent">// {{ footer.linksTitle }}</div>
|
||||
<ul class="mt-5 space-y-3">
|
||||
<li v-for="l in QUICK_LINKS" :key="l.href">
|
||||
<a :href="l.href" class="text-sm text-muted transition-colors hover:text-ink">
|
||||
@@ -91,7 +91,7 @@ const SOCIALS = computed(() =>
|
||||
|
||||
<!-- products -->
|
||||
<div class="md:col-span-2">
|
||||
<div class="font-mono text-xs tracking-[0.25em] text-accent">// محصولات</div>
|
||||
<div class="font-mono text-xs tracking-[0.25em] text-accent">// {{ footer.productsTitle }}</div>
|
||||
<ul class="mt-5 space-y-3">
|
||||
<li v-for="p in PRODUCT_LINKS" :key="p.href">
|
||||
<NuxtLink :to="p.href" class="text-sm text-muted transition-colors hover:text-ink">
|
||||
@@ -103,10 +103,19 @@ const SOCIALS = computed(() =>
|
||||
|
||||
<!-- contact -->
|
||||
<div class="md:col-span-3">
|
||||
<div class="font-mono text-xs tracking-[0.25em] text-accent">// ارتباط</div>
|
||||
<div class="font-mono text-xs tracking-[0.25em] text-accent">// {{ footer.contactTitle }}</div>
|
||||
<address class="mt-5 space-y-3 text-sm text-muted not-italic">
|
||||
<p class="leading-7">
|
||||
{{ settings.address.locality }}، {{ settings.address.street }}
|
||||
<a
|
||||
v-if="settings.mapUrl"
|
||||
:href="settings.mapUrl"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="ms-2 text-accent transition-colors hover:text-ink"
|
||||
>
|
||||
نقشه
|
||||
</a>
|
||||
</p>
|
||||
<p class="font-mono tracking-wider" dir="ltr">
|
||||
<a :href="`tel:${settings.phone}`" class="transition-colors hover:text-ink">{{ settings.phoneDisplay }}</a>
|
||||
|
||||
@@ -72,15 +72,15 @@ onBeforeUnmount(() => {
|
||||
<span class="size-1.5 animate-pulse rounded-full bg-emerald-400" />
|
||||
<span class="font-mono">{{ time }}</span>
|
||||
<span class="text-muted/50">|</span>
|
||||
<span>تهران — شیفت {{ shift }}</span>
|
||||
<span>{{ settings.address.locality }} — شیفت {{ shift }}</span>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="/#contact"
|
||||
:href="settings.headerCta.href"
|
||||
class="hidden items-center gap-2 rounded-full bg-accent px-5 py-2.5 text-sm font-bold text-bg transition-all duration-300 hover:bg-accent-soft hover:shadow-[0_0_30px_rgba(255,90,0,0.4)] md:flex"
|
||||
>
|
||||
<VuesaxIcon name="call" class="size-4" />
|
||||
درخواست مشاوره
|
||||
{{ settings.headerCta.label }}
|
||||
</a>
|
||||
|
||||
<button
|
||||
@@ -139,7 +139,7 @@ onBeforeUnmount(() => {
|
||||
<VuesaxIcon name="call" class="size-4 text-accent" />
|
||||
<a :href="`tel:${settings.phone}`" class="font-mono" dir="ltr">{{ settings.phoneDisplay }}</a>
|
||||
</div>
|
||||
<div class="text-xs text-muted">تهران — شیفت {{ shift }}، {{ time }}</div>
|
||||
<div class="text-xs text-muted">{{ settings.address.locality }} — شیفت {{ shift }}، {{ time }}</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
|
||||
@@ -50,14 +50,14 @@ const step = computed(
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<span
|
||||
class="flex size-12 items-center justify-center rounded-xl bg-white shadow-[0_0_28px_rgba(255,90,0,0.3)] ring-1 ring-white/10"
|
||||
class="flex size-20 items-center justify-center overflow-hidden rounded-2xl bg-white shadow-[0_0_28px_rgba(255,90,0,0.3)] ring-1 ring-white/10"
|
||||
>
|
||||
<img
|
||||
src="/images/logo.png"
|
||||
alt="ایمن پیشگامان"
|
||||
width="32"
|
||||
height="32"
|
||||
class="size-8 object-contain"
|
||||
width="80"
|
||||
height="80"
|
||||
class="size-full scale-110 object-contain object-center"
|
||||
/>
|
||||
</span>
|
||||
<span class="font-mono text-xs tracking-[0.35em] text-muted">
|
||||
|
||||
@@ -12,7 +12,13 @@ const content = computed(() => site.sections.contact);
|
||||
const INFO = computed(() => {
|
||||
const s = site.settings;
|
||||
return [
|
||||
{ icon: "location", label: "نشانی", value: `${s.address.locality}، ${s.address.street}` },
|
||||
{
|
||||
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 },
|
||||
@@ -59,6 +65,8 @@ const contact = useContactStore();
|
||||
<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'"
|
||||
>
|
||||
@@ -106,7 +114,7 @@ const contact = useContactStore();
|
||||
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>
|
||||
|
||||
@@ -172,7 +180,7 @@ const contact = useContactStore();
|
||||
</template>
|
||||
<template v-else>
|
||||
<VuesaxIcon name="send-2" class="size-5" />
|
||||
ارسال درخواست مشاوره
|
||||
{{ content.submitLabel }}
|
||||
</template>
|
||||
</AButton>
|
||||
</div>
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
const settings = computed(() => useSiteStore().settings);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLink to="/" class="flex items-center gap-3" aria-label="ایمن پیشگامان">
|
||||
<NuxtLink to="/" class="flex items-center gap-3" :aria-label="settings.name">
|
||||
<span
|
||||
class="flex size-10 items-center justify-center rounded-xl bg-white shadow-[0_0_28px_rgba(10,30,92,0.45)]"
|
||||
class="flex size-11 items-center justify-center overflow-hidden rounded-xl bg-white p-0.5 shadow-[0_0_28px_rgba(10,30,92,0.45)] sm:size-12"
|
||||
>
|
||||
<img
|
||||
src="/images/logo.png"
|
||||
alt="ایمن پیشگامان"
|
||||
width="28"
|
||||
height="28"
|
||||
class="size-7 object-contain"
|
||||
:alt="settings.name"
|
||||
width="48"
|
||||
height="48"
|
||||
class="size-full object-contain object-center"
|
||||
/>
|
||||
</span>
|
||||
<span class="leading-tight">
|
||||
<span class="block text-lg font-black">ایمن پیشگامان</span>
|
||||
<span class="block text-lg font-black">{{ settings.name }}</span>
|
||||
<span class="block font-mono text-[9px] tracking-[0.32em] text-muted">
|
||||
IMEN PISHGAMAN CO.
|
||||
{{ settings.nameEn }}
|
||||
</span>
|
||||
</span>
|
||||
</NuxtLink>
|
||||
|
||||
+18
-3
@@ -6,6 +6,9 @@
|
||||
# gateway (nginx) ─┬─ site domain → web (Nuxt SSR) /api → api /media → minio
|
||||
# └─ admin domain → admin (static) /api → api /media → minio
|
||||
|
||||
# services that other containers reach by name get an imen-* alias: on a shared network (Dokploy) plain
|
||||
# names like web or api would also resolve to other stacks' containers
|
||||
#
|
||||
# fixed project name, so this stack never shares containers or volumes with docker-compose.dev.yml
|
||||
name: imen
|
||||
|
||||
@@ -27,6 +30,9 @@ services:
|
||||
|
||||
minio:
|
||||
image: docker.arvancloud.ir/minio/minio:latest
|
||||
networks:
|
||||
default:
|
||||
aliases: [imen-minio]
|
||||
restart: unless-stopped
|
||||
command: server /data
|
||||
environment:
|
||||
@@ -51,6 +57,9 @@ services:
|
||||
|
||||
api:
|
||||
image: imen-api
|
||||
networks:
|
||||
default:
|
||||
aliases: [imen-api]
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/api.Dockerfile
|
||||
@@ -67,14 +76,14 @@ services:
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
COOKIE_SECURE: ${COOKIE_SECURE:-true}
|
||||
TRUST_PROXY: ${TRUST_PROXY:-2}
|
||||
S3_ENDPOINT: http://minio:9000
|
||||
S3_ENDPOINT: http://imen-minio:9000
|
||||
S3_BUCKET: ${S3_BUCKET:-imen-media}
|
||||
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
|
||||
S3_SECRET_KEY: ${S3_SECRET_KEY}
|
||||
S3_FORCE_PATH_STYLE: "true"
|
||||
S3_ENSURE_BUCKET: "true"
|
||||
MEDIA_PUBLIC_URL: /media
|
||||
WEB_REVALIDATE_URL: http://web:3000/_internal/revalidate
|
||||
WEB_REVALIDATE_URL: http://imen-web:3000/_internal/revalidate
|
||||
REVALIDATE_SECRET: ${REVALIDATE_SECRET}
|
||||
ADMIN_PHONE: ${ADMIN_PHONE}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
|
||||
@@ -82,6 +91,9 @@ services:
|
||||
|
||||
web:
|
||||
image: imen-web
|
||||
networks:
|
||||
default:
|
||||
aliases: [imen-web]
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/web.Dockerfile
|
||||
@@ -89,12 +101,15 @@ services:
|
||||
depends_on:
|
||||
- api
|
||||
environment:
|
||||
NUXT_API_INTERNAL_URL: http://api:4000/api
|
||||
NUXT_API_INTERNAL_URL: http://imen-api:4000/api
|
||||
NUXT_REVALIDATE_SECRET: ${REVALIDATE_SECRET}
|
||||
NUXT_PUBLIC_SITE_URL: https://${SITE_DOMAIN}
|
||||
|
||||
admin:
|
||||
image: imen-admin
|
||||
networks:
|
||||
default:
|
||||
aliases: [imen-admin]
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/admin.Dockerfile
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
# (only ${SITE_DOMAIN}, ${ADMIN_DOMAIN} and ${S3_BUCKET} are replaced; nginx's own $vars stay).
|
||||
# TLS is expected to be terminated in front of this container (host nginx / Traefik / CDN).
|
||||
|
||||
upstream web_upstream { server web:3000; }
|
||||
upstream api_upstream { server api:4000; }
|
||||
upstream admin_upstream { server admin:80; }
|
||||
upstream media_upstream { server minio:9000; }
|
||||
upstream web_upstream { server imen-web:3000; }
|
||||
upstream api_upstream { server imen-api:4000; }
|
||||
upstream admin_upstream { server imen-admin:80; }
|
||||
upstream media_upstream { server imen-minio:9000; }
|
||||
|
||||
# keep the scheme reported by the TLS proxy in front, if any
|
||||
map $http_x_forwarded_proto $forwarded_proto {
|
||||
|
||||
Vendored
+20
@@ -28,6 +28,7 @@ export declare const settingsSectionSchema: z.ZodObject<{
|
||||
locality: z.ZodString;
|
||||
country: z.ZodDefault<z.ZodString>;
|
||||
}, z.core.$strip>;
|
||||
mapUrl: z.ZodDefault<z.ZodString>;
|
||||
workingHours: z.ZodString;
|
||||
openingHours: z.ZodObject<{
|
||||
days: z.ZodArray<z.ZodEnum<{
|
||||
@@ -43,6 +44,10 @@ export declare const settingsSectionSchema: z.ZodObject<{
|
||||
closes: z.ZodString;
|
||||
}, z.core.$strip>;
|
||||
foundingDate: z.ZodString;
|
||||
headerCta: z.ZodDefault<z.ZodObject<{
|
||||
label: z.ZodString;
|
||||
href: z.ZodString;
|
||||
}, z.core.$strip>>;
|
||||
socials: z.ZodArray<z.ZodObject<{
|
||||
type: z.ZodEnum<{
|
||||
instagram: "instagram";
|
||||
@@ -224,6 +229,8 @@ export declare const contactSectionSchema: z.ZodObject<{
|
||||
formTitle: z.ZodString;
|
||||
successTitle: z.ZodString;
|
||||
successText: z.ZodString;
|
||||
submitLabel: z.ZodDefault<z.ZodString>;
|
||||
resetLabel: z.ZodDefault<z.ZodString>;
|
||||
}, z.core.$strip>;
|
||||
export declare const footerSectionSchema: z.ZodObject<{
|
||||
enabled: z.ZodDefault<z.ZodBoolean>;
|
||||
@@ -231,6 +238,9 @@ export declare const footerSectionSchema: z.ZodObject<{
|
||||
copyright: z.ZodString;
|
||||
certLine: z.ZodDefault<z.ZodString>;
|
||||
emergencyNote: z.ZodDefault<z.ZodString>;
|
||||
linksTitle: z.ZodDefault<z.ZodString>;
|
||||
productsTitle: z.ZodDefault<z.ZodString>;
|
||||
contactTitle: z.ZodDefault<z.ZodString>;
|
||||
}, z.core.$strip>;
|
||||
export declare const sectionSchemas: {
|
||||
readonly settings: z.ZodObject<{
|
||||
@@ -246,6 +256,7 @@ export declare const sectionSchemas: {
|
||||
locality: z.ZodString;
|
||||
country: z.ZodDefault<z.ZodString>;
|
||||
}, z.core.$strip>;
|
||||
mapUrl: z.ZodDefault<z.ZodString>;
|
||||
workingHours: z.ZodString;
|
||||
openingHours: z.ZodObject<{
|
||||
days: z.ZodArray<z.ZodEnum<{
|
||||
@@ -261,6 +272,10 @@ export declare const sectionSchemas: {
|
||||
closes: z.ZodString;
|
||||
}, z.core.$strip>;
|
||||
foundingDate: z.ZodString;
|
||||
headerCta: z.ZodDefault<z.ZodObject<{
|
||||
label: z.ZodString;
|
||||
href: z.ZodString;
|
||||
}, z.core.$strip>>;
|
||||
socials: z.ZodArray<z.ZodObject<{
|
||||
type: z.ZodEnum<{
|
||||
instagram: "instagram";
|
||||
@@ -442,6 +457,8 @@ export declare const sectionSchemas: {
|
||||
formTitle: z.ZodString;
|
||||
successTitle: z.ZodString;
|
||||
successText: z.ZodString;
|
||||
submitLabel: z.ZodDefault<z.ZodString>;
|
||||
resetLabel: z.ZodDefault<z.ZodString>;
|
||||
}, z.core.$strip>;
|
||||
readonly footer: z.ZodObject<{
|
||||
enabled: z.ZodDefault<z.ZodBoolean>;
|
||||
@@ -449,6 +466,9 @@ export declare const sectionSchemas: {
|
||||
copyright: z.ZodString;
|
||||
certLine: z.ZodDefault<z.ZodString>;
|
||||
emergencyNote: z.ZodDefault<z.ZodString>;
|
||||
linksTitle: z.ZodDefault<z.ZodString>;
|
||||
productsTitle: z.ZodDefault<z.ZodString>;
|
||||
contactTitle: z.ZodDefault<z.ZodString>;
|
||||
}, z.core.$strip>;
|
||||
};
|
||||
export type SectionKey = keyof typeof sectionSchemas;
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../src/sections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;GAKG;AAEH,eAAO,MAAM,SAAS,yFAQZ,CAAC;AAEX,eAAO,MAAM,YAAY,+HAYf,CAAC;AACX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAKvD,eAAO,MAAM,iBAAiB;;;;;iBAM5B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEhE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6BhC,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAa5B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;iBAG5B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;iBAY7B,CAAC;AAEH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;iBAKrC,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;iBAQ7B,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;iBAahC,CAAC;AAEH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;iBAMjC,CAAC;AAEH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;iBAalC,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;iBAIpC,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;iBAS/B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;iBAM9B,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAajB,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,cAAc,CAAC;AACrD,eAAO,MAAM,YAAY,EAAkC,UAAU,EAAE,CAAC;AACxE,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,UAAU,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpF,MAAM,MAAM,YAAY,GAAG;KAAG,CAAC,IAAI,UAAU,GAAG,WAAW,CAAC,CAAC,CAAC;CAAE,CAAC;AACjE,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;AAEnD,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,GAAG,IAAI,UAAmC,CAAC;AAEtF,yCAAyC;AACzC,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,UAAU,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAa9E,CAAC"}
|
||||
{"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../src/sections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;GAKG;AAEH,eAAO,MAAM,SAAS,yFAQZ,CAAC;AAEX,eAAO,MAAM,YAAY,+HAYf,CAAC;AACX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAKvD,eAAO,MAAM,iBAAiB;;;;;iBAM5B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEhE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiChC,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAa5B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;iBAG5B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;iBAY7B,CAAC;AAEH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;iBAKrC,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;iBAQ7B,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;iBAahC,CAAC;AAEH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;iBAMjC,CAAC;AAEH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;iBAalC,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;iBAIpC,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;iBAa/B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;iBAU9B,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAajB,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,cAAc,CAAC;AACrD,eAAO,MAAM,YAAY,EAAkC,UAAU,EAAE,CAAC;AACxE,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,UAAU,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpF,MAAM,MAAM,YAAY,GAAG;KAAG,CAAC,IAAI,UAAU,GAAG,WAAW,CAAC,CAAC,CAAC;CAAE,CAAC;AACjE,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;AAEnD,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,GAAG,IAAI,UAAmC,CAAC;AAEtF,yCAAyC;AACzC,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,UAAU,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAa9E,CAAC"}
|
||||
Vendored
+14
-2
@@ -53,6 +53,8 @@ export const settingsSectionSchema = z.object({
|
||||
locality: req(80),
|
||||
country: z.string().trim().length(2).default("IR"),
|
||||
}),
|
||||
/** Google Maps / Neshan / etc. share link shown next to the address */
|
||||
mapUrl: text(500),
|
||||
/** human-readable working hours, e.g. «شنبه تا پنجشنبه — ۸:۰۰ تا ۱۷:۰۰» */
|
||||
workingHours: req(120),
|
||||
openingHours: z.object({
|
||||
@@ -61,6 +63,8 @@ export const settingsSectionSchema = z.object({
|
||||
closes: z.string().regex(/^\d{2}:\d{2}$/, "قالب ساعت باید HH:MM باشد"),
|
||||
}),
|
||||
foundingDate: z.string().regex(/^\d{4}$/, "سال میلادی چهاررقمی"),
|
||||
/** sticky header CTA (e.g. «درخواست مشاوره») */
|
||||
headerCta: linkSchema.default({ label: "درخواست مشاوره", href: "/#contact" }),
|
||||
socials: z.array(z.object({ type: z.enum(SOCIAL_TYPES), url: req(300) })).max(12),
|
||||
ogImage: imageRefSchema.extend({
|
||||
width: z.number().int().positive().nullable().default(null),
|
||||
@@ -156,6 +160,10 @@ export const contactSectionSchema = z.object({
|
||||
formTitle: req(160),
|
||||
successTitle: req(80),
|
||||
successText: req(300),
|
||||
/** submit button on the contact form */
|
||||
submitLabel: req(80).default("ارسال درخواست مشاوره"),
|
||||
/** button shown after a successful submit */
|
||||
resetLabel: req(80).default("ثبت درخواست جدید"),
|
||||
});
|
||||
export const footerSectionSchema = z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
@@ -163,6 +171,10 @@ export const footerSectionSchema = z.object({
|
||||
copyright: req(160),
|
||||
certLine: text(160),
|
||||
emergencyNote: text(80),
|
||||
/** column headings under the brand block */
|
||||
linksTitle: req(40).default("دسترسی"),
|
||||
productsTitle: req(40).default("محصولات"),
|
||||
contactTitle: req(40).default("ارتباط"),
|
||||
});
|
||||
export const sectionSchemas = {
|
||||
settings: settingsSectionSchema,
|
||||
@@ -192,7 +204,7 @@ export const SECTION_LABELS = {
|
||||
standards: { title: "استانداردها", desc: "استانداردها و گواهینامهها" },
|
||||
industries: { title: "صنایع", desc: "صنایع تحت پوشش و تصویر هرکدام" },
|
||||
testimonials: { title: "نظرات مشتریان", desc: "نقلقولهای مشتریان" },
|
||||
contact: { title: "تماس با ما", desc: "عنوانها و متنهای فرم تماس" },
|
||||
footer: { title: "فوتر", desc: "متن معرفی، کپیرایت و توضیحات پایین صفحه" },
|
||||
contact: { title: "تماس با ما", desc: "عنوانها، متنهای فرم، و ویرایش نشانی و اطلاعات تماس" },
|
||||
footer: { title: "فوتر", desc: "متن معرفی، عناوین ستونها، کپیرایت و توضیحات پایین صفحه" },
|
||||
};
|
||||
//# sourceMappingURL=sections.js.map
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -61,6 +61,8 @@ export const settingsSectionSchema = z.object({
|
||||
locality: req(80),
|
||||
country: z.string().trim().length(2).default("IR"),
|
||||
}),
|
||||
/** Google Maps / Neshan / etc. share link shown next to the address */
|
||||
mapUrl: text(500),
|
||||
/** human-readable working hours, e.g. «شنبه تا پنجشنبه — ۸:۰۰ تا ۱۷:۰۰» */
|
||||
workingHours: req(120),
|
||||
openingHours: z.object({
|
||||
@@ -69,6 +71,8 @@ export const settingsSectionSchema = z.object({
|
||||
closes: z.string().regex(/^\d{2}:\d{2}$/, "قالب ساعت باید HH:MM باشد"),
|
||||
}),
|
||||
foundingDate: z.string().regex(/^\d{4}$/, "سال میلادی چهاررقمی"),
|
||||
/** sticky header CTA (e.g. «درخواست مشاوره») */
|
||||
headerCta: linkSchema.default({ label: "درخواست مشاوره", href: "/#contact" }),
|
||||
socials: z.array(z.object({ type: z.enum(SOCIAL_TYPES), url: req(300) })).max(12),
|
||||
ogImage: imageRefSchema.extend({
|
||||
width: z.number().int().positive().nullable().default(null),
|
||||
@@ -180,6 +184,10 @@ export const contactSectionSchema = z.object({
|
||||
formTitle: req(160),
|
||||
successTitle: req(80),
|
||||
successText: req(300),
|
||||
/** submit button on the contact form */
|
||||
submitLabel: req(80).default("ارسال درخواست مشاوره"),
|
||||
/** button shown after a successful submit */
|
||||
resetLabel: req(80).default("ثبت درخواست جدید"),
|
||||
});
|
||||
|
||||
export const footerSectionSchema = z.object({
|
||||
@@ -188,6 +196,10 @@ export const footerSectionSchema = z.object({
|
||||
copyright: req(160),
|
||||
certLine: text(160),
|
||||
emergencyNote: text(80),
|
||||
/** column headings under the brand block */
|
||||
linksTitle: req(40).default("دسترسی"),
|
||||
productsTitle: req(40).default("محصولات"),
|
||||
contactTitle: req(40).default("ارتباط"),
|
||||
});
|
||||
|
||||
export const sectionSchemas = {
|
||||
@@ -225,6 +237,6 @@ export const SECTION_LABELS: Record<SectionKey, { title: string; desc: string }>
|
||||
standards: { title: "استانداردها", desc: "استانداردها و گواهینامهها" },
|
||||
industries: { title: "صنایع", desc: "صنایع تحت پوشش و تصویر هرکدام" },
|
||||
testimonials: { title: "نظرات مشتریان", desc: "نقلقولهای مشتریان" },
|
||||
contact: { title: "تماس با ما", desc: "عنوانها و متنهای فرم تماس" },
|
||||
footer: { title: "فوتر", desc: "متن معرفی، کپیرایت و توضیحات پایین صفحه" },
|
||||
contact: { title: "تماس با ما", desc: "عنوانها، متنهای فرم، و ویرایش نشانی و اطلاعات تماس" },
|
||||
footer: { title: "فوتر", desc: "متن معرفی، عناوین ستونها، کپیرایت و توضیحات پایین صفحه" },
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user