2026-09-18 12:54:45 +03:30
|
|
|
|
<!--
|
2026-10-04 13:57:25 +03:30
|
|
|
|
Store sign-up, redesigned as a two-step form:
|
|
|
|
|
|
1. اطلاعات شما — first/last name, mobile
|
|
|
|
|
|
2. اطلاعات فروشگاه — store name, store phone, city, address
|
|
|
|
|
|
followed by an in-page "request received" state.
|
2026-09-18 12:54:45 +03:30
|
|
|
|
|
2026-10-04 13:57:25 +03:30
|
|
|
|
Why two steps: seven fields in one column read as a wall on a phone. Split
|
|
|
|
|
|
by who they describe, each step fits one screen, the progress bar sets the
|
|
|
|
|
|
expectation, and the CTA sits in a sticky footer so it is always in reach.
|
2026-09-18 12:54:45 +03:30
|
|
|
|
|
2026-10-04 13:57:25 +03:30
|
|
|
|
Ant's Form only validates the FormItems currently mounted, so a submit on
|
|
|
|
|
|
step 1 validates just step 1's fields; `onFinish` then advances instead of
|
|
|
|
|
|
registering. Enter on the keyboard therefore behaves like the CTA.
|
|
|
|
|
|
|
|
|
|
|
|
Numeric fields are `type="tel"` + `inputmode="numeric"` (a real number input
|
|
|
|
|
|
drops the leading 0 and shows spinners), and Persian/Arabic digits are
|
|
|
|
|
|
converted to Latin as the user types, so the validators accept them.
|
|
|
|
|
|
|
|
|
|
|
|
STUBBED, unchanged from before: legacy's POST /major/register has no
|
|
|
|
|
|
counterpart in apps/api yet. Submitting shows the "awaiting review" state
|
|
|
|
|
|
without persisting anything — the copy says the request is under review,
|
|
|
|
|
|
which is what legacy said too, so it does not claim an account exists.
|
2026-09-18 12:54:45 +03:30
|
|
|
|
-->
|
|
|
|
|
|
<template>
|
2026-10-04 13:57:25 +03:30
|
|
|
|
<div class="reg">
|
|
|
|
|
|
<PageBar title="ثبت نام" :back-action="goBack" />
|
2026-09-18 12:54:45 +03:30
|
|
|
|
|
2026-10-04 13:57:25 +03:30
|
|
|
|
<!-- Success -->
|
|
|
|
|
|
<Transition name="reg-fade" mode="out-in">
|
|
|
|
|
|
<container v-if="done" key="done" class="reg__body reg__done">
|
|
|
|
|
|
<div class="reg__doneMark" aria-hidden="true">
|
|
|
|
|
|
<span class="reg__doneRing" />
|
|
|
|
|
|
<Icon icon="vuesax-linear:tick-circle" size="40" />
|
2026-09-18 12:54:45 +03:30
|
|
|
|
</div>
|
2026-10-04 13:57:25 +03:30
|
|
|
|
<h2 class="reg__doneTitle">درخواست شما ثبت شد</h2>
|
|
|
|
|
|
<p class="reg__doneText">
|
|
|
|
|
|
اطلاعات فروشگاه {{ formModel.store_name }} برای کارشناسان ما ارسال شد. پس از بررسی و تایید، میتوانید با
|
|
|
|
|
|
شماره <span class="reg__ltr">{{ formModel.mobile }}</span> وارد شوید.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div class="reg__doneActions">
|
|
|
|
|
|
<button type="button" class="reg__cta" @click="router.push({ name: 'home' })">بازگشت به صفحه اصلی</button>
|
|
|
|
|
|
<button type="button" class="reg__ghost" @click="emit('register', false)">رفتن به صفحه ورود</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</container>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Form -->
|
|
|
|
|
|
<container v-else key="form" class="reg__body">
|
|
|
|
|
|
<header class="reg__head">
|
|
|
|
|
|
<div class="reg__steps" role="progressbar" :aria-valuenow="step" aria-valuemin="1" aria-valuemax="2">
|
|
|
|
|
|
<span class="reg__seg is-on" />
|
|
|
|
|
|
<span class="reg__seg" :class="{ 'is-on': step === 2 }" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p class="reg__stepLabel">مرحله {{ step === 1 ? '۱' : '۲' }} از ۲</p>
|
|
|
|
|
|
<h2 class="reg__title">{{ step === 1 ? 'اطلاعات شما' : 'اطلاعات فروشگاه' }}</h2>
|
|
|
|
|
|
<p class="reg__sub">
|
|
|
|
|
|
{{
|
|
|
|
|
|
step === 1
|
|
|
|
|
|
? 'برای ساخت حساب فروشگاه، ابتدا خودتان را معرفی کنید.'
|
|
|
|
|
|
: 'این اطلاعات برای ارسال سفارش و تایید حساب استفاده میشود.'
|
|
|
|
|
|
}}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<Form ref="formRef" :model="formModel" layout="vertical" class="reg__form" @finish="onFinish">
|
|
|
|
|
|
<Transition :name="step === 2 ? 'reg-next' : 'reg-prev'" mode="out-in">
|
|
|
|
|
|
<!-- Step 1 -->
|
|
|
|
|
|
<div v-if="step === 1" key="s1" class="reg__fields">
|
|
|
|
|
|
<div class="reg__row">
|
|
|
|
|
|
<FormItem name="fname" label="نام" :rules="[need('نام خود')]">
|
|
|
|
|
|
<Input
|
|
|
|
|
|
v-model:value="formModel.fname"
|
|
|
|
|
|
placeholder="مثلاً علی"
|
|
|
|
|
|
autocomplete="given-name"
|
|
|
|
|
|
enterkeyhint="next"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</FormItem>
|
|
|
|
|
|
<FormItem name="lname" label="نام خانوادگی" :rules="[need('نام خانوادگی')]">
|
|
|
|
|
|
<Input
|
|
|
|
|
|
v-model:value="formModel.lname"
|
|
|
|
|
|
placeholder="مثلاً محمدی"
|
|
|
|
|
|
autocomplete="family-name"
|
|
|
|
|
|
enterkeyhint="next"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</FormItem>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<FormItem
|
|
|
|
|
|
name="mobile"
|
|
|
|
|
|
label="شماره موبایل"
|
|
|
|
|
|
:rules="[need('شماره موبایل'), { pattern: /^09\d{9}$/, message: 'شماره موبایل باید ۱۱ رقم و با ۰۹ شروع شود' }]"
|
|
|
|
|
|
extra="کد ورود به این شماره پیامک میشود."
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
:value="formModel.mobile"
|
|
|
|
|
|
class="reg__num"
|
|
|
|
|
|
type="tel"
|
|
|
|
|
|
inputmode="numeric"
|
|
|
|
|
|
:maxlength="11"
|
|
|
|
|
|
placeholder="09123456789"
|
|
|
|
|
|
autocomplete="tel"
|
|
|
|
|
|
enterkeyhint="next"
|
|
|
|
|
|
@update:value="(v: string) => (formModel.mobile = digits(v))"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #prefix><Icon icon="vuesax-linear:mobile" size="18" /></template>
|
|
|
|
|
|
</Input>
|
|
|
|
|
|
</FormItem>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Step 2 -->
|
|
|
|
|
|
<div v-else key="s2" class="reg__fields">
|
|
|
|
|
|
<FormItem name="store_name" label="نام فروشگاه" :rules="[need('نام فروشگاه')]">
|
|
|
|
|
|
<Input
|
|
|
|
|
|
v-model:value="formModel.store_name"
|
|
|
|
|
|
placeholder="مثلاً گالری طلای آفتاب"
|
|
|
|
|
|
autocomplete="organization"
|
|
|
|
|
|
enterkeyhint="next"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #prefix><Icon icon="vuesax-linear:shop" size="18" /></template>
|
|
|
|
|
|
</Input>
|
|
|
|
|
|
</FormItem>
|
|
|
|
|
|
|
|
|
|
|
|
<FormItem
|
|
|
|
|
|
name="phone"
|
|
|
|
|
|
label="تلفن فروشگاه"
|
|
|
|
|
|
:rules="[need('تلفن فروشگاه'), { pattern: /^(0\d{10}|\d{8})$/, message: 'تلفن را با کد شهر وارد کنید، مثلاً 02112345678' }]"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
:value="formModel.phone"
|
|
|
|
|
|
class="reg__num"
|
|
|
|
|
|
type="tel"
|
|
|
|
|
|
inputmode="numeric"
|
|
|
|
|
|
:maxlength="11"
|
|
|
|
|
|
placeholder="02112345678"
|
|
|
|
|
|
autocomplete="tel-national"
|
|
|
|
|
|
enterkeyhint="next"
|
|
|
|
|
|
@update:value="(v: string) => (formModel.phone = digits(v))"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #prefix><Icon icon="vuesax-linear:call" size="18" /></template>
|
|
|
|
|
|
</Input>
|
|
|
|
|
|
</FormItem>
|
|
|
|
|
|
|
|
|
|
|
|
<FormItem name="city" label="شهر" :rules="[need('شهر')]">
|
|
|
|
|
|
<Input
|
|
|
|
|
|
v-model:value="formModel.city"
|
|
|
|
|
|
placeholder="مثلاً تهران"
|
|
|
|
|
|
autocomplete="address-level2"
|
|
|
|
|
|
enterkeyhint="next"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #prefix><Icon icon="vuesax-linear:buildings" size="18" /></template>
|
|
|
|
|
|
</Input>
|
|
|
|
|
|
</FormItem>
|
|
|
|
|
|
|
|
|
|
|
|
<FormItem name="address" label="آدرس پستی" :rules="[need('آدرس فروشگاه')]">
|
|
|
|
|
|
<Textarea
|
|
|
|
|
|
v-model:value="formModel.address"
|
|
|
|
|
|
:rows="3"
|
|
|
|
|
|
placeholder="خیابان، کوچه، پلاک، واحد"
|
|
|
|
|
|
autocomplete="street-address"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</FormItem>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="reg__note">
|
|
|
|
|
|
<Icon icon="vuesax-linear:shield-tick" size="18" />
|
|
|
|
|
|
<span>حساب شما پس از بررسی اطلاعات توسط کارشناسان فعال میشود.</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Transition>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Sticky action bar; inside the Form so it submits it. -->
|
|
|
|
|
|
<footer class="reg__footer">
|
|
|
|
|
|
<div class="reg__footerInner">
|
|
|
|
|
|
<button type="submit" class="reg__cta" :class="{ 'is-loading': loading }" :disabled="loading">
|
2026-10-04 16:18:56 +03:30
|
|
|
|
<span v-if="loading" class="reg__spinner keep-motion" aria-hidden="true" />
|
2026-10-04 13:57:25 +03:30
|
|
|
|
<span>{{ step === 1 ? 'ادامه' : loading ? 'در حال ثبت…' : 'ثبت درخواست' }}</span>
|
|
|
|
|
|
<Icon v-if="step === 1" icon="vuesax-linear:arrow-left" size="18" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<p v-if="step === 1" class="reg__switch">
|
|
|
|
|
|
حساب دارید؟
|
|
|
|
|
|
<button type="button" @click="emit('register', false)">وارد شوید</button>
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</container>
|
|
|
|
|
|
</Transition>
|
2026-09-18 12:54:45 +03:30
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { reactive, ref } from 'vue'
|
|
|
|
|
|
import { useRouter } from 'vue-router'
|
|
|
|
|
|
import { Form, FormItem, Input, Textarea } from 'ant-design-vue'
|
|
|
|
|
|
import Icon from '../../components/icon/Icon.vue'
|
|
|
|
|
|
import PageBar from '../../components/ui/PageBar.vue'
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{ (e: 'register', value: boolean): void }>()
|
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
|
2026-10-04 13:57:25 +03:30
|
|
|
|
const formRef = ref()
|
|
|
|
|
|
const step = ref<1 | 2>(1)
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
const done = ref(false)
|
2026-09-18 12:54:45 +03:30
|
|
|
|
|
|
|
|
|
|
const formModel = reactive({
|
|
|
|
|
|
fname: undefined as string | undefined,
|
|
|
|
|
|
lname: undefined as string | undefined,
|
|
|
|
|
|
mobile: undefined as string | undefined,
|
|
|
|
|
|
store_name: undefined as string | undefined,
|
|
|
|
|
|
phone: undefined as string | undefined,
|
|
|
|
|
|
city: undefined as string | undefined,
|
2026-10-04 13:57:25 +03:30
|
|
|
|
address: undefined as string | undefined,
|
2026-09-18 12:54:45 +03:30
|
|
|
|
})
|
|
|
|
|
|
|
2026-10-04 13:57:25 +03:30
|
|
|
|
const need = (what: string) => ({ required: true, whitespace: true, message: `لطفاً ${what} را وارد کنید` })
|
|
|
|
|
|
|
|
|
|
|
|
// Persian (۰-۹) and Arabic (٠-٩) digits → Latin, everything else dropped.
|
|
|
|
|
|
function digits(v: string): string {
|
|
|
|
|
|
return (v ?? '')
|
|
|
|
|
|
.replace(/[۰-۹]/g, (d) => String(d.charCodeAt(0) - 0x06f0))
|
|
|
|
|
|
.replace(/[٠-٩]/g, (d) => String(d.charCodeAt(0) - 0x0660))
|
|
|
|
|
|
.replace(/\D/g, '')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function goBack() {
|
|
|
|
|
|
if (done.value) router.push({ name: 'home' })
|
|
|
|
|
|
else if (step.value === 2) step.value = 1
|
|
|
|
|
|
else emit('register', false)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function onFinish() {
|
|
|
|
|
|
if (step.value === 1) {
|
|
|
|
|
|
step.value = 2
|
|
|
|
|
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-09-18 12:54:45 +03:30
|
|
|
|
try {
|
2026-10-04 13:57:25 +03:30
|
|
|
|
loading.value = true
|
2026-09-18 12:54:45 +03:30
|
|
|
|
// Stubbed — no create-customer endpoint exists in apps/api yet.
|
2026-10-04 13:57:25 +03:30
|
|
|
|
await new Promise((r) => setTimeout(r, 600))
|
|
|
|
|
|
done.value = true
|
|
|
|
|
|
window.scrollTo({ top: 0 })
|
2026-09-18 12:54:45 +03:30
|
|
|
|
} finally {
|
2026-10-04 13:57:25 +03:30
|
|
|
|
loading.value = false
|
2026-09-18 12:54:45 +03:30
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
2026-10-04 13:57:25 +03:30
|
|
|
|
.reg {
|
2026-09-18 12:54:45 +03:30
|
|
|
|
min-height: 100vh;
|
2026-10-04 16:18:56 +03:30
|
|
|
|
background: @page;
|
2026-09-18 12:54:45 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-04 13:57:25 +03:30
|
|
|
|
.reg__body {
|
|
|
|
|
|
padding-top: 78px;
|
|
|
|
|
|
// Room for the sticky footer.
|
|
|
|
|
|
padding-bottom: calc(150px + env(safe-area-inset-bottom, 0px));
|
2026-09-18 12:54:45 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-04 13:57:25 +03:30
|
|
|
|
/* ---------- Header + progress ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.reg__head {
|
|
|
|
|
|
padding: 6px 2px 22px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg__steps {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg__seg {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
height: 4px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
background: @hairline;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
|
|
&::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
inset: 0;
|
|
|
|
|
|
border-radius: inherit;
|
|
|
|
|
|
background: @wine;
|
|
|
|
|
|
transform: scaleX(0);
|
|
|
|
|
|
transform-origin: right center;
|
|
|
|
|
|
transition: transform 0.5s cubic-bezier(0.65, 0, 0.35, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.is-on::after {
|
|
|
|
|
|
transform: scaleX(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg__stepLabel {
|
|
|
|
|
|
margin: 14px 0 0;
|
|
|
|
|
|
font-size: 11.5px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: @wine;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg__title {
|
|
|
|
|
|
margin: 4px 0 0;
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
color: @ink;
|
|
|
|
|
|
letter-spacing: -0.2px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg__sub {
|
|
|
|
|
|
margin: 6px 0 0;
|
|
|
|
|
|
font-size: 12.5px;
|
|
|
|
|
|
line-height: 1.8;
|
|
|
|
|
|
color: @inkMuted;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- Fields ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
// minmax(0, 1fr): a plain 1fr column won't shrink below its content's
|
|
|
|
|
|
// min-width, which pushed the second name field off the screen.
|
|
|
|
|
|
.reg__row {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg__form {
|
|
|
|
|
|
// Force label-above-input. Ant's FormItem is a wrapping flex row, so in a
|
|
|
|
|
|
// narrow cell its label and control ended up side by side.
|
|
|
|
|
|
:deep(.ant-form-item) {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
flex-wrap: nowrap;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-form-item-label),
|
|
|
|
|
|
:deep(.ant-form-item-control) {
|
|
|
|
|
|
flex: none;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-form-item-label) {
|
|
|
|
|
|
padding: 0 0 6px;
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-form-item-label > label) {
|
|
|
|
|
|
height: auto;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: @inkSoft;
|
|
|
|
|
|
|
|
|
|
|
|
// Ant's required asterisk — every field here is required, so it's noise.
|
|
|
|
|
|
&::before {
|
|
|
|
|
|
display: none !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-input),
|
|
|
|
|
|
:deep(.ant-input-affix-wrapper) {
|
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
|
border: 1.5px solid @hairline;
|
|
|
|
|
|
background: @canvasSoft;
|
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: @ink;
|
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
|
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-input) {
|
|
|
|
|
|
min-height: 50px;
|
|
|
|
|
|
padding: 12px 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-input-affix-wrapper) {
|
|
|
|
|
|
min-height: 50px;
|
|
|
|
|
|
padding: 0 14px;
|
|
|
|
|
|
|
|
|
|
|
|
.ant-input {
|
|
|
|
|
|
min-height: 46px;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(textarea.ant-input) {
|
|
|
|
|
|
min-height: 96px;
|
|
|
|
|
|
line-height: 1.8;
|
|
|
|
|
|
resize: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-input-prefix) {
|
|
|
|
|
|
margin-inline-end: 10px;
|
|
|
|
|
|
color: @inkFaint;
|
|
|
|
|
|
transition: color 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-input::placeholder) {
|
2026-10-04 16:18:56 +03:30
|
|
|
|
color: @inkFaint;
|
2026-10-04 13:57:25 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-input:hover),
|
|
|
|
|
|
:deep(.ant-input-affix-wrapper:hover) {
|
2026-10-04 16:18:56 +03:30
|
|
|
|
border-color: @hairlineStrong;
|
2026-10-04 13:57:25 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-input:focus),
|
|
|
|
|
|
:deep(.ant-input-focused),
|
|
|
|
|
|
:deep(.ant-input-affix-wrapper-focused) {
|
|
|
|
|
|
border-color: @wine;
|
|
|
|
|
|
background: @canvas;
|
2026-10-04 16:18:56 +03:30
|
|
|
|
box-shadow: 0 0 0 4px alpha(@wine, 10%);
|
2026-10-04 13:57:25 +03:30
|
|
|
|
|
|
|
|
|
|
.ant-input-prefix {
|
|
|
|
|
|
color: @wine;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-input-affix-wrapper-focused .ant-input-prefix) {
|
|
|
|
|
|
color: @wine;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Errors
|
|
|
|
|
|
:deep(.ant-form-item-has-error .ant-input),
|
|
|
|
|
|
:deep(.ant-form-item-has-error .ant-input-affix-wrapper) {
|
|
|
|
|
|
border-color: @red !important;
|
2026-10-04 16:18:56 +03:30
|
|
|
|
background: alpha(@red, 3%);
|
2026-10-04 13:57:25 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-form-item-has-error .ant-input:focus),
|
|
|
|
|
|
:deep(.ant-form-item-has-error .ant-input-affix-wrapper-focused) {
|
2026-10-04 16:18:56 +03:30
|
|
|
|
box-shadow: 0 0 0 4px alpha(@red, 10%) !important;
|
2026-10-04 13:57:25 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-form-item-has-error .ant-input-affix-wrapper .ant-input) {
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
border: 0 !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-form-item-explain),
|
|
|
|
|
|
:deep(.ant-form-item-extra) {
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
margin-top: 6px;
|
|
|
|
|
|
font-size: 11.5px;
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-form-item-extra) {
|
|
|
|
|
|
color: @inkFaint;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Ant's own feedback icons duplicate the red border.
|
|
|
|
|
|
:deep(.ant-form-item-children-icon) {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Numbers are typed left-to-right but stay aligned with the RTL labels.
|
|
|
|
|
|
.reg__num :deep(input) {
|
|
|
|
|
|
direction: ltr;
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
letter-spacing: 0.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg__note {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
|
padding: 12px 14px;
|
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
|
background: @wineWash;
|
|
|
|
|
|
color: @wine;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
line-height: 1.8;
|
|
|
|
|
|
|
|
|
|
|
|
:deep(svg) {
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
margin-top: 2px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- Sticky footer ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.reg__footer {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
z-index: 15;
|
|
|
|
|
|
padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
|
2026-10-04 16:18:56 +03:30
|
|
|
|
background: alpha(@canvas, 86%);
|
2026-10-04 13:57:25 +03:30
|
|
|
|
-webkit-backdrop-filter: blur(16px) saturate(180%);
|
|
|
|
|
|
backdrop-filter: blur(16px) saturate(180%);
|
|
|
|
|
|
border-top: 1px solid @hairline;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg__footerInner {
|
|
|
|
|
|
max-width: 736px;
|
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg__cta {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
min-height: 52px;
|
|
|
|
|
|
padding: 0 18px;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 800;
|
2026-10-04 16:18:56 +03:30
|
|
|
|
color: @onInk;
|
2026-10-04 13:57:25 +03:30
|
|
|
|
background: @ink;
|
2026-10-04 16:18:56 +03:30
|
|
|
|
box-shadow: 0 10px 22px -12px alpha(@ink, 70%);
|
2026-10-04 13:57:25 +03:30
|
|
|
|
transition: transform 0.15s ease, opacity 0.2s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&:active {
|
|
|
|
|
|
transform: scale(0.98);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.is-loading,
|
|
|
|
|
|
&:disabled {
|
|
|
|
|
|
opacity: 0.75;
|
|
|
|
|
|
cursor: default;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(svg) {
|
|
|
|
|
|
transition: transform 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:active :deep(svg) {
|
|
|
|
|
|
transform: translateX(-3px);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg__spinner {
|
|
|
|
|
|
width: 16px;
|
|
|
|
|
|
height: 16px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
border: 2px solid fade(#fff, 35%);
|
|
|
|
|
|
border-top-color: #fff;
|
|
|
|
|
|
animation: reg-spin 0.7s linear infinite;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg__ghost {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
min-height: 50px;
|
|
|
|
|
|
border: 1.5px solid @hairline;
|
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
|
background: @canvas;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
font-size: 13.5px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: @ink;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg__switch {
|
|
|
|
|
|
margin: 10px 0 0;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: @inkMuted;
|
|
|
|
|
|
|
|
|
|
|
|
button {
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
padding: 0 3px;
|
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
color: @wine;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- Success ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.reg__done {
|
2026-09-18 12:54:45 +03:30
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
text-align: center;
|
2026-10-04 13:57:25 +03:30
|
|
|
|
padding-top: 120px;
|
|
|
|
|
|
padding-bottom: 40px;
|
2026-09-18 12:54:45 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-04 13:57:25 +03:30
|
|
|
|
.reg__doneMark {
|
|
|
|
|
|
position: relative;
|
2026-09-18 12:54:45 +03:30
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
2026-10-04 13:57:25 +03:30
|
|
|
|
width: 88px;
|
|
|
|
|
|
height: 88px;
|
|
|
|
|
|
border-radius: 26px;
|
|
|
|
|
|
color: @green;
|
2026-10-04 16:18:56 +03:30
|
|
|
|
background: alpha(@green, 10%);
|
2026-10-04 13:57:25 +03:30
|
|
|
|
animation: reg-pop 0.6s cubic-bezier(0.2, 1.5, 0.4, 1) both;
|
|
|
|
|
|
|
|
|
|
|
|
:deep(svg) {
|
|
|
|
|
|
animation: reg-pop 0.6s 0.15s cubic-bezier(0.2, 1.6, 0.4, 1) both;
|
|
|
|
|
|
}
|
2026-09-18 12:54:45 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-04 13:57:25 +03:30
|
|
|
|
.reg__doneRing {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
inset: 0;
|
|
|
|
|
|
border-radius: inherit;
|
|
|
|
|
|
border: 2px solid @green;
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
animation: reg-ring 1.1s 0.3s ease-out both;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg__doneTitle {
|
|
|
|
|
|
margin: 22px 0 0;
|
|
|
|
|
|
font-size: 19px;
|
2026-09-18 12:54:45 +03:30
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
color: @ink;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-04 13:57:25 +03:30
|
|
|
|
.reg__doneText {
|
|
|
|
|
|
max-width: 320px;
|
|
|
|
|
|
margin: 10px 0 0;
|
2026-09-18 12:54:45 +03:30
|
|
|
|
font-size: 13px;
|
2026-10-04 13:57:25 +03:30
|
|
|
|
line-height: 2;
|
2026-09-18 12:54:45 +03:30
|
|
|
|
color: @inkMuted;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-04 13:57:25 +03:30
|
|
|
|
.reg__ltr {
|
|
|
|
|
|
direction: ltr;
|
|
|
|
|
|
unicode-bidi: isolate;
|
2026-09-18 12:54:45 +03:30
|
|
|
|
font-weight: 700;
|
2026-10-04 13:57:25 +03:30
|
|
|
|
color: @ink;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg__doneActions {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
max-width: 360px;
|
|
|
|
|
|
margin-top: 30px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- Motion ---------- */
|
|
|
|
|
|
|
|
|
|
|
|
.reg-next-enter-active,
|
|
|
|
|
|
.reg-next-leave-active,
|
|
|
|
|
|
.reg-prev-enter-active,
|
|
|
|
|
|
.reg-prev-leave-active {
|
|
|
|
|
|
transition: transform 0.28s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.22s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RTL: moving forward slides content toward the left.
|
|
|
|
|
|
.reg-next-enter-from,
|
|
|
|
|
|
.reg-prev-leave-to {
|
|
|
|
|
|
transform: translateX(-24px);
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg-next-leave-to,
|
|
|
|
|
|
.reg-prev-enter-from {
|
|
|
|
|
|
transform: translateX(24px);
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg-fade-enter-active,
|
|
|
|
|
|
.reg-fade-leave-active {
|
|
|
|
|
|
transition: opacity 0.25s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reg-fade-enter-from,
|
|
|
|
|
|
.reg-fade-leave-to {
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes reg-spin {
|
|
|
|
|
|
to {
|
|
|
|
|
|
transform: rotate(360deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes reg-pop {
|
|
|
|
|
|
0% {
|
|
|
|
|
|
transform: scale(0.4);
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
100% {
|
|
|
|
|
|
transform: scale(1);
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes reg-ring {
|
|
|
|
|
|
0% {
|
|
|
|
|
|
transform: scale(1);
|
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
|
}
|
|
|
|
|
|
100% {
|
|
|
|
|
|
transform: scale(1.55);
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
|
|
|
|
.reg-next-enter-active,
|
|
|
|
|
|
.reg-next-leave-active,
|
|
|
|
|
|
.reg-prev-enter-active,
|
|
|
|
|
|
.reg-prev-leave-active {
|
|
|
|
|
|
transition: opacity 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
.reg-next-enter-from,
|
|
|
|
|
|
.reg-next-leave-to,
|
|
|
|
|
|
.reg-prev-enter-from,
|
|
|
|
|
|
.reg-prev-leave-to {
|
|
|
|
|
|
transform: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.reg__doneMark,
|
|
|
|
|
|
.reg__doneMark :deep(svg),
|
|
|
|
|
|
.reg__doneRing {
|
|
|
|
|
|
animation: none;
|
|
|
|
|
|
}
|
2026-09-18 12:54:45 +03:30
|
|
|
|
}
|
|
|
|
|
|
</style>
|