Improve auth, checkout, and API client for real backend.
Wire OTP and cart/catalog to the API with Vite proxy, fix resend/register name persistence, harden order submit errors, and polish OTP and cart footer UI. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
# Same-origin in dev (Vite proxies to the real API — avoids CORS)
|
||||
VITE_API_BASE_URL=/server/api
|
||||
# Set true only for offline local OTP (code 1234)
|
||||
VITE_USE_MOCK_AUTH=false
|
||||
@@ -1 +1,5 @@
|
||||
VITE_API_BASE_URL=https://parche.montech.ir/server/api
|
||||
|
||||
# For local Vite (optional): use /server/api with the proxy in vite.config.ts
|
||||
# VITE_API_BASE_URL=/server/api
|
||||
|
||||
|
||||
@@ -303,7 +303,7 @@ input[type='number'] {
|
||||
|
||||
.sticky-footer {
|
||||
position: fixed;
|
||||
bottom: var(--nav-height);
|
||||
bottom: calc(var(--nav-height) + 12px);
|
||||
inset-inline: 0;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { formatMeterage } from '@/utils/format'
|
||||
import { formatMeterage, toPersianDigits } from '@/utils/format'
|
||||
import IconCart from '@/components/icons/IconCart.vue'
|
||||
|
||||
defineProps<{
|
||||
@@ -13,21 +13,27 @@ defineEmits<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="cart-meterage-footer">
|
||||
<div class="cart-meterage-footer__info">
|
||||
<button
|
||||
type="button"
|
||||
class="cart-meterage-footer"
|
||||
aria-label="مشاهده سبد خرید"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<span class="cart-meterage-footer__info">
|
||||
<span class="cart-meterage-footer__label">کل متراژ</span>
|
||||
<span class="cart-meterage-footer__value">{{ formatMeterage(totalMeterage) }}</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="cart-meterage-footer__btn"
|
||||
aria-label="مشاهده سبد"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<IconCart :size="22" />
|
||||
<span v-if="itemCount" class="cart-meterage-footer__badge">{{ itemCount }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<span class="cart-meterage-footer__action">
|
||||
<span class="cart-meterage-footer__action-text">مشاهده سبد</span>
|
||||
<span class="cart-meterage-footer__icon-wrap">
|
||||
<IconCart :size="20" />
|
||||
<span v-if="itemCount" class="cart-meterage-footer__badge">
|
||||
{{ toPersianDigits(String(itemCount)) }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -36,15 +42,34 @@ defineEmits<{
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
min-height: 56px;
|
||||
padding: 12px 16px;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
box-shadow: var(--shadow-md);
|
||||
cursor: pointer;
|
||||
text-align: start;
|
||||
transition:
|
||||
transform 0.15s ease,
|
||||
filter 0.15s ease;
|
||||
}
|
||||
|
||||
.cart-meterage-footer:hover {
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
|
||||
.cart-meterage-footer:active {
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
.cart-meterage-footer__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.cart-meterage-footer__label {
|
||||
@@ -57,29 +82,33 @@ defineEmits<{
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cart-meterage-footer__btn {
|
||||
.cart-meterage-footer__action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cart-meterage-footer__action-text {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cart-meterage-footer__icon-wrap {
|
||||
position: relative;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border: none;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.cart-meterage-footer__btn:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.cart-meterage-footer__badge {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left: -4px;
|
||||
inset-inline-start: -4px;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
padding: 0 4px;
|
||||
|
||||
@@ -62,23 +62,29 @@ export function useProductOrdering() {
|
||||
|
||||
async function addProduct(productId: string) {
|
||||
const product = catalogStore.getProduct(productId)
|
||||
if (!product || !catalog.value) return
|
||||
if (!product || !catalog.value) {
|
||||
errors.value = {
|
||||
...errors.value,
|
||||
[productId]: 'اطلاعات محصول هنوز آماده نیست. صفحه را تازه کنید.',
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const meterage = getMeterage(productId)
|
||||
if (meterage <= 0) {
|
||||
errors.value[productId] = 'متراژ را وارد کنید.'
|
||||
errors.value = { ...errors.value, [productId]: 'متراژ را وارد کنید.' }
|
||||
return
|
||||
}
|
||||
|
||||
const result = await cartStore.addItem(product, catalog.value.code, meterage)
|
||||
if (result.success) {
|
||||
errors.value[productId] = ''
|
||||
errors.value = { ...errors.value, [productId]: '' }
|
||||
toast.value = `${product.name} به سبد اضافه شد.`
|
||||
setTimeout(() => {
|
||||
toast.value = ''
|
||||
}, 2000)
|
||||
} else {
|
||||
errors.value[productId] = result.message ?? 'خطا'
|
||||
errors.value = { ...errors.value, [productId]: result.message ?? 'خطا' }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+166
-2
@@ -1,8 +1,24 @@
|
||||
import { normalizePhone } from '@/utils/format'
|
||||
import { extractNumericString, normalizePhone } from '@/utils/format'
|
||||
import { MOCK_OTP_CODE } from '@/utils/constants'
|
||||
import { apiRequest, ApiError, getAccessToken, setAccessToken } from './http'
|
||||
import type { User } from '@/types'
|
||||
|
||||
const USER_KEY = 'ft_user'
|
||||
const PENDING_OTP_KEY = 'ft_pending_otp'
|
||||
const MOCK_USERS_KEY = 'ft_mock_users'
|
||||
|
||||
type PendingOtp = {
|
||||
phone: string
|
||||
isRegister: boolean
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
}
|
||||
|
||||
let memoryPending: PendingOtp | null = null
|
||||
|
||||
function useMockAuth(): boolean {
|
||||
return import.meta.env.VITE_USE_MOCK_AUTH === 'true'
|
||||
}
|
||||
|
||||
function cacheUser(user: User | null): void {
|
||||
if (user) localStorage.setItem(USER_KEY, JSON.stringify(user))
|
||||
@@ -28,6 +44,126 @@ export function isAuthenticated(): boolean {
|
||||
return !!getAccessToken()
|
||||
}
|
||||
|
||||
function readMockUsers(): Record<string, User> {
|
||||
try {
|
||||
const raw = localStorage.getItem(MOCK_USERS_KEY)
|
||||
if (!raw) return {}
|
||||
return JSON.parse(raw) as Record<string, User>
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function writeMockUsers(users: Record<string, User>) {
|
||||
localStorage.setItem(MOCK_USERS_KEY, JSON.stringify(users))
|
||||
}
|
||||
|
||||
function setPendingOtp(value: PendingOtp | null) {
|
||||
memoryPending = value
|
||||
try {
|
||||
if (value) sessionStorage.setItem(PENDING_OTP_KEY, JSON.stringify(value))
|
||||
else sessionStorage.removeItem(PENDING_OTP_KEY)
|
||||
} catch {
|
||||
// ignore storage failures; in-memory pending still works
|
||||
}
|
||||
}
|
||||
|
||||
function getPendingOtp(): PendingOtp | null {
|
||||
if (memoryPending) return memoryPending
|
||||
try {
|
||||
const raw = sessionStorage.getItem(PENDING_OTP_KEY)
|
||||
if (!raw) return null
|
||||
const parsed = JSON.parse(raw) as PendingOtp
|
||||
memoryPending = parsed
|
||||
return parsed
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function isMockToken(token: string | null): boolean {
|
||||
return !!token?.startsWith('mock.')
|
||||
}
|
||||
|
||||
function ensureMockUser(phone: string, firstName = 'کاربر', lastName = 'آزمایشی'): User {
|
||||
const users = readMockUsers()
|
||||
const existing = users[phone]
|
||||
if (existing) return existing
|
||||
const user: User = {
|
||||
id: `mock-${phone}`,
|
||||
firstName,
|
||||
lastName,
|
||||
phone,
|
||||
createdAt: new Date().toISOString(),
|
||||
}
|
||||
users[phone] = user
|
||||
writeMockUsers(users)
|
||||
return user
|
||||
}
|
||||
|
||||
async function sendOtpMock(
|
||||
phone: string,
|
||||
options: { firstName?: string; lastName?: string; isRegister: boolean },
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
const users = readMockUsers()
|
||||
|
||||
if (options.isRegister && users[phone]) {
|
||||
return { success: false, message: 'این شماره قبلاً ثبتنام شده است. از ورود استفاده کنید.' }
|
||||
}
|
||||
|
||||
if (!options.isRegister) {
|
||||
ensureMockUser(phone)
|
||||
}
|
||||
|
||||
setPendingOtp({
|
||||
phone,
|
||||
isRegister: options.isRegister,
|
||||
firstName: options.firstName,
|
||||
lastName: options.lastName,
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `کد تأیید ارسال شد. (کد آزمایشی: ${MOCK_OTP_CODE})`,
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyOtpMock(
|
||||
phone: string,
|
||||
code: string,
|
||||
): Promise<{ success: boolean; message: string; user?: User }> {
|
||||
const normalizedCode = extractNumericString(code)
|
||||
if (normalizedCode !== MOCK_OTP_CODE) {
|
||||
return { success: false, message: 'کد تأیید نامعتبر است.' }
|
||||
}
|
||||
|
||||
let pending = getPendingOtp()
|
||||
if (pending && normalizePhone(pending.phone) !== phone) {
|
||||
pending = null
|
||||
}
|
||||
|
||||
// If the OTP page is open but pending was lost (refresh/HMR), still allow the known mock code.
|
||||
if (!pending) {
|
||||
pending = {
|
||||
phone,
|
||||
isRegister: false,
|
||||
}
|
||||
}
|
||||
|
||||
const user = pending.isRegister
|
||||
? ensureMockUser(
|
||||
phone,
|
||||
pending.firstName?.trim() || 'کاربر',
|
||||
pending.lastName?.trim() || 'جدید',
|
||||
)
|
||||
: ensureMockUser(phone)
|
||||
|
||||
setAccessToken(`mock.${phone}`)
|
||||
cacheUser(user)
|
||||
setPendingOtp(null)
|
||||
return { success: true, message: 'ورود موفق.', user }
|
||||
}
|
||||
|
||||
export async function sendOtp(
|
||||
phone: string,
|
||||
options: { firstName?: string; lastName?: string; isRegister: boolean },
|
||||
@@ -41,6 +177,10 @@ export async function sendOtp(
|
||||
return { success: false, message: 'نام و نام خانوادگی الزامی است.' }
|
||||
}
|
||||
|
||||
if (useMockAuth()) {
|
||||
return sendOtpMock(normalized, options)
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await apiRequest<{ success: boolean; message: string; debugCode?: string }>(
|
||||
'/auth/otp/request',
|
||||
@@ -70,6 +210,11 @@ export async function verifyOtp(
|
||||
code: string,
|
||||
): Promise<{ success: boolean; message: string; user?: User }> {
|
||||
const normalized = normalizePhone(phone)
|
||||
|
||||
if (useMockAuth()) {
|
||||
return verifyOtpMock(normalized, code)
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await apiRequest<{ accessToken: string; user: User }>('/auth/otp/verify', {
|
||||
method: 'POST',
|
||||
@@ -90,13 +235,20 @@ export async function verifyOtp(
|
||||
export function logout(): void {
|
||||
setAccessToken(null)
|
||||
cacheUser(null)
|
||||
setPendingOtp(null)
|
||||
}
|
||||
|
||||
export async function fetchMe(): Promise<User | null> {
|
||||
if (!getAccessToken()) {
|
||||
const token = getAccessToken()
|
||||
if (!token) {
|
||||
cacheUser(null)
|
||||
return null
|
||||
}
|
||||
|
||||
if (isMockToken(token)) {
|
||||
return getCachedUser()
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await apiRequest<User>('/auth/me')
|
||||
cacheUser(user)
|
||||
@@ -110,6 +262,18 @@ export async function fetchMe(): Promise<User | null> {
|
||||
export async function updateProfile(
|
||||
data: Partial<Pick<User, 'firstName' | 'lastName' | 'nationalId'>>,
|
||||
): Promise<User | null> {
|
||||
const token = getAccessToken()
|
||||
if (isMockToken(token)) {
|
||||
const current = getCachedUser()
|
||||
if (!current) return null
|
||||
const updated = { ...current, ...data }
|
||||
cacheUser(updated)
|
||||
const users = readMockUsers()
|
||||
users[current.phone] = updated
|
||||
writeMockUsers(users)
|
||||
return updated
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await apiRequest<User>('/auth/profile', {
|
||||
method: 'PATCH',
|
||||
|
||||
+53
-13
@@ -31,7 +31,14 @@ type RequestOptions = {
|
||||
|
||||
function buildUrl(path: string, query?: RequestOptions['query']): string {
|
||||
const base = getApiBaseUrl()
|
||||
const url = new URL(path.startsWith('http') ? path : `${base}${path.startsWith('/') ? '' : '/'}${path}`)
|
||||
const joined = path.startsWith('http')
|
||||
? path
|
||||
: `${base.replace(/\/$/, '')}/${path.replace(/^\//, '')}`
|
||||
|
||||
const url = joined.startsWith('http')
|
||||
? new URL(joined)
|
||||
: new URL(joined, typeof window !== 'undefined' ? window.location.origin : 'http://127.0.0.1')
|
||||
|
||||
if (query) {
|
||||
for (const [key, value] of Object.entries(query)) {
|
||||
if (value === undefined || value === null || value === '') continue
|
||||
@@ -41,6 +48,31 @@ function buildUrl(path: string, query?: RequestOptions['query']): string {
|
||||
return url.toString()
|
||||
}
|
||||
|
||||
function errorMessageFromBody(data: unknown, status: number): string {
|
||||
if (typeof data === 'object' && data) {
|
||||
if ('message' in data) {
|
||||
const message = (data as { message: unknown }).message
|
||||
if (Array.isArray(message)) return message.join('، ')
|
||||
if (message != null && String(message).trim()) return String(message)
|
||||
}
|
||||
if ('error' in data) {
|
||||
const error = (data as { error: unknown }).error
|
||||
if (typeof error === 'string' && error.trim()) return `${error} (${status})`
|
||||
}
|
||||
}
|
||||
if (typeof data === 'string') {
|
||||
if (/upstream error/i.test(data)) {
|
||||
return 'سرور API در دسترس نیست. کمی بعد دوباره تلاش کنید.'
|
||||
}
|
||||
if (data.trim() && !data.trimStart().startsWith('<')) {
|
||||
return data.trim().slice(0, 200)
|
||||
}
|
||||
}
|
||||
if (status === 400) return 'درخواست نامعتبر است. اطلاعات سفارش را بررسی کنید.'
|
||||
if (status === 401) return 'نشست شما منقضی شده است. دوباره وارد شوید.'
|
||||
return `خطای سرور (${status})`
|
||||
}
|
||||
|
||||
export async function apiRequest<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
||||
const headers: Record<string, string> = {
|
||||
Accept: 'application/json',
|
||||
@@ -53,11 +85,25 @@ export async function apiRequest<T>(path: string, options: RequestOptions = {}):
|
||||
if (token) headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
|
||||
const res = await fetch(buildUrl(path, options.query), {
|
||||
method: options.method ?? (options.body !== undefined ? 'POST' : 'GET'),
|
||||
headers,
|
||||
body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
|
||||
})
|
||||
let res: Response
|
||||
try {
|
||||
res = await fetch(buildUrl(path, options.query), {
|
||||
method: options.method ?? (options.body !== undefined ? 'POST' : 'GET'),
|
||||
headers,
|
||||
body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
|
||||
signal: AbortSignal.timeout(20_000),
|
||||
})
|
||||
} catch (err) {
|
||||
const timedOut =
|
||||
(err instanceof DOMException && err.name === 'TimeoutError') ||
|
||||
(err instanceof Error && err.name === 'TimeoutError')
|
||||
throw new ApiError(
|
||||
0,
|
||||
timedOut
|
||||
? 'پاسخ سرور طول کشید. دوباره تلاش کنید.'
|
||||
: 'اتصال به سرور برقرار نشد. اینترنت یا آدرس API را بررسی کنید.',
|
||||
)
|
||||
}
|
||||
|
||||
const text = await res.text()
|
||||
let data: unknown = null
|
||||
@@ -70,13 +116,7 @@ export async function apiRequest<T>(path: string, options: RequestOptions = {}):
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
const message =
|
||||
typeof data === 'object' && data && 'message' in data
|
||||
? Array.isArray((data as { message: unknown }).message)
|
||||
? ((data as { message: string[] }).message).join('، ')
|
||||
: String((data as { message: unknown }).message)
|
||||
: `خطای سرور (${res.status})`
|
||||
throw new ApiError(res.status, message)
|
||||
throw new ApiError(res.status, errorMessageFromBody(data, res.status))
|
||||
}
|
||||
|
||||
return data as T
|
||||
|
||||
+37
-4
@@ -9,6 +9,11 @@ import {
|
||||
updateProfile as authUpdateProfile,
|
||||
fetchMe,
|
||||
} from '@/services/authService'
|
||||
import {
|
||||
getPendingRegister,
|
||||
setPendingRegister,
|
||||
type PendingRegister,
|
||||
} from '@/utils/pendingRegister'
|
||||
import { useCartStore } from './cart'
|
||||
import { useOrderStore } from './order'
|
||||
import { useCatalogStore } from './catalog'
|
||||
@@ -17,6 +22,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
const user = ref<User | null>(getCurrentUser())
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
const pendingRegister = ref<PendingRegister | null>(getPendingRegister())
|
||||
|
||||
const isAuthenticated = computed(() => !!user.value)
|
||||
const fullName = computed(() =>
|
||||
@@ -27,16 +33,41 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
error.value = null
|
||||
}
|
||||
|
||||
function clearPendingRegister() {
|
||||
pendingRegister.value = null
|
||||
setPendingRegister(null)
|
||||
}
|
||||
|
||||
async function requestOtp(
|
||||
phone: string,
|
||||
options: { firstName?: string; lastName?: string; isRegister: boolean },
|
||||
) {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
const result = await sendOtp(phone, options)
|
||||
loading.value = false
|
||||
if (!result.success) error.value = result.message
|
||||
return result
|
||||
|
||||
try {
|
||||
// Re-read storage on every call (survives HMR / refresh / separate tabs).
|
||||
const stored = getPendingRegister()
|
||||
pendingRegister.value = stored
|
||||
|
||||
const firstName = options.firstName?.trim() || stored?.firstName
|
||||
const lastName = options.lastName?.trim() || stored?.lastName
|
||||
|
||||
if (options.isRegister && firstName && lastName) {
|
||||
pendingRegister.value = { firstName, lastName }
|
||||
setPendingRegister(pendingRegister.value)
|
||||
}
|
||||
|
||||
const result = await sendOtp(phone, {
|
||||
isRegister: options.isRegister,
|
||||
firstName,
|
||||
lastName,
|
||||
})
|
||||
if (!result.success) error.value = result.message
|
||||
return result
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmOtp(phone: string, code: string) {
|
||||
@@ -46,6 +77,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
loading.value = false
|
||||
if (result.success && result.user) {
|
||||
user.value = result.user
|
||||
clearPendingRegister()
|
||||
await Promise.all([
|
||||
useCatalogStore().loadCompanies(),
|
||||
useCartStore().hydrate(),
|
||||
@@ -60,6 +92,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
function logout() {
|
||||
authLogout()
|
||||
user.value = null
|
||||
clearPendingRegister()
|
||||
useCartStore().resetLocal()
|
||||
useOrderStore().resetLocal()
|
||||
}
|
||||
|
||||
+90
-4
@@ -1,9 +1,8 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { Cart, Company, Product } from '@/types'
|
||||
import type { Cart, CartItem, Company, Product } from '@/types'
|
||||
import { validateMeterage } from '@/utils/validation'
|
||||
import { apiRequest, ApiError } from '@/services/http'
|
||||
import { getAccessToken } from '@/services/http'
|
||||
import { apiRequest, ApiError, getAccessToken } from '@/services/http'
|
||||
import { useCatalogStore } from './catalog'
|
||||
|
||||
const DEFAULT_MAX_METERAGE = 5000
|
||||
@@ -14,6 +13,11 @@ function emptyCart(): Cart {
|
||||
return { id: 'local', companyId: '', companyName: '', items: [] }
|
||||
}
|
||||
|
||||
function useLocalCart(): boolean {
|
||||
const token = getAccessToken()
|
||||
return import.meta.env.VITE_USE_MOCK_AUTH === 'true' || !!token?.startsWith('mock.')
|
||||
}
|
||||
|
||||
function uniqueCompanyNames(items: Cart['items']): string {
|
||||
const names = [...new Set(items.map((item) => item.companyName).filter(Boolean))]
|
||||
return names.join('، ')
|
||||
@@ -31,7 +35,9 @@ export const useCartStore = defineStore('cart', () => {
|
||||
const selectedCompanyId = ref<string | null>(null)
|
||||
const syncing = ref(false)
|
||||
|
||||
const activeCart = computed(() => carts.value.find((c) => c.id === activeCartId.value) ?? carts.value[0] ?? null)
|
||||
const activeCart = computed(
|
||||
() => carts.value.find((c) => c.id === activeCartId.value) ?? carts.value[0] ?? null,
|
||||
)
|
||||
|
||||
const totalMeterage = computed(
|
||||
() => activeCart.value?.items.reduce((sum, i) => sum + i.meterage, 0) ?? 0,
|
||||
@@ -77,11 +83,21 @@ export const useCartStore = defineStore('cart', () => {
|
||||
if (cart.companyId) selectedCompanyId.value = cart.companyId
|
||||
}
|
||||
|
||||
function replaceActiveCart(cart: Cart) {
|
||||
carts.value = [{ ...cart, items: cart.items.map((item) => ({ ...item })) }]
|
||||
activeCartId.value = carts.value[0]!.id
|
||||
}
|
||||
|
||||
async function hydrate() {
|
||||
if (!getAccessToken()) {
|
||||
resetLocal()
|
||||
return
|
||||
}
|
||||
if (useLocalCart()) {
|
||||
// Keep whatever local cart already exists for mock sessions.
|
||||
syncing.value = false
|
||||
return
|
||||
}
|
||||
syncing.value = true
|
||||
try {
|
||||
const serverCart = await apiRequest<ApiCart>('/carts')
|
||||
@@ -118,6 +134,10 @@ export const useCartStore = defineStore('cart', () => {
|
||||
}
|
||||
|
||||
async function createNewCartForSameCompany(): Promise<Cart | null> {
|
||||
if (useLocalCart()) {
|
||||
resetLocal()
|
||||
return activeCart.value
|
||||
}
|
||||
try {
|
||||
const serverCart = await apiRequest<ApiCart>('/carts', { method: 'DELETE' })
|
||||
applyServerCart(serverCart)
|
||||
@@ -128,6 +148,45 @@ export const useCartStore = defineStore('cart', () => {
|
||||
}
|
||||
}
|
||||
|
||||
function addItemLocal(
|
||||
product: Product,
|
||||
catalogCode: string,
|
||||
meterage: number,
|
||||
company: Company,
|
||||
): { success: boolean; message?: string } {
|
||||
const cart = getOrCreateCart(company)
|
||||
const existing = cart.items.find((i) => i.productId === product.id)
|
||||
const items = [...cart.items]
|
||||
|
||||
if (existing) {
|
||||
const index = items.findIndex((i) => i.id === existing.id)
|
||||
items[index] = { ...existing, meterage }
|
||||
} else {
|
||||
const item: CartItem = {
|
||||
id: `local-item-${product.id}-${Date.now()}`,
|
||||
productId: product.id,
|
||||
catalogCode,
|
||||
productName: product.name,
|
||||
productCode: product.code,
|
||||
color: product.color,
|
||||
meterage,
|
||||
companyId: company.id,
|
||||
companyName: company.name,
|
||||
}
|
||||
items.push(item)
|
||||
}
|
||||
|
||||
replaceActiveCart({
|
||||
id: cart.id || 'local',
|
||||
companyId: company.id,
|
||||
companyName: company.name,
|
||||
items,
|
||||
notes: cart.notes,
|
||||
})
|
||||
selectedCompanyId.value = company.id
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
async function addItem(
|
||||
product: Product,
|
||||
catalogCode: string,
|
||||
@@ -155,6 +214,10 @@ export const useCartStore = defineStore('cart', () => {
|
||||
return { success: false, message: validation.message }
|
||||
}
|
||||
|
||||
if (useLocalCart()) {
|
||||
return addItemLocal(product, catalogCode, meterage, company)
|
||||
}
|
||||
|
||||
try {
|
||||
let serverCart: ApiCart
|
||||
if (existing) {
|
||||
@@ -203,6 +266,14 @@ export const useCartStore = defineStore('cart', () => {
|
||||
return { success: false, message: validation.message }
|
||||
}
|
||||
|
||||
if (useLocalCart()) {
|
||||
const items = activeCart.value.items.map((entry) =>
|
||||
entry.id === itemId ? { ...entry, meterage } : entry,
|
||||
)
|
||||
replaceActiveCart({ ...activeCart.value, items })
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
try {
|
||||
const serverCart = await apiRequest<ApiCart>(`/carts/items/${itemId}`, {
|
||||
method: 'PATCH',
|
||||
@@ -219,6 +290,12 @@ export const useCartStore = defineStore('cart', () => {
|
||||
}
|
||||
|
||||
async function removeItem(itemId: string) {
|
||||
if (useLocalCart()) {
|
||||
if (!activeCart.value) return
|
||||
const items = activeCart.value.items.filter((entry) => entry.id !== itemId)
|
||||
replaceActiveCart({ ...activeCart.value, items })
|
||||
return
|
||||
}
|
||||
try {
|
||||
const serverCart = await apiRequest<ApiCart>(`/carts/items/${itemId}`, {
|
||||
method: 'DELETE',
|
||||
@@ -230,6 +307,11 @@ export const useCartStore = defineStore('cart', () => {
|
||||
}
|
||||
|
||||
async function setNotes(notes: string) {
|
||||
if (useLocalCart()) {
|
||||
if (!activeCart.value) return
|
||||
replaceActiveCart({ ...activeCart.value, notes })
|
||||
return
|
||||
}
|
||||
try {
|
||||
const serverCart = await apiRequest<ApiCart>('/carts/notes', {
|
||||
method: 'PATCH',
|
||||
@@ -242,6 +324,10 @@ export const useCartStore = defineStore('cart', () => {
|
||||
}
|
||||
|
||||
async function clearActiveCart() {
|
||||
if (useLocalCart()) {
|
||||
resetLocal()
|
||||
return
|
||||
}
|
||||
try {
|
||||
const serverCart = await apiRequest<ApiCart>('/carts', { method: 'DELETE' })
|
||||
applyServerCart(serverCart)
|
||||
|
||||
+107
-24
@@ -1,8 +1,16 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { apiRequest } from '@/services/http'
|
||||
import {
|
||||
bankAccounts as mockBankAccounts,
|
||||
catalogs as mockCatalogs,
|
||||
companies as mockCompanies,
|
||||
products as mockProducts,
|
||||
} from '@/data/mockData'
|
||||
import type { BankAccount, Catalog, Company, Product } from '@/types'
|
||||
|
||||
const useMockCatalog = () => import.meta.env.VITE_USE_MOCK_AUTH === 'true'
|
||||
|
||||
export const useCatalogStore = defineStore('catalog', () => {
|
||||
const companies = ref<Company[]>([])
|
||||
const catalogs = ref<Catalog[]>([])
|
||||
@@ -16,6 +24,17 @@ export const useCatalogStore = defineStore('catalog', () => {
|
||||
if (loaded.value && !force) return companies.value
|
||||
loading.value = true
|
||||
try {
|
||||
if (import.meta.env.VITE_USE_MOCK_AUTH === 'true') {
|
||||
companies.value = mockCompanies
|
||||
.filter((c) => c.isActive)
|
||||
.sort((a, b) => a.displayOrder - b.displayOrder)
|
||||
catalogs.value = mockCatalogs
|
||||
products.value = mockProducts
|
||||
bankAccounts.value = mockBankAccounts
|
||||
loaded.value = true
|
||||
return companies.value
|
||||
}
|
||||
|
||||
const list = await apiRequest<Company[]>('/companies', {
|
||||
query: { activeOnly: true },
|
||||
})
|
||||
@@ -23,6 +42,18 @@ export const useCatalogStore = defineStore('catalog', () => {
|
||||
.filter((c) => c.isActive)
|
||||
.sort((a, b) => a.displayOrder - b.displayOrder)
|
||||
loaded.value = true
|
||||
} catch (err) {
|
||||
if (useMockCatalog()) {
|
||||
companies.value = mockCompanies
|
||||
.filter((c) => c.isActive)
|
||||
.sort((a, b) => a.displayOrder - b.displayOrder)
|
||||
catalogs.value = mockCatalogs
|
||||
products.value = mockProducts
|
||||
bankAccounts.value = mockBankAccounts
|
||||
loaded.value = true
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -38,36 +69,88 @@ export const useCatalogStore = defineStore('catalog', () => {
|
||||
}
|
||||
|
||||
async function loadCatalogs(companyId: string) {
|
||||
const list = await apiRequest<Catalog[]>('/catalogs', {
|
||||
query: { companyId, activeOnly: true },
|
||||
})
|
||||
catalogs.value = [
|
||||
...catalogs.value.filter((c) => c.companyId !== companyId),
|
||||
...list,
|
||||
]
|
||||
return list
|
||||
if (import.meta.env.VITE_USE_MOCK_AUTH === 'true') {
|
||||
const list = mockCatalogs.filter((c) => c.companyId === companyId && c.isActive)
|
||||
catalogs.value = [
|
||||
...catalogs.value.filter((c) => c.companyId !== companyId),
|
||||
...list,
|
||||
]
|
||||
return list
|
||||
}
|
||||
try {
|
||||
const list = await apiRequest<Catalog[]>('/catalogs', {
|
||||
query: { companyId, activeOnly: true },
|
||||
})
|
||||
catalogs.value = [
|
||||
...catalogs.value.filter((c) => c.companyId !== companyId),
|
||||
...list,
|
||||
]
|
||||
return list
|
||||
} catch (err) {
|
||||
if (!useMockCatalog()) throw err
|
||||
const list = mockCatalogs.filter((c) => c.companyId === companyId && c.isActive)
|
||||
catalogs.value = [
|
||||
...catalogs.value.filter((c) => c.companyId !== companyId),
|
||||
...list,
|
||||
]
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProducts(catalogId: string, color?: string) {
|
||||
const list = await apiRequest<Product[]>('/products', {
|
||||
query: {
|
||||
catalogId,
|
||||
color: color && color !== 'all' ? color : undefined,
|
||||
activeOnly: true,
|
||||
},
|
||||
})
|
||||
const others = products.value.filter((p) => p.catalogId !== catalogId)
|
||||
products.value = [...others, ...list]
|
||||
return list
|
||||
if (import.meta.env.VITE_USE_MOCK_AUTH === 'true') {
|
||||
let list = mockProducts.filter((p) => p.catalogId === catalogId && p.isActive)
|
||||
if (color && color !== 'all') list = list.filter((p) => p.color.includes(color))
|
||||
const others = products.value.filter((p) => p.catalogId !== catalogId)
|
||||
products.value = [...others, ...list]
|
||||
return list
|
||||
}
|
||||
try {
|
||||
const list = await apiRequest<Product[]>('/products', {
|
||||
query: {
|
||||
catalogId,
|
||||
color: color && color !== 'all' ? color : undefined,
|
||||
activeOnly: true,
|
||||
},
|
||||
})
|
||||
const others = products.value.filter((p) => p.catalogId !== catalogId)
|
||||
products.value = [...others, ...list]
|
||||
return list
|
||||
} catch (err) {
|
||||
if (!useMockCatalog()) throw err
|
||||
let list = mockProducts.filter((p) => p.catalogId === catalogId && p.isActive)
|
||||
if (color && color !== 'all') list = list.filter((p) => p.color.includes(color))
|
||||
const others = products.value.filter((p) => p.catalogId !== catalogId)
|
||||
products.value = [...others, ...list]
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
||||
async function loadBankAccounts(companyId: string) {
|
||||
const list = await apiRequest<BankAccount[]>(`/companies/${companyId}/bank-accounts`)
|
||||
bankAccounts.value = [
|
||||
...bankAccounts.value.filter((b) => b.companyId !== companyId),
|
||||
...list.filter((b) => b.isActive),
|
||||
]
|
||||
return bankAccounts.value.filter((b) => b.companyId === companyId)
|
||||
if (import.meta.env.VITE_USE_MOCK_AUTH === 'true') {
|
||||
const list = mockBankAccounts.filter((b) => b.companyId === companyId && b.isActive)
|
||||
bankAccounts.value = [
|
||||
...bankAccounts.value.filter((b) => b.companyId !== companyId),
|
||||
...list,
|
||||
]
|
||||
return list
|
||||
}
|
||||
try {
|
||||
const list = await apiRequest<BankAccount[]>(`/companies/${companyId}/bank-accounts`)
|
||||
bankAccounts.value = [
|
||||
...bankAccounts.value.filter((b) => b.companyId !== companyId),
|
||||
...list.filter((b) => b.isActive),
|
||||
]
|
||||
return bankAccounts.value.filter((b) => b.companyId === companyId)
|
||||
} catch (err) {
|
||||
if (!useMockCatalog()) throw err
|
||||
const list = mockBankAccounts.filter((b) => b.companyId === companyId && b.isActive)
|
||||
bankAccounts.value = [
|
||||
...bankAccounts.value.filter((b) => b.companyId !== companyId),
|
||||
...list,
|
||||
]
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
||||
function fetchCatalogs(companyId: string, query = ''): Catalog[] {
|
||||
|
||||
+8
-2
@@ -104,21 +104,27 @@ export const useOrderStore = defineStore('order', () => {
|
||||
checks?: CheckData[],
|
||||
checkMode?: CheckRegistrationMode,
|
||||
): Promise<Order> {
|
||||
if (!cart.companyId) {
|
||||
const companyId = cart.companyId || cart.items[0]?.companyId || ''
|
||||
if (!companyId) {
|
||||
throw new Error('شرکت سبد مشخص نیست.')
|
||||
}
|
||||
if (!cart.items.length) {
|
||||
throw new Error('سبد خرید خالی است.')
|
||||
}
|
||||
if (cart.id === 'local' || cart.items.some((item) => item.id.startsWith('local-item-'))) {
|
||||
throw new Error('سبد خرید با سرور همگام نیست. لطفاً دوباره محصولات را به سبد اضافه کنید.')
|
||||
}
|
||||
|
||||
const body = {
|
||||
companyId: cart.companyId,
|
||||
companyId,
|
||||
settlementType,
|
||||
settlementMethod: settlementType,
|
||||
checkMode: settlementType === 'check' ? (checkMode ?? 'full') : undefined,
|
||||
notes: cart.notes,
|
||||
items: cart.items.map((item) => ({
|
||||
productId: item.productId,
|
||||
meterage: item.meterage,
|
||||
requestedMeterage: item.meterage,
|
||||
notes: item.notes,
|
||||
})),
|
||||
checks:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { OrderStatus } from '@/types'
|
||||
|
||||
export const DEFAULT_MAX_METERAGE = 5000
|
||||
export const MOCK_OTP_CODE = '123456'
|
||||
export const MOCK_OTP_CODE = '1234'
|
||||
|
||||
export const ORDER_STATUS_LABELS: Record<OrderStatus, string> = {
|
||||
cart: 'سبد خرید',
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
const PENDING_REGISTER_KEY = 'ft_pending_register'
|
||||
|
||||
export type PendingRegister = { firstName: string; lastName: string }
|
||||
|
||||
export function getPendingRegister(): PendingRegister | null {
|
||||
try {
|
||||
const raw = sessionStorage.getItem(PENDING_REGISTER_KEY)
|
||||
if (!raw) return null
|
||||
const parsed = JSON.parse(raw) as PendingRegister
|
||||
if (!parsed?.firstName?.trim() || !parsed?.lastName?.trim()) return null
|
||||
return {
|
||||
firstName: parsed.firstName.trim(),
|
||||
lastName: parsed.lastName.trim(),
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function setPendingRegister(value: PendingRegister | null) {
|
||||
if (value?.firstName?.trim() && value?.lastName?.trim()) {
|
||||
sessionStorage.setItem(
|
||||
PENDING_REGISTER_KEY,
|
||||
JSON.stringify({
|
||||
firstName: value.firstName.trim(),
|
||||
lastName: value.lastName.trim(),
|
||||
}),
|
||||
)
|
||||
return
|
||||
}
|
||||
sessionStorage.removeItem(PENDING_REGISTER_KEY)
|
||||
}
|
||||
+58
-26
@@ -6,22 +6,29 @@ 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 IconEditPen from '@/components/icons/IconEditPen.vue'
|
||||
import { toPersianDigits, extractNumericString, normalizePhone } from '@/utils/format'
|
||||
import {
|
||||
FORM_VALIDATION_SUMMARY,
|
||||
clearFieldError,
|
||||
hasFieldErrors,
|
||||
type FieldErrors,
|
||||
} from '@/utils/formErrors'
|
||||
import { getPendingRegister } from '@/utils/pendingRegister'
|
||||
|
||||
const OTP_LENGTH = 6
|
||||
const OTP_LENGTH = 4
|
||||
const RESEND_SECONDS = 60
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
|
||||
const phone = computed(() => (route.query.phone as string) ?? '')
|
||||
const phone = computed(() => {
|
||||
const raw = route.query.phone
|
||||
const value = Array.isArray(raw) ? raw[0] : raw
|
||||
return normalizePhone(typeof value === 'string' ? value : '')
|
||||
})
|
||||
const isRegister = computed(() => route.query.mode === 'register')
|
||||
const digits = ref<string[]>(Array.from({ length: OTP_LENGTH }, () => ''))
|
||||
const inputRefs = ref<(HTMLInputElement | null)[]>([])
|
||||
const fieldErrors = ref<FieldErrors>({})
|
||||
@@ -171,7 +178,7 @@ async function submit() {
|
||||
return
|
||||
}
|
||||
|
||||
const result = await auth.confirmOtp(phone.value, code.value)
|
||||
const result = await auth.confirmOtp(phone.value, extractNumericString(code.value))
|
||||
if (result.success) {
|
||||
router.replace('/')
|
||||
} else {
|
||||
@@ -184,12 +191,23 @@ async function submit() {
|
||||
async function resend() {
|
||||
if (!canResend.value) return
|
||||
|
||||
const isRegister = route.query.mode === 'register'
|
||||
const result = await auth.requestOtp(phone.value, { isRegister })
|
||||
formError.value = ''
|
||||
fieldErrors.value = {}
|
||||
|
||||
const pending = isRegister.value ? getPendingRegister() : null
|
||||
|
||||
if (isRegister.value && !pending) {
|
||||
formError.value = 'برای ارسال مجدد کد، نام و نام خانوادگی لازم است. لطفاً دوباره ثبتنام را شروع کنید.'
|
||||
return
|
||||
}
|
||||
|
||||
const result = await auth.requestOtp(phone.value, {
|
||||
isRegister: isRegister.value,
|
||||
firstName: pending?.firstName,
|
||||
lastName: pending?.lastName,
|
||||
})
|
||||
if (result.success) {
|
||||
digits.value = Array.from({ length: OTP_LENGTH }, () => '')
|
||||
formError.value = ''
|
||||
fieldErrors.value = {}
|
||||
startTimer()
|
||||
focusInput(0)
|
||||
} else {
|
||||
@@ -198,11 +216,14 @@ async function resend() {
|
||||
}
|
||||
|
||||
function changePhone() {
|
||||
const isRegister = route.query.mode === 'register'
|
||||
router.replace({ name: isRegister ? 'register' : 'login' })
|
||||
router.replace({ name: isRegister.value ? 'register' : 'login' })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!phone.value) {
|
||||
router.replace({ name: 'login' })
|
||||
return
|
||||
}
|
||||
startTimer()
|
||||
focusInput(0)
|
||||
})
|
||||
@@ -222,8 +243,12 @@ onMounted(() => {
|
||||
<FormErrorBanner :message="formError" />
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="otp-0">کد تأیید</label>
|
||||
<div class="flex justify-center gap-2.5 ltr" dir="ltr" role="group" aria-label="کد تأیید">
|
||||
<div
|
||||
class="otp-row flex w-full max-w-full justify-center ltr"
|
||||
dir="ltr"
|
||||
role="group"
|
||||
aria-label="کد تأیید"
|
||||
>
|
||||
<input
|
||||
v-for="(digit, index) in digits"
|
||||
:id="`otp-${index}`"
|
||||
@@ -257,24 +282,39 @@ onMounted(() => {
|
||||
<template v-if="canResend">ارسال مجدد کد</template>
|
||||
<template v-else>ارسال مجدد کد ({{ timerLabel }})</template>
|
||||
</BaseButton>
|
||||
<BaseButton block variant="ghost" type="button" class="mt-2" @click="changePhone">
|
||||
تغییر شماره موبایل
|
||||
</BaseButton>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="mt-3 mx-auto flex items-center justify-center gap-1.5 border-0 bg-transparent p-0 text-sm text-muted cursor-pointer transition-colors hover:text-text"
|
||||
@click="changePhone"
|
||||
>
|
||||
<span class="ltr tabular-nums" dir="ltr">{{ toPersianDigits(phone) }}</span>
|
||||
<span>ویرایش شماره</span>
|
||||
<IconEditPen :size="18" :stroke-width="1.5" />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.otp-row {
|
||||
gap: clamp(0.25rem, 1.8vw, 0.625rem);
|
||||
}
|
||||
|
||||
.otp-box {
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
flex: 1 1 0;
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
max-width: 48px;
|
||||
aspect-ratio: 48 / 52;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
font-size: 1.25rem;
|
||||
font-size: clamp(0.95rem, 4.2vw, 1.25rem);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
caret-color: var(--color-primary);
|
||||
@@ -295,12 +335,4 @@ onMounted(() => {
|
||||
font-size: 0.8125rem;
|
||||
margin: 4px 0 0;
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.otp-box {
|
||||
width: 42px;
|
||||
height: 48px;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
hasFieldErrors,
|
||||
type FieldErrors,
|
||||
} from '@/utils/formErrors'
|
||||
import { setPendingRegister } from '@/utils/pendingRegister'
|
||||
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
@@ -52,10 +53,15 @@ async function submit() {
|
||||
return
|
||||
}
|
||||
|
||||
const result = await auth.requestOtp(normalized, {
|
||||
isRegister: true,
|
||||
const names = {
|
||||
firstName: firstName.value.trim(),
|
||||
lastName: lastName.value.trim(),
|
||||
}
|
||||
setPendingRegister(names)
|
||||
|
||||
const result = await auth.requestOtp(normalized, {
|
||||
isRegister: true,
|
||||
...names,
|
||||
})
|
||||
if (result.success) {
|
||||
router.push({ name: 'otp', query: { phone: normalized, mode: 'register' } })
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useAuthStore } from '@/stores/auth'
|
||||
import { useCatalogStore } from '@/stores/catalog'
|
||||
import PageHeader from '@/components/layout/PageHeader.vue'
|
||||
import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import FormErrorBanner from '@/components/ui/FormErrorBanner.vue'
|
||||
import IconSuccessCircle from '@/components/icons/IconSuccessCircle.vue'
|
||||
import { toPersianDigits } from '@/utils/format'
|
||||
import type { BankAccount, Order } from '@/types'
|
||||
@@ -18,6 +19,8 @@ const authStore = useAuthStore()
|
||||
const catalogStore = useCatalogStore()
|
||||
const bankAccounts = ref<BankAccount[]>([])
|
||||
const submitting = ref(false)
|
||||
const formError = ref('')
|
||||
const createdOrder = ref<Order | null>(null)
|
||||
|
||||
const companyId = computed(() => {
|
||||
const firstItem = cartStore.activeCart?.items[0]
|
||||
@@ -39,7 +42,7 @@ onMounted(async () => {
|
||||
const expiryMessage = computed(() => {
|
||||
const hour = catalogStore.getCompany(companyId.value)?.cashInvoiceValidUntilHour ?? 17
|
||||
const time = toPersianDigits(String(hour).padStart(2, '0'))
|
||||
return `این فاکتور تا ساعت ${time}:۰۰ امروز معتبر است.`
|
||||
return `پس از ثبت، فاکتور تا ساعت ${time}:۰۰ امروز معتبر است.`
|
||||
})
|
||||
|
||||
const accountsSectionTitle = computed(() => {
|
||||
@@ -59,14 +62,17 @@ function formatIban(value: string) {
|
||||
}
|
||||
|
||||
async function createOrder(): Promise<Order | null> {
|
||||
if (createdOrder.value) return createdOrder.value
|
||||
if (!authStore.user || !cartStore.activeCart || submitting.value) return null
|
||||
formError.value = ''
|
||||
submitting.value = true
|
||||
try {
|
||||
const order = await orderStore.submitOrder(authStore.user.id, cartStore.activeCart, 'cash')
|
||||
createdOrder.value = order
|
||||
await cartStore.hydrate()
|
||||
return order
|
||||
} catch (e) {
|
||||
alert(e instanceof Error ? e.message : 'ثبت سفارش ناموفق بود.')
|
||||
formError.value = e instanceof Error ? e.message : 'ثبت سفارش ناموفق بود.'
|
||||
return null
|
||||
} finally {
|
||||
submitting.value = false
|
||||
@@ -97,9 +103,13 @@ async function registerReceipt() {
|
||||
/>
|
||||
|
||||
<div class="cash-page__scroll page__scroll">
|
||||
<FormErrorBanner :message="formError" />
|
||||
|
||||
<div class="cash-page__success">
|
||||
<IconSuccessCircle :size="56" />
|
||||
<p class="cash-page__success-title">فاکتور شما ثبت شد.</p>
|
||||
<p class="cash-page__success-title">
|
||||
{{ createdOrder ? 'فاکتور شما ثبت شد.' : 'آماده ثبت فاکتور نقدی' }}
|
||||
</p>
|
||||
<p class="cash-page__success-subtitle">{{ expiryMessage }}</p>
|
||||
</div>
|
||||
|
||||
@@ -128,8 +138,12 @@ async function registerReceipt() {
|
||||
</div>
|
||||
|
||||
<div class="cash-page__footer">
|
||||
<BaseButton variant="outline" block @click="viewOrder">مشاهده سفارش</BaseButton>
|
||||
<BaseButton block class="cash-page__submit" @click="registerReceipt">ثبت رسید پرداخت</BaseButton>
|
||||
<BaseButton variant="outline" block :loading="submitting" @click="viewOrder">
|
||||
ثبت و مشاهده سفارش
|
||||
</BaseButton>
|
||||
<BaseButton block class="cash-page__submit" :loading="submitting" @click="registerReceipt">
|
||||
ثبت سفارش و رسید پرداخت
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -142,9 +156,13 @@ async function registerReceipt() {
|
||||
|
||||
<div class="cash-page__success">
|
||||
<IconSuccessCircle :size="56" />
|
||||
<p class="cash-page__success-title">فاکتور شما ثبت شد.</p>
|
||||
<p class="cash-page__success-title">
|
||||
{{ createdOrder ? 'فاکتور شما ثبت شد.' : 'آماده ثبت فاکتور نقدی' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<FormErrorBanner :message="formError" />
|
||||
|
||||
<section class="accounts-panel">
|
||||
<h2 class="accounts-panel__title">{{ accountsSectionTitle }}</h2>
|
||||
<div class="desktop-grid-2">
|
||||
@@ -166,8 +184,12 @@ async function registerReceipt() {
|
||||
</section>
|
||||
|
||||
<div class="cash-desktop-page__actions">
|
||||
<BaseButton variant="outline" class="desktop-btn-compact" @click="viewOrder">مشاهده سفارش</BaseButton>
|
||||
<BaseButton class="desktop-btn-compact" @click="registerReceipt">ثبت رسید پرداخت</BaseButton>
|
||||
<BaseButton variant="outline" class="desktop-btn-compact" :loading="submitting" @click="viewOrder">
|
||||
ثبت و مشاهده سفارش
|
||||
</BaseButton>
|
||||
<BaseButton class="desktop-btn-compact" :loading="submitting" @click="registerReceipt">
|
||||
ثبت سفارش و رسید پرداخت
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useCartStore } from '@/stores/cart'
|
||||
import { useOrderStore } from '@/stores/order'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import FormErrorBanner from '@/components/ui/FormErrorBanner.vue'
|
||||
import EmptyState from '@/components/ui/EmptyState.vue'
|
||||
import IconChevronBack from '@/components/icons/IconChevronBack.vue'
|
||||
import { formatCurrency, formatDate, formatNumber, toPersianDigits } from '@/utils/format'
|
||||
@@ -17,6 +18,7 @@ const router = useRouter()
|
||||
const cartStore = useCartStore()
|
||||
const orderStore = useOrderStore()
|
||||
const authStore = useAuthStore()
|
||||
const formError = ref('')
|
||||
|
||||
const isCheckout = computed(() => route.name === 'check-list')
|
||||
|
||||
@@ -88,6 +90,7 @@ function toggleMenu(id: string) {
|
||||
|
||||
async function submitOrder() {
|
||||
if (!authStore.user || !cartStore.activeCart || checks.value.length === 0) return
|
||||
formError.value = ''
|
||||
|
||||
try {
|
||||
const order = await orderStore.submitOrder(
|
||||
@@ -100,7 +103,7 @@ async function submitOrder() {
|
||||
await cartStore.hydrate()
|
||||
router.replace(`/orders/${order.id}/success`)
|
||||
} catch (e) {
|
||||
alert(e instanceof Error ? e.message : 'ثبت سفارش ناموفق بود.')
|
||||
formError.value = e instanceof Error ? e.message : 'ثبت سفارش ناموفق بود.'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -127,6 +130,8 @@ async function submitOrder() {
|
||||
|
||||
<template v-else>
|
||||
<div class="check-list-page__scroll page__scroll">
|
||||
<FormErrorBanner :message="formError" />
|
||||
|
||||
<div class="summary-cards">
|
||||
<div class="summary-card">
|
||||
<span class="summary-card__label">مجموع مبلغ چکها</span>
|
||||
|
||||
@@ -126,7 +126,7 @@ const emit = defineEmits<{
|
||||
}
|
||||
|
||||
.product-mobile-page__scroll {
|
||||
padding-bottom: 76px;
|
||||
padding-bottom: 96px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
|
||||
@@ -19,4 +19,13 @@ export default defineConfig({
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'/server/api': {
|
||||
target: 'https://parche.montech.ir',
|
||||
changeOrigin: true,
|
||||
secure: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -298,6 +298,14 @@
|
||||
"@babel/helper-string-parser" "^8.0.0"
|
||||
"@babel/helper-validator-identifier" "^8.0.4"
|
||||
|
||||
"@emnapi/core@1.10.0":
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.10.0.tgz#380ccc8f2412ea22d1d972df7f8ee23a3b9c7467"
|
||||
integrity sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==
|
||||
dependencies:
|
||||
"@emnapi/wasi-threads" "1.2.1"
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@emnapi/core@2.0.0-alpha.3":
|
||||
version "2.0.0-alpha.3"
|
||||
resolved "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz"
|
||||
@@ -306,6 +314,13 @@
|
||||
"@emnapi/wasi-threads" "2.0.1"
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@emnapi/runtime@1.10.0":
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.10.0.tgz#4b260c0d3534204e98c6110b8db1a987d26ec87c"
|
||||
integrity sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@emnapi/runtime@2.0.0-alpha.3":
|
||||
version "2.0.0-alpha.3"
|
||||
resolved "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz"
|
||||
@@ -313,6 +328,13 @@
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@emnapi/wasi-threads@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz#28fed21a1ba1ce797c44a070abc94d42f3ae8548"
|
||||
integrity sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@emnapi/wasi-threads@2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz"
|
||||
@@ -447,9 +469,9 @@
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@napi-rs/wasm-runtime@^1.2.0":
|
||||
"@napi-rs/wasm-runtime@^1.1.4", "@napi-rs/wasm-runtime@^1.2.0":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz"
|
||||
resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz#c70706532e5827c0932ca6bf43ee2c512f29c639"
|
||||
integrity sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==
|
||||
dependencies:
|
||||
"@tybys/wasm-util" "^0.10.3"
|
||||
@@ -462,7 +484,7 @@
|
||||
"@nodelib/fs.stat" "2.0.5"
|
||||
run-parallel "^1.1.9"
|
||||
|
||||
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
|
||||
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
|
||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||
@@ -475,20 +497,184 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@oxc-parser/binding-android-arm-eabi@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.131.0.tgz#fff5aa799c504a18f8e9a488895f8d6226cba17a"
|
||||
integrity sha512-t2xicr9pfzkSRYx5aPqZqlLaayIwJTqgQ81Jor31Xep2nGyL2Aq3d0K5wOfeR7VevaSdxaS9dzSQP9xDwn8fDg==
|
||||
|
||||
"@oxc-parser/binding-android-arm64@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.131.0.tgz#4c194c64794ef5c7531a326da96e71bd24c2472d"
|
||||
integrity sha512-nlGIod6gw75x1aEDgLS+srj+JRGY0HHm9MI9YgzE/B64l6d6+H3MSP9NOgp0+HTg8tp4vV9rVfgQGgd+TfVZcA==
|
||||
|
||||
"@oxc-parser/binding-darwin-arm64@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.131.0.tgz#746b75fa752651eadd4a628a510f5555af272185"
|
||||
integrity sha512-jukuV6xe5RbQKFo7QD34NDCLDZp4PSOm8rmckhNdH/60ymG5zXbDzGBEyc+nTkuLQNama2aSGCt+CPfpjNTqyw==
|
||||
|
||||
"@oxc-parser/binding-darwin-x64@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.131.0.tgz#57ccc1f57cebd41265725496e345c0943adab0fd"
|
||||
integrity sha512-g3JOo4khe9rslHm5WYaVDWb0HS/M1MLR3I9S8560MkKIcC96VQY00QjOlsuRyfSj/JDXj8i9T7ryPO2RidiXVg==
|
||||
|
||||
"@oxc-parser/binding-freebsd-x64@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.131.0.tgz#7db5f2249b3c2fe9c4ce8026b7aa01ec069e1c36"
|
||||
integrity sha512-1hziITDTxjMePnX+dR9ocVT+EuZkQ8wm4FPAbmbEiKG+Phbo73J1ZnPAA6Y/aGsWF3McOFnQuZIktAFwalkfJQ==
|
||||
|
||||
"@oxc-parser/binding-linux-arm-gnueabihf@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.131.0.tgz#d1007ed7bcf893f5edefc80205910d7a7c36fbe8"
|
||||
integrity sha512-9uRxfXwyKG9+MwmGQBo2ncPNwZH5HTmCETFM2WiuDBNDCW4NC5ttSQkwCAMrTAWgwMzVBH1CP8pM0v7nebCWXQ==
|
||||
|
||||
"@oxc-parser/binding-linux-arm-musleabihf@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.131.0.tgz#48287ab9d34ac638b056f1706bf31c173212ff7b"
|
||||
integrity sha512-mgbLvzRShXOLBdWGInf08Af4q+pfj1xD8hSgLClDZ9of/BXkB6+LIhTH7fihiDUipqB3yoSkKBWaZ3Ejlf5Yag==
|
||||
|
||||
"@oxc-parser/binding-linux-arm64-gnu@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.131.0.tgz#aff802fcf1f4b028a6b7ff01deb6615b74cac1c3"
|
||||
integrity sha512-OPT8++4aN6j2GJ8+3IZHS/byXoZP4aSBn+FoG6rgBJ2fKwPKXWF3MqrFMNW7NKHM28FLY579xYLxJSfgobEqPA==
|
||||
|
||||
"@oxc-parser/binding-linux-arm64-musl@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.131.0.tgz#8d32894c6241aa589cc398ce22db2adacb65e06e"
|
||||
integrity sha512-vtPiwmfVTAXzaxDKsOXG+LwgRAA7WEnaeHzhS5z0GE89gAK18KSXnly7Z6saXXq6L3dVMyK44uoTI03zKxrpmw==
|
||||
|
||||
"@oxc-parser/binding-linux-ppc64-gnu@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.131.0.tgz#38ef44e8d6b5d03e43aae79aeecaf55ca04f9ab8"
|
||||
integrity sha512-8AW8L7w5cGHSdZPcyZX2yR0+GUODsT15rbRjfdD54rv6DMbtuEB19ysLOpKJlRGfH6UNYNpCHaU1uJWgTWf1/w==
|
||||
|
||||
"@oxc-parser/binding-linux-riscv64-gnu@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.131.0.tgz#159f290a4d05aa8c2ce21792028c050c0edd155d"
|
||||
integrity sha512-vvpjkjEOUsPcsYf8evE4MO3aGx9+3wodXEBOicGNnOwTuAik8eBONNkgSdhkGsAblQmfVHJyanRnpxglddTXIA==
|
||||
|
||||
"@oxc-parser/binding-linux-riscv64-musl@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.131.0.tgz#a14613f25d497e9a39e8f5ba0bd7236d59fb3664"
|
||||
integrity sha512-AqmcNC3fClXX+fxQ6VGEN1667xVFiRBkY0CZmDMSiaeFUsv1+UkBPYYi48IUKcA9/ivvoKNRzQl2I4//kT9F/w==
|
||||
|
||||
"@oxc-parser/binding-linux-s390x-gnu@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.131.0.tgz#7939c8cdb56fd42ea8cbda997f8bc8f57f6f25fe"
|
||||
integrity sha512-7d3jOMKy7RSQCcDLIci+ySll2FgsOMl/GiRux4q2JNv0zg4EdhFISa9idvrdN/HEUIQQJNg6dmveUeJl2YErGA==
|
||||
|
||||
"@oxc-parser/binding-linux-x64-gnu@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.npmmirror.com/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.131.0.tgz"
|
||||
integrity sha512-JHK/h95qVqVQ+ITER837kcTdwBDFpFaNnOTYGCP0zdUSX/mLKC7tXOoyrTb6vG7iRPwGlcgBil3v2IjYw1FqJA==
|
||||
|
||||
"@oxc-parser/binding-linux-x64-musl@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.131.0.tgz#8508564351285c9349797498a9e1ae4143ae488c"
|
||||
integrity sha512-b2BO82O8azXAyf7EUgOPKu145nWypbNyk07HbU09fkzhm9lEA5oPvaN/M8Nlo7tOErVTa2WOgS4QbOnxAPXdDQ==
|
||||
|
||||
"@oxc-parser/binding-openharmony-arm64@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.131.0.tgz#4cfee92a1aaaecc8146e36ca7077e33f4ffecb5e"
|
||||
integrity sha512-GHO9glZaX7LkX/OGfluEPf1yjg+ehiFbUdowbX6uNWOQhmwKWU4m4+nZ9FJkrHNKuxyI1KKertMdGjVKCApKWA==
|
||||
|
||||
"@oxc-parser/binding-wasm32-wasi@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.131.0.tgz#6026e7aa98fb42044218458d6908e7870e28a873"
|
||||
integrity sha512-3SkikPaEFoih1N83qLVEDLRLeY4nYsf6JT9SnWiMCQ5lGQdKup6bEuKCqkRiG9dD1IIaFeYz9RjlciPmYoFIWA==
|
||||
dependencies:
|
||||
"@emnapi/core" "1.10.0"
|
||||
"@emnapi/runtime" "1.10.0"
|
||||
"@napi-rs/wasm-runtime" "^1.1.4"
|
||||
|
||||
"@oxc-parser/binding-win32-arm64-msvc@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.131.0.tgz#e55f2b5d23c4c0c6b7d911518656f15e22c8bcdd"
|
||||
integrity sha512-Os5bEhryeA2jkH+ZrnZyAC1EP5gs+X4YB1Fjqml7UPD5kU7ecsK1MPEVMfCrdt/GDNpDbavYXiOXOdyJ5b3OPw==
|
||||
|
||||
"@oxc-parser/binding-win32-ia32-msvc@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.131.0.tgz#cf309e1abf0676acc6c92ebdc8e90e71c9d875da"
|
||||
integrity sha512-m+jNz9EuF0NXoiptc6B9h5yompZQVW/a5MJeOu5zojfH5yWk82tvF2ccrHkfhgtrS9h9DD5l1Qv8dWlfY7Nz8g==
|
||||
|
||||
"@oxc-parser/binding-win32-x64-msvc@0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.131.0.tgz#af435bd3f60b0a75a07ac67c7c9e9b74907746e0"
|
||||
integrity sha512-o14Hk8dAyiEUMFEWEgmAwFZvBt1RzAYLM3xeQ+5315JXgVYhoemivgYcbYVRbsFkS71ShMGlAFE0kPnr460rww==
|
||||
|
||||
"@oxc-project/types@=0.142.0":
|
||||
version "0.142.0"
|
||||
resolved "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz"
|
||||
integrity sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==
|
||||
|
||||
"@oxc-project/types@^0.131.0":
|
||||
version "0.131.0"
|
||||
resolved "https://registry.npmmirror.com/@oxc-project/types/-/types-0.131.0.tgz"
|
||||
integrity sha512-PgnWDfV0h+b16XNKbXU7Daib/BFSt/J2mEzfYIBu6JB/wNdlU+kVYXCkGA1A9fWkTbOgbjh4e6NhPeQOYvFhEA==
|
||||
|
||||
"@oxc-project/types@=0.142.0":
|
||||
version "0.142.0"
|
||||
resolved "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz"
|
||||
integrity sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==
|
||||
"@oxlint/binding-android-arm-eabi@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.74.0.tgz#994edbb0a457a0311ef0cc678bdb659c0b0050e9"
|
||||
integrity sha512-+gHd12muVI9ZLBaWLPkHt3Fj7jihFjgQ1MGtBaRL8vWrWrI0P7dLUty/cHrHS0oqPYIRgQUJsPu2CExQuMcwNw==
|
||||
|
||||
"@oxlint/binding-android-arm64@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-android-arm64/-/binding-android-arm64-1.74.0.tgz#d1d99acb90b2b59d5c1e9897024960769a1f9055"
|
||||
integrity sha512-xjKdoMB+H+RCOByv/7l7nfIGW9mlOisqYdcyC75UqYuQecLpReAeEYUf2CNeDEI3KtmUgxpRw/+c63y4AeF/Bw==
|
||||
|
||||
"@oxlint/binding-darwin-arm64@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.74.0.tgz#d1553475634eba1b69935e1d5eba009f6273df05"
|
||||
integrity sha512-iUK7wvc6sejMKsC+Pt67mntoF5weFcyEunhZfLJceU6gL419mexz5wBkSx/EnkFBExMLNtOi9fnDSc5xfK0IzQ==
|
||||
|
||||
"@oxlint/binding-darwin-x64@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.74.0.tgz#4d666398406b66d31af5117e72187dcb11828606"
|
||||
integrity sha512-ggKc/tn5SJ1u2yG2izC6VKODfYKV8MQ2AicJlNzOjuyrC29udvOef6/JzK2r32xqCnBDLFouR1VCkjzEI0/N9Q==
|
||||
|
||||
"@oxlint/binding-freebsd-x64@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.74.0.tgz#2884cb55c1134fec7f32e92c191412c5b136de5f"
|
||||
integrity sha512-u++dH/43jy9hTLbneaWlS0gla/Bp1JdwJ2zgevCl8nDFUh6qRCGMxcL0f0lb7By3A9p/LfFr+7cG4HU1hG856g==
|
||||
|
||||
"@oxlint/binding-linux-arm-gnueabihf@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.74.0.tgz#9b4d2a448530ba0c135d741fe409a8fbf4cb69dc"
|
||||
integrity sha512-Sj1zmtFDVTPeIbIz4ZfcXAbFHqCmKCXdCUlAJzvTF7I20NTH1RDpoF2PhkqNODutJzVhJYmm3oz0GwgY+tvE2g==
|
||||
|
||||
"@oxlint/binding-linux-arm-musleabihf@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.74.0.tgz#f757ff57227688090919ebfa70f9759228746d2d"
|
||||
integrity sha512-//PKyQb/tQXcHArx2f7z+oVI/eMS2Jpv+edNuAtOrgIhWdGcpHxogveAxzmF2rpH1AIHp4Hq04RF/rgJdiICnQ==
|
||||
|
||||
"@oxlint/binding-linux-arm64-gnu@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.74.0.tgz#26c715d6a96e1d2a3483b510a36e354c5b24ef1f"
|
||||
integrity sha512-/k1Me+aX2tjuH10K62mLS0y8cLkJBHX6Ce0xPK+eWeel4bSdEGZ8dv4+hYMzg0GrSmjwy4yAYsDPeEeKBft/2w==
|
||||
|
||||
"@oxlint/binding-linux-arm64-musl@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.74.0.tgz#9aea8604b9db2123470b1e63bb6a950f03eb8cac"
|
||||
integrity sha512-3tFSjBxc5D8/zvjEuLvOqcA8ZXKD0+6NuaVO/edeamNc49MoAsbfaC9s1UiwODwgF6slGaF8yJA2TPkukd77tg==
|
||||
|
||||
"@oxlint/binding-linux-ppc64-gnu@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.74.0.tgz#e0a9270fdb3b8da8d7959ca1fb888585a603130d"
|
||||
integrity sha512-9QggtPkSPXOCTu8Szis7auOK/sC7KdQaN+/TujP7YVVhzCAOhgdRfgv8uEz0r2tk5xdgus5rLYUrCDoZNtiRUw==
|
||||
|
||||
"@oxlint/binding-linux-riscv64-gnu@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.74.0.tgz#6ffa5b16fb09011aeddf36b03710c300c1a2a937"
|
||||
integrity sha512-VM5VPUJ4DJIWiK+AZn8FScUqMr6OFrCAYybMYjEEi7W13ParI64MByiXTkKMqZpBmvQ9zxl9Ebq2VUOiZRJYUg==
|
||||
|
||||
"@oxlint/binding-linux-riscv64-musl@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.74.0.tgz#a5820666ed566cbe8edeacc46f82a9e87eb0abaa"
|
||||
integrity sha512-SaDY1gh9rOA592J54g+gu5hkOFFQBZsMmIYHs+NRHG+Uq0OxtuuCXMWQ3vu1830Eugv5uMXyjG+bv2Z9y4IXjw==
|
||||
|
||||
"@oxlint/binding-linux-s390x-gnu@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.74.0.tgz#51b7ef319f61762adc4869d1dedb935eb99c5e52"
|
||||
integrity sha512-ZATQeHZCyr6MbDveg0obD5sxLHFOghtOdC5jwVwYlvFWqtFOxctgFEG6Ef/64hYvZrWyhyCckB10AelqLopeDA==
|
||||
|
||||
"@oxlint/binding-linux-x64-gnu@1.74.0":
|
||||
version "1.74.0"
|
||||
@@ -500,6 +686,26 @@
|
||||
resolved "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.74.0.tgz"
|
||||
integrity sha512-XyktaR8lhK2qWiCK0Tk8oYD+/cgn+oHA6ddRnxSSXUKkkojkV78CmShZUxQF+yrBFs0SuW+JBOPG6hecyc/iZg==
|
||||
|
||||
"@oxlint/binding-openharmony-arm64@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.74.0.tgz#24d9f9c013f3954973eea8632d5f2ce6082c5fd2"
|
||||
integrity sha512-mzbjrPl4neaVUiJ1fUiEUxTGaSZBoiKtaoB6jmIpz9S+VOA2vDYmJpihQ82w6178V5jxziclTg8Cgj5yF6tTDg==
|
||||
|
||||
"@oxlint/binding-win32-arm64-msvc@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.74.0.tgz#deebc14f0e6261de3488945ef64b21bb2e4338ba"
|
||||
integrity sha512-vUAe9okpS2Oa5+lX67lqHMuNUvfkleRKwrUDJ/WJBsgmddvZ1mrsh2HVmuFDRzqFELhaJhFaCNOuR6a7L3rtIA==
|
||||
|
||||
"@oxlint/binding-win32-ia32-msvc@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.74.0.tgz#ef3abbea80c30242991896d2754ed0eb3ea6cc59"
|
||||
integrity sha512-yyXXJyYYSXL4I8K8jAWjJs+J3fa9gH2JmEbo4f5adm+1tNC9itseicBNuwK7BDHvqQ5J534s+yDULu89vYL2ZQ==
|
||||
|
||||
"@oxlint/binding-win32-x64-msvc@1.74.0":
|
||||
version "1.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.74.0.tgz#bfd70b708910cd3f6f897063efcc3f088e22a81e"
|
||||
integrity sha512-VTC9IYTIMrVUk/i6Ms1ohzzDKZFkWn0KU2OBbPBzgmVZ2V30165T/zK4LztTr0Xgp9fZ1qQZ1rsZAu/rEmySlA==
|
||||
|
||||
"@polka/url@^1.0.0-next.24":
|
||||
version "1.0.0-next.29"
|
||||
resolved "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz"
|
||||
@@ -512,6 +718,51 @@
|
||||
dependencies:
|
||||
quansync "^1.0.0"
|
||||
|
||||
"@rolldown/binding-android-arm64@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.1.tgz#dbc5a2453c063aa9b2974410d19b4be2e730856f"
|
||||
integrity sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==
|
||||
|
||||
"@rolldown/binding-darwin-arm64@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.1.tgz#dd43dc12d5acd6a34edbd48cdf3008f5d4c52e2a"
|
||||
integrity sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==
|
||||
|
||||
"@rolldown/binding-darwin-x64@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.1.tgz#00b32d5e0adba8aab6925633280ed1bf6d90c1d2"
|
||||
integrity sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==
|
||||
|
||||
"@rolldown/binding-freebsd-x64@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.1.tgz#0884face257d2763024754abae947e1c0b7b6c24"
|
||||
integrity sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.1.tgz#a82580ac5cf3030c65ccf6181326d64f91889524"
|
||||
integrity sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.1.tgz#5221b6c7eab55f82d776e65f9146d387cec3791e"
|
||||
integrity sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.1.tgz#3552c35275daad1f1553f1f29452aa62bffbd120"
|
||||
integrity sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==
|
||||
|
||||
"@rolldown/binding-linux-ppc64-gnu@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.1.tgz#6b4972d32614b70885c5bf580695fb1e365b5313"
|
||||
integrity sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==
|
||||
|
||||
"@rolldown/binding-linux-s390x-gnu@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.1.tgz#42ebada72e617003e30b67ac9b4afff66a75b15a"
|
||||
integrity sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.1.tgz"
|
||||
@@ -522,6 +773,11 @@
|
||||
resolved "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.1.tgz"
|
||||
integrity sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==
|
||||
|
||||
"@rolldown/binding-openharmony-arm64@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.1.tgz#d163a5fe25d3542b7b325c05e0211825f42d2b5c"
|
||||
integrity sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==
|
||||
|
||||
"@rolldown/binding-wasm32-wasi@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.1.tgz"
|
||||
@@ -531,6 +787,16 @@
|
||||
"@emnapi/runtime" "2.0.0-alpha.3"
|
||||
"@napi-rs/wasm-runtime" "^1.2.0"
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.1.tgz#c730559e7d74fa9ba24106d9104e7c8dc25f2236"
|
||||
integrity sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.1.tgz#ea040d8719fe26c8b0d781081329b20a1606a006"
|
||||
integrity sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==
|
||||
|
||||
"@rolldown/pluginutils@^1.0.0", "@rolldown/pluginutils@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz"
|
||||
@@ -634,7 +900,7 @@
|
||||
"@typescript-eslint/types" "8.65.0"
|
||||
"@typescript-eslint/visitor-keys" "8.65.0"
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@^8.65.0", "@typescript-eslint/tsconfig-utils@8.65.0":
|
||||
"@typescript-eslint/tsconfig-utils@8.65.0", "@typescript-eslint/tsconfig-utils@^8.65.0":
|
||||
version "8.65.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz"
|
||||
integrity sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==
|
||||
@@ -650,7 +916,7 @@
|
||||
debug "^4.4.3"
|
||||
ts-api-utils "^2.5.0"
|
||||
|
||||
"@typescript-eslint/types@^8.65.0", "@typescript-eslint/types@8.65.0":
|
||||
"@typescript-eslint/types@8.65.0", "@typescript-eslint/types@^8.65.0":
|
||||
version "8.65.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz"
|
||||
integrity sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==
|
||||
@@ -670,7 +936,7 @@
|
||||
tinyglobby "^0.2.15"
|
||||
ts-api-utils "^2.5.0"
|
||||
|
||||
"@typescript-eslint/utils@^8.60.0", "@typescript-eslint/utils@8.65.0":
|
||||
"@typescript-eslint/utils@8.65.0", "@typescript-eslint/utils@^8.60.0":
|
||||
version "8.65.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz"
|
||||
integrity sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==
|
||||
@@ -798,14 +1064,6 @@
|
||||
"@unocss/core" "66.7.5"
|
||||
ofetch "^1.5.1"
|
||||
|
||||
"@unocss/preset-wind@66.7.5":
|
||||
version "66.7.5"
|
||||
resolved "https://registry.npmmirror.com/@unocss/preset-wind/-/preset-wind-66.7.5.tgz"
|
||||
integrity sha512-LVKgGr0A9Lc7F85xGXrrb71DDXMlHGA8bcj/Kht8BCsuRdBZadNbLlnv5qnSJiHYhi3/blzcgVoaeRor6L9yNA==
|
||||
dependencies:
|
||||
"@unocss/core" "66.7.5"
|
||||
"@unocss/preset-wind3" "66.7.5"
|
||||
|
||||
"@unocss/preset-wind3@66.7.5":
|
||||
version "66.7.5"
|
||||
resolved "https://registry.npmmirror.com/@unocss/preset-wind3/-/preset-wind3-66.7.5.tgz"
|
||||
@@ -824,6 +1082,14 @@
|
||||
"@unocss/extractor-arbitrary-variants" "66.7.5"
|
||||
"@unocss/rule-utils" "66.7.5"
|
||||
|
||||
"@unocss/preset-wind@66.7.5":
|
||||
version "66.7.5"
|
||||
resolved "https://registry.npmmirror.com/@unocss/preset-wind/-/preset-wind-66.7.5.tgz"
|
||||
integrity sha512-LVKgGr0A9Lc7F85xGXrrb71DDXMlHGA8bcj/Kht8BCsuRdBZadNbLlnv5qnSJiHYhi3/blzcgVoaeRor6L9yNA==
|
||||
dependencies:
|
||||
"@unocss/core" "66.7.5"
|
||||
"@unocss/preset-wind3" "66.7.5"
|
||||
|
||||
"@unocss/rule-utils@66.7.5":
|
||||
version "66.7.5"
|
||||
resolved "https://registry.npmmirror.com/@unocss/rule-utils/-/rule-utils-66.7.5.tgz"
|
||||
@@ -864,7 +1130,7 @@
|
||||
dependencies:
|
||||
"@unocss/core" "66.7.5"
|
||||
|
||||
"@unocss/vite@^66.7.5", "@unocss/vite@66.7.5":
|
||||
"@unocss/vite@66.7.5", "@unocss/vite@^66.7.5":
|
||||
version "66.7.5"
|
||||
resolved "https://registry.npmmirror.com/@unocss/vite/-/vite-66.7.5.tgz"
|
||||
integrity sha512-1z1TBCNJCR2WzjPtjzmCHr8rtzXHosMjLdsCNe6Mzsfwiw044gzzLVuiEZO9vIjkI50lvQ+gIOxOiVQG0hGAeA==
|
||||
@@ -1002,7 +1268,7 @@
|
||||
estree-walker "^2.0.2"
|
||||
source-map-js "^1.2.1"
|
||||
|
||||
"@vue/compiler-dom@^3.3.4", "@vue/compiler-dom@^3.5.0", "@vue/compiler-dom@3.5.40":
|
||||
"@vue/compiler-dom@3.5.40", "@vue/compiler-dom@^3.3.4", "@vue/compiler-dom@^3.5.0":
|
||||
version "3.5.40"
|
||||
resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz"
|
||||
integrity sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==
|
||||
@@ -1010,7 +1276,7 @@
|
||||
"@vue/compiler-core" "3.5.40"
|
||||
"@vue/shared" "3.5.40"
|
||||
|
||||
"@vue/compiler-sfc@^3.5.18", "@vue/compiler-sfc@^3.5.22", "@vue/compiler-sfc@3.5.40":
|
||||
"@vue/compiler-sfc@3.5.40", "@vue/compiler-sfc@^3.5.18", "@vue/compiler-sfc@^3.5.22":
|
||||
version "3.5.40"
|
||||
resolved "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz"
|
||||
integrity sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==
|
||||
@@ -1120,7 +1386,7 @@
|
||||
"@vue/runtime-dom" "3.5.40"
|
||||
"@vue/shared" "3.5.40"
|
||||
|
||||
"@vue/shared@^3.5.0", "@vue/shared@^3.5.18", "@vue/shared@^3.5.22", "@vue/shared@3.5.40":
|
||||
"@vue/shared@3.5.40", "@vue/shared@^3.5.0", "@vue/shared@^3.5.18", "@vue/shared@^3.5.22":
|
||||
version "3.5.40"
|
||||
resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz"
|
||||
integrity sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==
|
||||
@@ -1446,12 +1712,7 @@ eslint-visitor-keys@^3.4.3:
|
||||
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz"
|
||||
integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
|
||||
|
||||
eslint-visitor-keys@^5.0.0:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz"
|
||||
integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==
|
||||
|
||||
eslint-visitor-keys@^5.0.1:
|
||||
eslint-visitor-keys@^5.0.0, eslint-visitor-keys@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz"
|
||||
integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==
|
||||
@@ -1615,6 +1876,11 @@ flatted@^3.2.9:
|
||||
resolved "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz"
|
||||
integrity sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==
|
||||
|
||||
fsevents@~2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||
|
||||
gensync@^1.0.0-beta.2:
|
||||
version "1.0.0-beta.2"
|
||||
resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz"
|
||||
@@ -1787,6 +2053,41 @@ levn@^0.4.1:
|
||||
prelude-ls "^1.2.1"
|
||||
type-check "~0.4.0"
|
||||
|
||||
lightningcss-android-arm64@1.33.0:
|
||||
version "1.33.0"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz#9a6841f88ae50fc83502903892b41af41bc2b907"
|
||||
integrity sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==
|
||||
|
||||
lightningcss-darwin-arm64@1.33.0:
|
||||
version "1.33.0"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz#c0f2c31c0bfd19fa4dd3f18e957a1f1a152097d6"
|
||||
integrity sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==
|
||||
|
||||
lightningcss-darwin-x64@1.33.0:
|
||||
version "1.33.0"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz#cb0705965acb538c6683949ce6925fb3cdf7c361"
|
||||
integrity sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==
|
||||
|
||||
lightningcss-freebsd-x64@1.33.0:
|
||||
version "1.33.0"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz#763538828b26bab2680dadafcc84ee78b0eb502b"
|
||||
integrity sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==
|
||||
|
||||
lightningcss-linux-arm-gnueabihf@1.33.0:
|
||||
version "1.33.0"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz#6862e3176a331aedbdec1ed352b4d7d0dd0784de"
|
||||
integrity sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.33.0:
|
||||
version "1.33.0"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz#c6a3a2ed15141daf6bdc2628930f8e39bdf473aa"
|
||||
integrity sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==
|
||||
|
||||
lightningcss-linux-arm64-musl@1.33.0:
|
||||
version "1.33.0"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz#7fa1334971fc82845f9827df6ef8a0b20914bac6"
|
||||
integrity sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==
|
||||
|
||||
lightningcss-linux-x64-gnu@1.33.0:
|
||||
version "1.33.0"
|
||||
resolved "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz"
|
||||
@@ -1797,6 +2098,16 @@ lightningcss-linux-x64-musl@1.33.0:
|
||||
resolved "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz"
|
||||
integrity sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.33.0:
|
||||
version "1.33.0"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz#850ee1103dac989cfab50e3ac22d1a69e394e63d"
|
||||
integrity sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==
|
||||
|
||||
lightningcss-win32-x64-msvc@1.33.0:
|
||||
version "1.33.0"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz#e343ae152eed3609dc6e11949d1a3bf39a1c946f"
|
||||
integrity sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==
|
||||
|
||||
lightningcss@^1.33.0:
|
||||
version "1.33.0"
|
||||
resolved "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz"
|
||||
@@ -2134,22 +2445,7 @@ picomatch@^2.3.1:
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz"
|
||||
integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==
|
||||
|
||||
picomatch@^4.0.2:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz"
|
||||
integrity sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==
|
||||
|
||||
picomatch@^4.0.3:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz"
|
||||
integrity sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==
|
||||
|
||||
picomatch@^4.0.4:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz"
|
||||
integrity sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==
|
||||
|
||||
picomatch@^4.0.5:
|
||||
picomatch@^4.0.2, picomatch@^4.0.3, picomatch@^4.0.4, picomatch@^4.0.5:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz"
|
||||
integrity sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==
|
||||
@@ -2305,12 +2601,7 @@ semver@^6.3.1:
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
|
||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||
|
||||
semver@^7.6.3:
|
||||
version "7.8.5"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz"
|
||||
integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==
|
||||
|
||||
semver@^7.7.3:
|
||||
semver@^7.6.3, semver@^7.7.3:
|
||||
version "7.8.5"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz"
|
||||
integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==
|
||||
|
||||
Reference in New Issue
Block a user