Refactor task components, add mobile-friendly enhancements, and improve settings management
- Introduced `TodoItemTouch`, a responsive task item optimized for mobile interaction with swipe actions. - Added `MobileActions` for streamlined task creation and settings access on smaller screens. - Refactored `App.vue` to adapt layouts dynamically for mobile and desktop users. - Implemented collapsible, categorized task lists with improved handling in `TodoList` and `TodoListTouch`. - Improved task swipe actions with `useSwipe` for smoother UI interactions. - Updated styling with DaisyUI theme customization and multi-theme support. - Enhanced `useSettings` with a `todayShown` toggle for quick agenda visibility.
This commit is contained in:
@@ -1,68 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { Task } from '../types.ts'
|
||||
import { PhPlus } from '@phosphor-icons/vue'
|
||||
import { useTemplateRef } from 'vue'
|
||||
import { useTasks } from '../composables/useTasks.ts'
|
||||
import { router } from '../router.ts'
|
||||
|
||||
const { createTask } = useTasks()
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
const data = new FormData(e.target as HTMLFormElement)
|
||||
const task: Partial<Task> = Object.fromEntries(data)
|
||||
|
||||
await createTask(task)
|
||||
await router.push('/')
|
||||
}
|
||||
const createModal = useTemplateRef('createModal')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="fab">
|
||||
<button class="btn btn-xl btn-circle btn-secondary" @click="createModal?.showModal()">
|
||||
<PhPlus size="24" weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
<dialog ref="createModal" class="modal">
|
||||
<div class="modal-box">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">
|
||||
✕
|
||||
</button>
|
||||
</form>
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
What is your name?
|
||||
</legend>
|
||||
<input type="text" class="input" name="title" placeholder="Type here">
|
||||
</fieldset>
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
Category
|
||||
</legend>
|
||||
<select class="select" name="tag">
|
||||
<option disabled selected value="@uncategorized">
|
||||
@uncategorized
|
||||
</option>
|
||||
<option value="@home">
|
||||
@home
|
||||
</option>
|
||||
<option value="@work">
|
||||
Amber
|
||||
</option>
|
||||
</select>
|
||||
</fieldset>
|
||||
<button class="btn btn-primary">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
69
src/components/MobileActions.vue
Normal file
69
src/components/MobileActions.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
import { PhDotsNine, PhPlus, PhSliders, PhX } from '@phosphor-icons/vue'
|
||||
import { ref, useTemplateRef } from 'vue'
|
||||
import CreateForm from './forms/CreateForm.vue'
|
||||
import EditForm from './forms/EditForm.vue'
|
||||
import SettingsForm from './forms/SettingsForm.vue'
|
||||
|
||||
const modalComponent = useTemplateRef('modalComponent')
|
||||
|
||||
type ModalShown = 'create' | 'edit' | 'settings'
|
||||
|
||||
const modalShown = ref<ModalShown>()
|
||||
|
||||
const componentMap = {
|
||||
create: CreateForm,
|
||||
edit: EditForm,
|
||||
settings: SettingsForm,
|
||||
}
|
||||
|
||||
function showModal(component: ModalShown) {
|
||||
modalShown.value = component
|
||||
modalComponent.value?.showModal()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="fab select-none">
|
||||
<!-- a focusable div with tabindex is necessary to work on all browsers. role="button" is necessary for accessibility -->
|
||||
<div tabindex="0" role="button" class="btn btn-xl btn-circle bg-white border border-black border-2">
|
||||
<PhDotsNine :size="30" weight="bold" />
|
||||
</div>
|
||||
|
||||
<!-- close button should not be focusable so it can close the FAB when clicked. It's just a visual placeholder -->
|
||||
<div class="fab-close">
|
||||
<span class="btn btn-xl btn-circle bg-white border border-black border-2"><PhX size="30" weight="bold" /></span>
|
||||
</div>
|
||||
|
||||
<!-- buttons that show up when FAB is open -->
|
||||
<div>
|
||||
<button class="btn btn-xl btn-circle bg-white border border-black border-2" @click="showModal('create')">
|
||||
<PhPlus size="30" weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-xl btn-circle bg-white border border-black border-2" @click="showModal('settings')">
|
||||
<PhSliders :size="30" weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<dialog ref="modalComponent" class="modal">
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
<div class="modal-box">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">
|
||||
<PhX size="24" />
|
||||
</button>
|
||||
</form>
|
||||
<component :is="componentMap[modalShown]" v-if="modalShown" />
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -3,9 +3,7 @@ import { PhX } from '@phosphor-icons/vue'
|
||||
import { onKeyStroke, useMagicKeys, whenever } from '@vueuse/core'
|
||||
|
||||
import { useTemplateRef } from 'vue'
|
||||
import { useSettings } from '../composables/useSettings.ts'
|
||||
|
||||
const { settings } = useSettings()
|
||||
import SettingsForm from './forms/SettingsForm.vue'
|
||||
|
||||
const settingsModal = useTemplateRef('settingsModal')
|
||||
|
||||
@@ -26,26 +24,16 @@ onKeyStroke('Escape', (e) => {
|
||||
<template>
|
||||
<div>
|
||||
<dialog ref="settingsModal" class="modal">
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
<div class="modal-box">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">
|
||||
<PhX size="24" />
|
||||
</button>
|
||||
</form>
|
||||
<form @submit.prevent>
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
Username
|
||||
</legend>
|
||||
<input v-model="settings.username" type="text" class="input" name="username">
|
||||
</fieldset>
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
Password
|
||||
</legend>
|
||||
<input v-model="settings.password" type="text" class="input" name="password">
|
||||
</fieldset>
|
||||
</form>
|
||||
<SettingsForm />
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { UseSwipeDirection } from '@vueuse/core'
|
||||
import type { Task } from '../types.ts'
|
||||
import { PhCheckSquare, PhDotsThree, PhFlag, PhPause, PhPlay, PhSquare, PhX } from '@phosphor-icons/vue'
|
||||
import { PhCheckSquare, PhClockCountdown, PhFlag, PhPause, PhPlay, PhSquare, PhTrash } from '@phosphor-icons/vue'
|
||||
import { onClickOutside, useSwipe } from '@vueuse/core'
|
||||
import { DateTime } from 'luxon'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useTasks } from '../composables/useTasks.ts'
|
||||
import { computed, shallowRef, useTemplateRef } from 'vue'
|
||||
import useActions from '../composables/useActions.ts'
|
||||
import { TaskStatus } from '../types.ts'
|
||||
|
||||
const { task } = defineProps<{ task: Task }>()
|
||||
|
||||
const { updateTask } = useTasks()
|
||||
const { run } = useActions()
|
||||
|
||||
const dueColor = computed(() => {
|
||||
const dueDiff = task.dueDate ? DateTime.fromMillis(task.dueDate).diffNow('days').days : undefined
|
||||
@@ -28,57 +30,90 @@ const dueColor = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const statusSelectVisible = ref(false)
|
||||
const target = useTemplateRef('target')
|
||||
const container = useTemplateRef('container')
|
||||
const containerWidth = computed(() => container.value?.offsetWidth)
|
||||
const left = shallowRef('0')
|
||||
const opacity = shallowRef(1)
|
||||
|
||||
async function handleClick(update: Partial<Task>) {
|
||||
updateTask({ ...task, ...update })
|
||||
statusSelectVisible.value = false
|
||||
function reset() {
|
||||
left.value = '0'
|
||||
opacity.value = 1
|
||||
}
|
||||
const { isSwiping, lengthX } = useSwipe(
|
||||
target,
|
||||
{
|
||||
passive: false,
|
||||
onSwipe(_e: TouchEvent) {
|
||||
if (containerWidth.value) {
|
||||
if (lengthX.value < 0) {
|
||||
const length = Math.abs(lengthX.value)
|
||||
left.value = `${length}px`
|
||||
opacity.value = 1.1 - length / containerWidth.value
|
||||
}
|
||||
else {
|
||||
left.value = '0'
|
||||
opacity.value = 1
|
||||
}
|
||||
}
|
||||
},
|
||||
onSwipeEnd(_e: TouchEvent, _direction: UseSwipeDirection) {
|
||||
if (lengthX.value < 0 && containerWidth.value && (Math.abs(lengthX.value) / containerWidth.value) >= 0.5) {
|
||||
left.value = '100%'
|
||||
opacity.value = 0
|
||||
}
|
||||
else {
|
||||
left.value = '0'
|
||||
opacity.value = 1
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
async function handleClick(command: string) {
|
||||
await run(command)
|
||||
reset()
|
||||
}
|
||||
|
||||
onClickOutside(container, reset)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li class="list-row">
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="btn btn-square btn-ghost" @click="statusSelectVisible = !statusSelectVisible">
|
||||
<PhX v-if="statusSelectVisible" :size="20" />
|
||||
<template v-else>
|
||||
<PhSquare v-if="task.status === TaskStatus.WAIT" :size="20" />
|
||||
<PhCheckSquare v-else-if="task.status === TaskStatus.DONE" :size="20" weight="fill" class="text-success" />
|
||||
<PhFlag v-else-if="task.status === TaskStatus.FLAG" :size="20" weight="fill" class="text-warning" />
|
||||
<PhPlay v-else-if="task.status === TaskStatus.WIP" :size="20" weight="fill" class="text-info" />
|
||||
</template>
|
||||
<li ref="container" class="badge badge-xl badge-neutral badge-outline w-full h-auto py-2 select-none relative overflow-hidden">
|
||||
<div class=" flex flex-row justify-start gap-2 w-full">
|
||||
<template v-if="task.status !== TaskStatus.WIP">
|
||||
<button class="btn btn-square btn-ghost" @click="handleClick(`check ${task.id_}`)">
|
||||
<PhCheckSquare v-if="task.status !== TaskStatus.DONE" :size="30" class="text-success" /><PhSquare v-else :size="30" />
|
||||
</button>
|
||||
<button v-if="task.status !== TaskStatus.FLAG" class="btn btn-square btn-ghost" @click="handleClick(`flag ${task.id_}`)">
|
||||
<PhFlag :size="30" weight="fill" class="text-warning" />
|
||||
</button>
|
||||
<button class="btn btn-square btn-ghost" @click="handleClick(`start ${task.id_}`)">
|
||||
<PhPlay :size="30" weight="fill" class="text-info" />
|
||||
</button>
|
||||
</template>
|
||||
<button v-else class="btn btn-square btn-ghost" @click="handleClick(`stop ${task.id_}`)">
|
||||
<PhPause :size="30" weight="fill" class="text-info" />
|
||||
</button>
|
||||
<Transition>
|
||||
<div v-if="statusSelectVisible" class="">
|
||||
<template v-if="task.status !== TaskStatus.WIP">
|
||||
<button v-if="task.status !== TaskStatus.DONE" class="btn btn-square btn-ghost" @click="handleClick({ status: TaskStatus.DONE })">
|
||||
<PhCheckSquare :size="24" weight="regular" class="text-success" />
|
||||
</button>
|
||||
<button v-if="task.status !== TaskStatus.WAIT" class="btn btn-square btn-ghost" @click="handleClick({ status: TaskStatus.WAIT })">
|
||||
<PhSquare :size="24" weight="regular" />
|
||||
</button>
|
||||
<button v-if="task.status !== TaskStatus.FLAG" class="btn btn-square btn-ghost" @click="handleClick({ status: TaskStatus.FLAG })">
|
||||
<PhFlag :size="24" weight="fill" class="text-warning" />
|
||||
</button>
|
||||
<button class="btn btn-square btn-ghost" @click="handleClick({ status: TaskStatus.WIP })">
|
||||
<PhPlay :size="24" weight="fill" class="text-info" />
|
||||
</button>
|
||||
</template>
|
||||
<button v-else class="btn btn-square btn-ghost" @click="handleClick({ status: TaskStatus.WAIT })">
|
||||
<PhPause :size="24" weight="fill" class="text-info" />
|
||||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center">
|
||||
<div>{{ task.id }} {{ task.id_ }} {{ task.title }}</div>
|
||||
<div v-if="task.dueDate" :class="dueColor">
|
||||
{{ DateTime.fromMillis(task.dueDate).toFormat('dd/MM/yyyy') }}
|
||||
<div class="grow flex justify-end">
|
||||
<button class="btn btn-square btn-ghost" @click="handleClick(`delete ${task.id_}`)">
|
||||
<PhTrash :size="30" weight="fill" class="text-error" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="target" :class="{ 'transition-all': !isSwiping }" :style="{ left, opacity }" class="top-0 left-0 w-full h-full absolute rounded bg-white font-mono text-md font-semibold flex flex-row justify-start items-center gap-2 min-h-10 px-2">
|
||||
<div class="flex items-center justify-center">
|
||||
<PhSquare v-if="task.status === TaskStatus.WAIT" :size="30" />
|
||||
<PhCheckSquare v-else-if="task.status === TaskStatus.DONE" :size="30" weight="fill" class="text-success" />
|
||||
<PhFlag v-else-if="task.status === TaskStatus.FLAG" :size="30" weight="fill" class="text-warning" />
|
||||
<PhPlay v-else-if="task.status === TaskStatus.WIP" :size="30" weight="fill" class="text-info" />
|
||||
</div>
|
||||
<div class="grow">
|
||||
{{ task.title }}
|
||||
</div>
|
||||
<div v-if="task.dueDate" :class="dueColor" class="text-right">
|
||||
<PhClockCountdown :size="30" :weight="dueColor === 'text-error' ? 'fill' : 'regular'" />
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-square btn-ghost">
|
||||
<PhDotsThree :size="24" weight="regular" />
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
|
||||
45
src/components/TodoList.vue
Normal file
45
src/components/TodoList.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import type { Task } from '../types.ts'
|
||||
import { ref } from 'vue'
|
||||
import { useSettings } from '../composables/useSettings.ts'
|
||||
import TodoItem from './TodoItem.vue'
|
||||
|
||||
const { categorizedTasks, today } = defineProps<{ categorizedTasks: Record<string, Task[]>, today: { today: Task[], next7days: Task[] } }>()
|
||||
const { settings } = useSettings()
|
||||
const collapsed = ref<string[]>([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div v-for="(catTasks, category) in categorizedTasks" :key="category" class="m-4 ">
|
||||
<div class="mb-4">
|
||||
<button class="badge badge-lg badge-neutral badge-outline font-mono font-bold" @click="collapsed.includes(category) ? collapsed.splice(collapsed.indexOf(category), 1) : collapsed.push(category)">
|
||||
{{ category }} [{{ catTasks.length }}]
|
||||
</button>
|
||||
</div>
|
||||
<Transition name="fade">
|
||||
<ul v-if="!collapsed.includes(category)" class="space-y-2">
|
||||
<TodoItem v-for="task in catTasks" :key="task.id" :task />
|
||||
</ul>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="{ 'translate-x-full': !settings.todayShown }" class=" transition-transform duration-500 border-primary border-l-2 border-t-2 fixed bottom-0 right-0 top-0 w-1/2 max-w-sm text-center text-sm bg-neutral-50">
|
||||
<div class="">
|
||||
today
|
||||
</div><div v-for="task in today.today" :key="task.id">
|
||||
{{ task.title }}
|
||||
</div>
|
||||
<div class="">
|
||||
next 7 days
|
||||
</div><div v-for="task in today.next7days" :key="task.id">
|
||||
{{ task.title }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
45
src/components/TodoListTouch.vue
Normal file
45
src/components/TodoListTouch.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import type { Task } from '../types.ts'
|
||||
import { ref } from 'vue'
|
||||
import { useSettings } from '../composables/useSettings.ts'
|
||||
import TodoItemTouch from './TodoItemTouch.vue'
|
||||
|
||||
const { categorizedTasks, today } = defineProps<{ categorizedTasks: Record<string, Task[]>, today: { today: Task[], next7days: Task[] } }>()
|
||||
const { settings } = useSettings()
|
||||
const collapsed = ref<string[]>([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div v-for="(catTasks, category) in categorizedTasks" :key="category" class="m-4 ">
|
||||
<div class="mb-4">
|
||||
<button class="badge badge-xl badge-neutral badge-outline font-mono font-extrabold" @click="collapsed.includes(category) ? collapsed.splice(collapsed.indexOf(category), 1) : collapsed.push(category)">
|
||||
{{ category }} [{{ catTasks.length }}]
|
||||
</button>
|
||||
</div>
|
||||
<Transition name="fade">
|
||||
<ul v-if="!collapsed.includes(category)" class="space-y-2">
|
||||
<TodoItemTouch v-for="task in catTasks" :key="task.id" :task />
|
||||
</ul>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="{ 'translate-x-full': !settings.todayShown }" class=" transition-transform duration-500 border-l border-t fixed bottom-0 right-0 top-0 w-1/2 max-w-sm text-center text-sm bg-neutral-50">
|
||||
<div class="">
|
||||
today
|
||||
</div><div v-for="task in today.today" :key="task.id">
|
||||
{{ task.title }}
|
||||
</div>
|
||||
<div class="">
|
||||
next 7 days
|
||||
</div><div v-for="task in today.next7days" :key="task.id">
|
||||
{{ task.title }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
47
src/components/forms/CreateForm.vue
Normal file
47
src/components/forms/CreateForm.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import type { Task } from '../../types.ts'
|
||||
import { useTasks } from '../../composables/useTasks.ts'
|
||||
|
||||
const { createTask } = useTasks()
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
const data = new FormData(e.target as HTMLFormElement)
|
||||
const task = Object.fromEntries(data) as unknown as Pick<Task, 'tag' | 'title' | 'dueDate'>
|
||||
|
||||
await createTask(task)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
What is your name?
|
||||
</legend>
|
||||
<input type="text" class="input" name="title" placeholder="Type here">
|
||||
</fieldset>
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
Category
|
||||
</legend>
|
||||
<select class="select" name="tag">
|
||||
<option disabled selected value="@uncategorized">
|
||||
@uncategorized
|
||||
</option>
|
||||
<option value="@home">
|
||||
@home
|
||||
</option>
|
||||
<option value="@work">
|
||||
Amber
|
||||
</option>
|
||||
</select>
|
||||
</fieldset>
|
||||
<button class="btn btn-primary">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
47
src/components/forms/EditForm.vue
Normal file
47
src/components/forms/EditForm.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import type { Task } from '../../types.ts'
|
||||
import { useTasks } from '../../composables/useTasks.ts'
|
||||
|
||||
const { createTask } = useTasks()
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
const data = new FormData(e.target as HTMLFormElement)
|
||||
const task = Object.fromEntries(data) as unknown as Pick<Task, 'tag' | 'title' | 'dueDate'>
|
||||
|
||||
await createTask(task)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
What is your name?
|
||||
</legend>
|
||||
<input type="text" class="input" name="title" placeholder="Type here">
|
||||
</fieldset>
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
Category
|
||||
</legend>
|
||||
<select class="select" name="tag">
|
||||
<option disabled selected value="@uncategorized">
|
||||
@uncategorized
|
||||
</option>
|
||||
<option value="@home">
|
||||
@home
|
||||
</option>
|
||||
<option value="@work">
|
||||
Amber
|
||||
</option>
|
||||
</select>
|
||||
</fieldset>
|
||||
<button class="btn btn-primary">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
22
src/components/forms/SettingsForm.vue
Normal file
22
src/components/forms/SettingsForm.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { useSettings } from '../../composables/useSettings.ts'
|
||||
|
||||
const { settings } = useSettings()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit.prevent>
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
Username
|
||||
</legend>
|
||||
<input v-model="settings.username" type="text" class="input" name="username">
|
||||
</fieldset>
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
Password
|
||||
</legend>
|
||||
<input v-model="settings.password" type="password" class="input" name="password">
|
||||
</fieldset>
|
||||
</form>
|
||||
</template>
|
||||
Reference in New Issue
Block a user