Improve auth UI: theme toggle, inline register/login links, and mobile guest frame.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -104,36 +104,60 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Guest pages (login, checkout) ── */
|
||||
/* ── Guest pages (login, register, checkout) — all breakpoints ── */
|
||||
.app-shell--no-nav {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
background: var(--color-page-background);
|
||||
}
|
||||
|
||||
.app-shell--no-nav .app-layout {
|
||||
max-width: var(--form-max-width);
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: calc(100dvh - 32px);
|
||||
}
|
||||
|
||||
.app-shell--no-nav .app-frame {
|
||||
max-width: var(--form-max-width);
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: calc(100dvh - 32px);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-frame);
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
.app-shell--no-nav .app-content {
|
||||
padding-inline: var(--page-padding);
|
||||
}
|
||||
|
||||
.app-shell--no-nav .page {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.app-shell--no-nav.app-shell--wide .app-layout,
|
||||
.app-shell--no-nav.app-shell--wide .app-frame {
|
||||
max-width: min(var(--content-max-width), calc(100vw - 32px));
|
||||
}
|
||||
|
||||
.app-shell--no-nav.app-shell--wide .app-frame {
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.app-shell--no-nav {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
background: var(--color-page-background);
|
||||
}
|
||||
|
||||
.app-shell--no-nav .app-layout {
|
||||
max-width: var(--form-max-width);
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: calc(100dvh - 48px);
|
||||
}
|
||||
|
||||
.app-shell--no-nav .app-layout,
|
||||
.app-shell--no-nav .app-frame {
|
||||
max-width: var(--form-max-width);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-frame);
|
||||
max-height: calc(100dvh - 48px);
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
.app-shell--no-nav.app-shell--wide .app-layout,
|
||||
.app-shell--no-nav.app-shell--wide .app-frame {
|
||||
max-width: min(var(--content-max-width), calc(100vw - 48px));
|
||||
}
|
||||
|
||||
.app-shell--no-nav.app-shell--wide .app-frame {
|
||||
background: var(--color-bg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +204,13 @@ input[type='number'] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.auth-page .card {
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding-inline: 0;
|
||||
}
|
||||
|
||||
.card + .card {
|
||||
margin-top: var(--page-gap);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
|
||||
const themeStore = useThemeStore()
|
||||
const { isDark } = storeToRefs(themeStore)
|
||||
|
||||
const themeIcon = computed(() =>
|
||||
isDark.value ? 'vuesax-linear:sun-1' : 'vuesax-linear:moon',
|
||||
)
|
||||
const themeLabel = computed(() =>
|
||||
isDark.value ? 'تغییر به تم روشن' : 'تغییر به تم تاریک',
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="auth-theme-toggle"
|
||||
:aria-label="themeLabel"
|
||||
:title="themeLabel"
|
||||
@click="themeStore.toggleLightDark()"
|
||||
>
|
||||
<BasicIcon :icon="themeIcon" :size="22" :stroke-width="1.5" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.auth-theme-toggle {
|
||||
position: fixed;
|
||||
top: calc(12px + env(safe-area-inset-top, 0px));
|
||||
inset-inline-end: 16px;
|
||||
z-index: 100;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
padding: 0;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 12px;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
box-shadow: var(--shadow-sm);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 0.15s ease,
|
||||
border-color 0.15s ease,
|
||||
color 0.15s ease;
|
||||
}
|
||||
|
||||
.auth-theme-toggle:hover {
|
||||
border-color: var(--color-primary);
|
||||
background: var(--color-primary-soft);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
</style>
|
||||
@@ -25,10 +25,23 @@ export const useThemeStore = defineStore('theme', () => {
|
||||
preference.value = value
|
||||
}
|
||||
|
||||
function cyclePreference() {
|
||||
const order: ThemePreference[] = ['light', 'dark', 'system']
|
||||
const index = order.indexOf(preference.value)
|
||||
preference.value = order[(index + 1) % order.length]!
|
||||
}
|
||||
|
||||
/** Explicit light ↔ dark toggle (skips system). */
|
||||
function toggleLightDark() {
|
||||
preference.value = isDark.value ? 'light' : 'dark'
|
||||
}
|
||||
|
||||
return {
|
||||
preference,
|
||||
resolved,
|
||||
isDark,
|
||||
setPreference,
|
||||
cyclePreference,
|
||||
toggleLightDark,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useAuthStore } from '@/stores/auth'
|
||||
import BaseInput from '@/components/ui/BaseInput.vue'
|
||||
import FormErrorBanner from '@/components/ui/FormErrorBanner.vue'
|
||||
import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import AuthThemeToggle from '@/components/ui/AuthThemeToggle.vue'
|
||||
import { normalizePhone, toPersianDigits } from '@/utils/format'
|
||||
import { validatePhone } from '@/utils/validation'
|
||||
import {
|
||||
@@ -72,6 +73,8 @@ async function submit() {
|
||||
|
||||
<template>
|
||||
<div class="page page-shell auth-page">
|
||||
<AuthThemeToggle />
|
||||
|
||||
<div class="page__scroll flex flex-col justify-center py-6">
|
||||
<div class="auth-header">
|
||||
<div class="text-5xl mb-3">🧵</div>
|
||||
@@ -100,11 +103,16 @@ async function submit() {
|
||||
<BaseButton block type="submit" :loading="auth.loading">دریافت کد تأیید</BaseButton>
|
||||
</form>
|
||||
|
||||
<div class="mt-6 w-full max-w-[var(--form-max-width)]">
|
||||
<p class="m-0 mb-2.5 text-center text-sm text-muted">حساب کاربری ندارید؟</p>
|
||||
<RouterLink v-slot="{ navigate }" to="/register" custom>
|
||||
<BaseButton block variant="outline" type="button" @click="navigate">ثبتنام</BaseButton>
|
||||
</RouterLink>
|
||||
<div class="mt-6 w-full max-w-[var(--form-max-width)] text-center">
|
||||
<p class="m-0 text-sm text-muted">
|
||||
حساب کاربری ندارید؟
|
||||
<RouterLink
|
||||
to="/register"
|
||||
class="ms-1 font-bold text-primary no-underline hover:underline"
|
||||
>
|
||||
ثبت نام کنید
|
||||
</RouterLink>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useIntervalFn } from '@vueuse/core'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import FormErrorBanner from '@/components/ui/FormErrorBanner.vue'
|
||||
import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import AuthThemeToggle from '@/components/ui/AuthThemeToggle.vue'
|
||||
import { toPersianDigits, extractNumericString } from '@/utils/format'
|
||||
import {
|
||||
FORM_VALIDATION_SUMMARY,
|
||||
@@ -209,6 +210,8 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="page page-shell auth-page">
|
||||
<AuthThemeToggle />
|
||||
|
||||
<div class="page__scroll flex flex-col justify-center py-6">
|
||||
<div class="auth-header">
|
||||
<h1 class="page-title">تأیید شماره موبایل</h1>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useAuthStore } from '@/stores/auth'
|
||||
import BaseInput from '@/components/ui/BaseInput.vue'
|
||||
import FormErrorBanner from '@/components/ui/FormErrorBanner.vue'
|
||||
import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import AuthThemeToggle from '@/components/ui/AuthThemeToggle.vue'
|
||||
import { normalizePhone } from '@/utils/format'
|
||||
import { validatePhone } from '@/utils/validation'
|
||||
import {
|
||||
@@ -66,6 +67,8 @@ async function submit() {
|
||||
|
||||
<template>
|
||||
<div class="page page-shell auth-page">
|
||||
<AuthThemeToggle />
|
||||
|
||||
<div class="page__scroll flex flex-col justify-center py-6">
|
||||
<div class="auth-header">
|
||||
<div class="text-5xl mb-3">🧵</div>
|
||||
@@ -101,11 +104,16 @@ async function submit() {
|
||||
<BaseButton block :loading="auth.loading" @click="submit">دریافت کد تأیید</BaseButton>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 w-full max-w-[var(--form-max-width)]">
|
||||
<p class="m-0 mb-2.5 text-center text-sm text-muted">قبلاً ثبتنام کردهاید؟</p>
|
||||
<RouterLink v-slot="{ navigate }" to="/login" custom>
|
||||
<BaseButton block variant="outline" type="button" @click="navigate">ورود</BaseButton>
|
||||
</RouterLink>
|
||||
<div class="mt-6 w-full max-w-[var(--form-max-width)] text-center">
|
||||
<p class="m-0 text-sm text-muted">
|
||||
قبلاً ثبتنام کردهاید؟
|
||||
<RouterLink
|
||||
to="/login"
|
||||
class="ms-1 font-bold text-primary no-underline hover:underline"
|
||||
>
|
||||
وارد شوید
|
||||
</RouterLink>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user