优化
This commit is contained in:
@@ -1,14 +1,10 @@
|
|||||||
import { BellDot, Bug, Coins, DollarSign, Palette, PictureInPicture2, Receipt, Settings, SquareUserRound, TrendingUp, User, Users, Wrench } from 'lucide-vue-next'
|
import { Coins, DollarSign, Palette, Receipt, Settings, TrendingUp, Users } from 'lucide-vue-next'
|
||||||
|
|
||||||
import type { NavGroup } from '@/components/app-sidebar/types'
|
import type { NavGroup } from '@/components/app-sidebar/types'
|
||||||
|
|
||||||
export function useSidebar() {
|
export function useSidebar() {
|
||||||
const settingsNavItems = [
|
const settingsNavItems = [
|
||||||
{ title: 'Profile', url: '/settings/', icon: User },
|
{ title: '外观设置', url: '/settings/appearance', icon: Palette },
|
||||||
{ title: 'Account', url: '/settings/account', icon: Wrench },
|
|
||||||
{ title: 'Appearance', url: '/settings/appearance', icon: Palette },
|
|
||||||
{ title: 'Notifications', url: '/settings/notifications', icon: BellDot },
|
|
||||||
{ title: 'Display', url: '/settings/display', icon: PictureInPicture2 },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const navData = ref<NavGroup[]>([
|
const navData = ref<NavGroup[]>([
|
||||||
@@ -22,29 +18,6 @@ export function useSidebar() {
|
|||||||
{ title: '订单审批', url: '/monisuo/orders', icon: Receipt },
|
{ title: '订单审批', url: '/monisuo/orders', icon: Receipt },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: 'Pages',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: 'Auth',
|
|
||||||
icon: SquareUserRound,
|
|
||||||
items: [
|
|
||||||
{ title: 'Monisuo Login', url: '/auth/monisuo-sign-in' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Errors',
|
|
||||||
icon: Bug,
|
|
||||||
items: [
|
|
||||||
{ title: '401 | Unauthorized', url: '/errors/401' },
|
|
||||||
{ title: '403 | Forbidden', url: '/errors/403' },
|
|
||||||
{ title: '404 | Not Found', url: '/errors/404' },
|
|
||||||
{ title: '500 | Internal Server Error', url: '/errors/500' },
|
|
||||||
{ title: '503 | Maintenance Error', url: '/errors/503' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: 'Other',
|
title: 'Other',
|
||||||
items: [
|
items: [
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import AccountForm from './components/account-form.vue'
|
|
||||||
import SettingsLayout from './components/settings-layout.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<SettingsLayout>
|
|
||||||
<AccountForm />
|
|
||||||
</SettingsLayout>
|
|
||||||
</template>
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { CalendarDate, DateFormatter, getLocalTimeZone, today } from '@internationalized/date'
|
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
|
||||||
import { CalendarDays, Check, ChevronsUpDown } from 'lucide-vue-next'
|
|
||||||
import { toDate } from 'reka-ui/date'
|
|
||||||
import { toast } from 'vue-sonner'
|
|
||||||
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import { Calendar } from '@/components/ui/calendar'
|
|
||||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'
|
|
||||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
|
|
||||||
import { Input } from '@/components/ui/input'
|
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
|
||||||
import { Separator } from '@/components/ui/separator'
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
|
|
||||||
import { accountValidator } from '../validators/account.validator'
|
|
||||||
|
|
||||||
const open = ref(false)
|
|
||||||
const dateValue = ref()
|
|
||||||
const placeholder = ref()
|
|
||||||
|
|
||||||
const languages = [
|
|
||||||
{ label: 'English', value: 'en' },
|
|
||||||
{ label: 'French', value: 'fr' },
|
|
||||||
{ label: 'German', value: 'de' },
|
|
||||||
{ label: 'Spanish', value: 'es' },
|
|
||||||
{ label: 'Portuguese', value: 'pt' },
|
|
||||||
{ label: 'Russian', value: 'ru' },
|
|
||||||
{ label: 'Japanese', value: 'ja' },
|
|
||||||
{ label: 'Korean', value: 'ko' },
|
|
||||||
{ label: 'Chinese', value: 'zh' },
|
|
||||||
] as const
|
|
||||||
|
|
||||||
const df = new DateFormatter('en-US', {
|
|
||||||
dateStyle: 'long',
|
|
||||||
})
|
|
||||||
|
|
||||||
const accountFormSchema = toTypedSchema(accountValidator)
|
|
||||||
|
|
||||||
// https://github.com/logaretm/vee-validate/issues/3521
|
|
||||||
// https://github.com/logaretm/vee-validate/discussions/3571
|
|
||||||
async function onSubmit(values: any) {
|
|
||||||
toast('You submitted the following values:', {
|
|
||||||
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h3 class="text-lg font-medium">
|
|
||||||
Account
|
|
||||||
</h3>
|
|
||||||
<p class="text-sm text-muted-foreground">
|
|
||||||
Update your account settings. Set your preferred language and timezone.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Separator class="my-4" />
|
|
||||||
<Form v-slot="{ setFieldValue }" :validation-schema="accountFormSchema" class="space-y-8" @submit="onSubmit">
|
|
||||||
<FormField v-slot="{ componentField }" name="name">
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Name</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input type="text" placeholder="Your name" v-bind="componentField" />
|
|
||||||
</FormControl>
|
|
||||||
<FormDescription>
|
|
||||||
This is the name that will be displayed on your profile and in emails.
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
|
|
||||||
<FormField v-slot="{ field, value }" name="dob">
|
|
||||||
<FormItem class="flex flex-col">
|
|
||||||
<FormLabel>Date of birth</FormLabel>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger as-child>
|
|
||||||
<FormControl>
|
|
||||||
<Button
|
|
||||||
variant="outline" :class="cn(
|
|
||||||
'w-[240px] justify-start text-left font-normal',
|
|
||||||
!value && 'text-muted-foreground',
|
|
||||||
)"
|
|
||||||
>
|
|
||||||
<CalendarDays class="size-4 opacity-50" />
|
|
||||||
<span>{{ value ? df.format(toDate(dateValue, getLocalTimeZone())) : "Pick a date" }}</span>
|
|
||||||
</Button>
|
|
||||||
</FormControl>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent>
|
|
||||||
<Calendar
|
|
||||||
v-model:placeholder="placeholder"
|
|
||||||
v-model="dateValue"
|
|
||||||
calendar-label="Date of birth"
|
|
||||||
initial-focus
|
|
||||||
:min-value="new CalendarDate(1900, 1, 1)"
|
|
||||||
:max-value="today(getLocalTimeZone())"
|
|
||||||
@update:model-value="(v) => {
|
|
||||||
if (v) {
|
|
||||||
dateValue = v
|
|
||||||
setFieldValue('dob', toDate(v).toISOString())
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
dateValue = undefined
|
|
||||||
setFieldValue('dob', undefined)
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
<FormDescription>
|
|
||||||
Your date of birth is used to calculate your age.
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
<input type="hidden" v-bind="field">
|
|
||||||
</FormField>
|
|
||||||
|
|
||||||
<FormField v-slot="{ value }" name="language">
|
|
||||||
<FormItem class="flex flex-col">
|
|
||||||
<FormLabel>Language</FormLabel>
|
|
||||||
|
|
||||||
<Popover v-model:open="open">
|
|
||||||
<PopoverTrigger as-child>
|
|
||||||
<FormControl>
|
|
||||||
<Button
|
|
||||||
variant="outline" role="combobox" :aria-expanded="open" :class="cn(
|
|
||||||
'w-[200px] justify-between',
|
|
||||||
!value && 'text-muted-foreground',
|
|
||||||
)"
|
|
||||||
>
|
|
||||||
{{ value ? languages.find(
|
|
||||||
(language) => language.value === value,
|
|
||||||
)?.label : 'Select language...' }}
|
|
||||||
|
|
||||||
<ChevronsUpDown class="size-4 ml-2 opacity-50 shrink-0" />
|
|
||||||
</Button>
|
|
||||||
</FormControl>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent class="w-[200px] p-0">
|
|
||||||
<Command>
|
|
||||||
<CommandInput placeholder="Search language..." />
|
|
||||||
<CommandEmpty>No language found.</CommandEmpty>
|
|
||||||
<CommandList>
|
|
||||||
<CommandGroup>
|
|
||||||
<CommandItem
|
|
||||||
v-for="language in languages" :key="language.value" :value="language.label"
|
|
||||||
@select="() => {
|
|
||||||
setFieldValue('language', language.value)
|
|
||||||
open = false
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<Check
|
|
||||||
:class="cn(
|
|
||||||
'mr-2 h-4 w-4',
|
|
||||||
value === language.value ? 'opacity-100' : 'opacity-0',
|
|
||||||
)"
|
|
||||||
/>
|
|
||||||
{{ language.label }}
|
|
||||||
</CommandItem>
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</Command>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
|
|
||||||
<FormDescription>
|
|
||||||
This is the language that will be used in the dashboard.
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
|
|
||||||
<div class="flex justify-start">
|
|
||||||
<Button type="submit">
|
|
||||||
Update account
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
</template>
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
|
||||||
import { useForm } from 'vee-validate'
|
|
||||||
import { toast } from 'vue-sonner'
|
|
||||||
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import { Checkbox } from '@/components/ui/checkbox'
|
|
||||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
|
|
||||||
import { Separator } from '@/components/ui/separator'
|
|
||||||
|
|
||||||
import { displayValidator } from '../validators/display.validator'
|
|
||||||
|
|
||||||
const items = [
|
|
||||||
{
|
|
||||||
id: 'recents',
|
|
||||||
label: 'Recents',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'home',
|
|
||||||
label: 'Home',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'applications',
|
|
||||||
label: 'Applications',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'desktop',
|
|
||||||
label: 'Desktop',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'downloads',
|
|
||||||
label: 'Downloads',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'documents',
|
|
||||||
label: 'Documents',
|
|
||||||
},
|
|
||||||
] as const
|
|
||||||
|
|
||||||
const displayFormSchema = toTypedSchema(displayValidator)
|
|
||||||
|
|
||||||
const { handleSubmit } = useForm({
|
|
||||||
validationSchema: displayFormSchema,
|
|
||||||
initialValues: {
|
|
||||||
items: ['recents', 'home'],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
|
||||||
toast('You submitted the following values:', {
|
|
||||||
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h3 class="text-lg font-medium">
|
|
||||||
Display
|
|
||||||
</h3>
|
|
||||||
<p class="text-sm text-muted-foreground">
|
|
||||||
Turn items on or off to control what's displayed in the app.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Separator class="my-4" />
|
|
||||||
<form @submit="onSubmit">
|
|
||||||
<FormField name="items">
|
|
||||||
<FormItem>
|
|
||||||
<div class="mb-4">
|
|
||||||
<FormLabel class="text-base">
|
|
||||||
Sidebar
|
|
||||||
</FormLabel>
|
|
||||||
<FormDescription>
|
|
||||||
Select the items you want to display in the sidebar.
|
|
||||||
</FormDescription>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<FormField v-for="item in items" v-slot="{ value, handleChange }" :key="item.id" name="items">
|
|
||||||
<FormItem :key="item.id" class="flex flex-row items-start space-x-3 space-y-0">
|
|
||||||
<FormControl>
|
|
||||||
<Checkbox
|
|
||||||
:model-value="value.includes(item.id)"
|
|
||||||
@update:model-value="(checked: boolean | 'indeterminate') => {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
handleChange(checked ? [...value, item.id] : value.filter(id => id !== item.id))
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormLabel class="font-normal">
|
|
||||||
{{ item.label }}
|
|
||||||
</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
|
|
||||||
<div class="flex justify-start mt-4">
|
|
||||||
<Button type="submit">
|
|
||||||
Update display
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
|
||||||
import { useForm } from 'vee-validate'
|
|
||||||
import { toast } from 'vue-sonner'
|
|
||||||
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import { Checkbox } from '@/components/ui/checkbox'
|
|
||||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
|
|
||||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
|
||||||
import { Separator } from '@/components/ui/separator'
|
|
||||||
import { Switch } from '@/components/ui/switch'
|
|
||||||
|
|
||||||
import { notificationsValidator } from '../validators/notifications.validator'
|
|
||||||
|
|
||||||
const notificationsFormSchema = toTypedSchema(notificationsValidator)
|
|
||||||
|
|
||||||
const { handleSubmit } = useForm({
|
|
||||||
validationSchema: notificationsFormSchema,
|
|
||||||
initialValues: {
|
|
||||||
communication_emails: false,
|
|
||||||
marketing_emails: false,
|
|
||||||
social_emails: true,
|
|
||||||
security_emails: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
|
||||||
toast('You submitted the following values:', {
|
|
||||||
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h3 class="text-lg font-medium">
|
|
||||||
Notifications
|
|
||||||
</h3>
|
|
||||||
<p class="text-sm text-muted-foreground">
|
|
||||||
Configure how you receive notifications.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Separator class="my-4" />
|
|
||||||
<form class="space-y-8" @submit="onSubmit">
|
|
||||||
<FormField v-slot="{ componentField }" type="radio" name="type">
|
|
||||||
<FormItem class="space-y-3">
|
|
||||||
<FormLabel>Notify me about...</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<RadioGroup
|
|
||||||
class="flex flex-col space-y-1"
|
|
||||||
v-bind="componentField"
|
|
||||||
>
|
|
||||||
<FormItem class="flex items-center space-y-0">
|
|
||||||
<FormControl>
|
|
||||||
<RadioGroupItem value="all" />
|
|
||||||
</FormControl>
|
|
||||||
<FormLabel class="font-normal">
|
|
||||||
All new messages
|
|
||||||
</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
<FormItem class="flex items-center space-y-0">
|
|
||||||
<FormControl>
|
|
||||||
<RadioGroupItem value="mentions" />
|
|
||||||
</FormControl>
|
|
||||||
<FormLabel class="font-normal">
|
|
||||||
Direct messages and mentions
|
|
||||||
</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
<FormItem class="flex items-center space-y-0">
|
|
||||||
<FormControl>
|
|
||||||
<RadioGroupItem value="none" />
|
|
||||||
</FormControl>
|
|
||||||
<FormLabel class="font-normal">
|
|
||||||
Nothing
|
|
||||||
</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
</RadioGroup>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 class="mb-4 text-lg font-medium">
|
|
||||||
Email Notifications
|
|
||||||
</h3>
|
|
||||||
<div class="space-y-4">
|
|
||||||
<FormField v-slot="{ handleChange, value }" type="checkbox" name="communication_emails">
|
|
||||||
<FormItem class="flex flex-row items-center justify-between p-4 border rounded-lg">
|
|
||||||
<div class="space-y-0.5">
|
|
||||||
<FormLabel class="text-base">
|
|
||||||
Communication emails
|
|
||||||
</FormLabel>
|
|
||||||
<FormDescription>
|
|
||||||
Receive emails about your account activity.
|
|
||||||
</FormDescription>
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
:checked="value"
|
|
||||||
@update:checked="handleChange"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
|
|
||||||
<FormField v-slot="{ handleChange, value }" type="checkbox" name="marketing_emails">
|
|
||||||
<FormItem class="flex flex-row items-center justify-between p-4 border rounded-lg">
|
|
||||||
<div class="space-y-0.5">
|
|
||||||
<FormLabel class="text-base">
|
|
||||||
Marketing emails
|
|
||||||
</FormLabel>
|
|
||||||
<FormDescription>
|
|
||||||
Receive emails about new products, features, and more.
|
|
||||||
</FormDescription>
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
:checked="value"
|
|
||||||
@update:checked="handleChange"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
|
|
||||||
<FormField v-slot="{ handleChange, value }" type="checkbox" name="social_emails">
|
|
||||||
<FormItem class="flex flex-row items-center justify-between p-4 border rounded-lg">
|
|
||||||
<div class="space-y-0.5">
|
|
||||||
<FormLabel class="text-base">
|
|
||||||
Social emails
|
|
||||||
</FormLabel>
|
|
||||||
<FormDescription>
|
|
||||||
Receive emails for friend requests, follows, and more.
|
|
||||||
</FormDescription>
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
:checked="value"
|
|
||||||
@update:checked="handleChange"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
|
|
||||||
<FormField v-slot="{ handleChange, value }" type="checkbox" name="security_emails">
|
|
||||||
<FormItem class="flex flex-row items-center justify-between p-4 border rounded-lg">
|
|
||||||
<div class="space-y-0.5">
|
|
||||||
<FormLabel class="text-base">
|
|
||||||
Security emails
|
|
||||||
</FormLabel>
|
|
||||||
<FormDescription>
|
|
||||||
Receive emails about your account activity and security.
|
|
||||||
</FormDescription>
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
:checked="value"
|
|
||||||
@update:checked="handleChange"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<FormField v-slot="{ handleChange, value }" type="checkbox" name="mobile">
|
|
||||||
<FormItem class="flex flex-row items-start space-x-3 space-y-0">
|
|
||||||
<FormControl>
|
|
||||||
<Checkbox
|
|
||||||
:model-value="value"
|
|
||||||
@update:model-value="handleChange"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<div class="space-y-1 leading-none">
|
|
||||||
<FormLabel>
|
|
||||||
Use different settings for my mobile devices
|
|
||||||
</FormLabel>
|
|
||||||
<FormDescription>
|
|
||||||
You can manage your mobile notifications in the
|
|
||||||
<a href="/examples/forms">
|
|
||||||
mobile settings
|
|
||||||
</a> page.
|
|
||||||
</FormDescription>
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
|
|
||||||
<div class="flex justify-start">
|
|
||||||
<Button type="submit">
|
|
||||||
Update notifications
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
|
||||||
import { X } from 'lucide-vue-next'
|
|
||||||
import { FieldArray, useForm } from 'vee-validate'
|
|
||||||
import { toast } from 'vue-sonner'
|
|
||||||
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
|
|
||||||
import { Input } from '@/components/ui/input'
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectGroup,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/components/ui/select'
|
|
||||||
import { Separator } from '@/components/ui/separator'
|
|
||||||
import { Textarea } from '@/components/ui/textarea'
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
|
|
||||||
import { profileValidator } from '../validators/profile.validator'
|
|
||||||
|
|
||||||
const verifiedEmails = ref(['m@example.com', 'm@google.com', 'm@support.com'])
|
|
||||||
|
|
||||||
const profileFormSchema = toTypedSchema(profileValidator)
|
|
||||||
|
|
||||||
const { handleSubmit, resetForm } = useForm({
|
|
||||||
validationSchema: profileFormSchema,
|
|
||||||
initialValues: {
|
|
||||||
bio: 'I own a computer.',
|
|
||||||
urls: [
|
|
||||||
{ value: 'https://shadcn.com' },
|
|
||||||
{ value: 'http://twitter.com/shadcn' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
|
||||||
toast('You submitted the following values:', {
|
|
||||||
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h3 class="text-lg font-medium">
|
|
||||||
Profile
|
|
||||||
</h3>
|
|
||||||
<p class="text-sm text-muted-foreground">
|
|
||||||
This is how others will see you on the site.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Separator orientation="horizontal" class="my-4" />
|
|
||||||
<form class="space-y-8" @submit="onSubmit">
|
|
||||||
<FormField v-slot="{ componentField }" name="username">
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Username</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input type="text" placeholder="shadcn" v-bind="componentField" />
|
|
||||||
</FormControl>
|
|
||||||
<FormDescription>
|
|
||||||
This is your public display name. It can be your real name or a pseudonym. You can only change this once every 30 days.
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
|
|
||||||
<FormField v-slot="{ componentField }" name="email">
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Email</FormLabel>
|
|
||||||
|
|
||||||
<Select v-bind="componentField">
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select an email" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectItem v-for="email in verifiedEmails" :key="email" :value="email">
|
|
||||||
{{ email }}
|
|
||||||
</SelectItem>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<FormDescription>
|
|
||||||
You can manage verified email addresses in your email settings.
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
|
|
||||||
<FormField v-slot="{ componentField }" name="bio">
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Bio</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Textarea placeholder="Tell us a little bit about yourself" v-bind="componentField" />
|
|
||||||
</FormControl>
|
|
||||||
<FormDescription>
|
|
||||||
You can <span>@mention</span> other users and organizations to link to them.
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<FieldArray v-slot="{ fields, push, remove }" name="urls">
|
|
||||||
<div v-for="(field, index) in fields" :key="`urls-${field.key}`" class="mb-2">
|
|
||||||
<FormField v-slot="{ componentField }" :name="`urls[${index}].value`">
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel :class="cn(index !== 0 && 'sr-only')">
|
|
||||||
URLs
|
|
||||||
</FormLabel>
|
|
||||||
<FormDescription :class="cn(index !== 0 && 'sr-only')">
|
|
||||||
Add links to your website, blog, or social media profiles.
|
|
||||||
</FormDescription>
|
|
||||||
<div class="relative flex items-center">
|
|
||||||
<FormControl>
|
|
||||||
<Input type="url" v-bind="componentField" />
|
|
||||||
</FormControl>
|
|
||||||
<button type="button" class="absolute py-2 pe-3 end-0 text-muted-foreground" @click="remove(index)">
|
|
||||||
<X class="w-3" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
class="w-20 mt-2 text-xs"
|
|
||||||
@click="push({ value: '' })"
|
|
||||||
>
|
|
||||||
Add URL
|
|
||||||
</Button>
|
|
||||||
</FieldArray>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex justify-start gap-2">
|
|
||||||
<Button type="submit">
|
|
||||||
Update profile
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
@click="resetForm"
|
|
||||||
>
|
|
||||||
Reset form
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import DisplayForm from './components/display-form.vue'
|
|
||||||
import SettingsLayout from './components/settings-layout.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<SettingsLayout>
|
|
||||||
<DisplayForm />
|
|
||||||
</SettingsLayout>
|
|
||||||
</template>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import ProfileForm from './components/profile-form.vue'
|
|
||||||
import SettingsLayout from './components/settings-layout.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<SettingsLayout>
|
|
||||||
<ProfileForm />
|
|
||||||
</SettingsLayout>
|
|
||||||
</template>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import NotificationsForm from './components/notifications-form.vue'
|
|
||||||
import SettingsLayout from './components/settings-layout.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<SettingsLayout>
|
|
||||||
<NotificationsForm />
|
|
||||||
</SettingsLayout>
|
|
||||||
</template>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { z } from 'zod'
|
|
||||||
|
|
||||||
export const displayValidator = z.object({
|
|
||||||
items: z
|
|
||||||
.array(z.string())
|
|
||||||
.refine(value => value.some(item => item), {
|
|
||||||
error: 'You have to select at least one item.',
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
export type DisplayValidator = z.infer<typeof displayValidator>
|
|
||||||
52
monisuo-admin/src/types/route-map.d.ts
vendored
52
monisuo-admin/src/types/route-map.d.ts
vendored
@@ -82,20 +82,6 @@ declare module 'vue-router/auto-routes' {
|
|||||||
Record<never, never>,
|
Record<never, never>,
|
||||||
| never
|
| never
|
||||||
>,
|
>,
|
||||||
'/settings/': RouteRecordInfo<
|
|
||||||
'/settings/',
|
|
||||||
'/settings',
|
|
||||||
Record<never, never>,
|
|
||||||
Record<never, never>,
|
|
||||||
| never
|
|
||||||
>,
|
|
||||||
'/settings/account': RouteRecordInfo<
|
|
||||||
'/settings/account',
|
|
||||||
'/settings/account',
|
|
||||||
Record<never, never>,
|
|
||||||
Record<never, never>,
|
|
||||||
| never
|
|
||||||
>,
|
|
||||||
'/settings/appearance': RouteRecordInfo<
|
'/settings/appearance': RouteRecordInfo<
|
||||||
'/settings/appearance',
|
'/settings/appearance',
|
||||||
'/settings/appearance',
|
'/settings/appearance',
|
||||||
@@ -103,20 +89,6 @@ declare module 'vue-router/auto-routes' {
|
|||||||
Record<never, never>,
|
Record<never, never>,
|
||||||
| never
|
| never
|
||||||
>,
|
>,
|
||||||
'/settings/display': RouteRecordInfo<
|
|
||||||
'/settings/display',
|
|
||||||
'/settings/display',
|
|
||||||
Record<never, never>,
|
|
||||||
Record<never, never>,
|
|
||||||
| never
|
|
||||||
>,
|
|
||||||
'/settings/notifications': RouteRecordInfo<
|
|
||||||
'/settings/notifications',
|
|
||||||
'/settings/notifications',
|
|
||||||
Record<never, never>,
|
|
||||||
Record<never, never>,
|
|
||||||
| never
|
|
||||||
>,
|
|
||||||
'/tasks/': RouteRecordInfo<
|
'/tasks/': RouteRecordInfo<
|
||||||
'/tasks/',
|
'/tasks/',
|
||||||
'/tasks',
|
'/tasks',
|
||||||
@@ -192,36 +164,12 @@ declare module 'vue-router/auto-routes' {
|
|||||||
views:
|
views:
|
||||||
| never
|
| never
|
||||||
}
|
}
|
||||||
'src/pages/settings/index.vue': {
|
|
||||||
routes:
|
|
||||||
| '/settings/'
|
|
||||||
views:
|
|
||||||
| never
|
|
||||||
}
|
|
||||||
'src/pages/settings/account.vue': {
|
|
||||||
routes:
|
|
||||||
| '/settings/account'
|
|
||||||
views:
|
|
||||||
| never
|
|
||||||
}
|
|
||||||
'src/pages/settings/appearance.vue': {
|
'src/pages/settings/appearance.vue': {
|
||||||
routes:
|
routes:
|
||||||
| '/settings/appearance'
|
| '/settings/appearance'
|
||||||
views:
|
views:
|
||||||
| never
|
| never
|
||||||
}
|
}
|
||||||
'src/pages/settings/display.vue': {
|
|
||||||
routes:
|
|
||||||
| '/settings/display'
|
|
||||||
views:
|
|
||||||
| never
|
|
||||||
}
|
|
||||||
'src/pages/settings/notifications.vue': {
|
|
||||||
routes:
|
|
||||||
| '/settings/notifications'
|
|
||||||
views:
|
|
||||||
| never
|
|
||||||
}
|
|
||||||
'src/pages/tasks/index.vue': {
|
'src/pages/tasks/index.vue': {
|
||||||
routes:
|
routes:
|
||||||
| '/tasks/'
|
| '/tasks/'
|
||||||
|
|||||||
Reference in New Issue
Block a user