Refactor input elements for consistency and readability; update styles for better alignment and spacing in SignupPage and ListDetailPage.
This commit is contained in:
parent
18f759aa7c
commit
29682b7e9c
@ -1,13 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="container page-padding">
|
<main class="container page-padding">
|
||||||
<div v-if="loading" class="text-center">
|
<div v-if="loading" class="text-center">
|
||||||
<div class="spinner-dots" role="status"><span/><span/><span/></div>
|
<div class="spinner-dots" role="status"><span /><span /><span /></div>
|
||||||
<p>Loading list details...</p>
|
<p>Loading list details...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="error" class="alert alert-error mb-3" role="alert">
|
<div v-else-if="error" class="alert alert-error mb-3" role="alert">
|
||||||
<div class="alert-content">
|
<div class="alert-content">
|
||||||
<svg class="icon" aria-hidden="true"><use xlink:href="#icon-alert-triangle" /></svg>
|
<svg class="icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#icon-alert-triangle" />
|
||||||
|
</svg>
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-sm btn-danger" @click="fetchListDetails">Retry</button>
|
<button type="button" class="btn btn-sm btn-danger" @click="fetchListDetails">Retry</button>
|
||||||
@ -18,11 +20,15 @@
|
|||||||
<h1>{{ list.name }}</h1>
|
<h1>{{ list.name }}</h1>
|
||||||
<div class="flex items-center flex-wrap" style="gap: 0.5rem;">
|
<div class="flex items-center flex-wrap" style="gap: 0.5rem;">
|
||||||
<button class="btn btn-neutral btn-sm" @click="showCostSummaryDialog = true">
|
<button class="btn btn-neutral btn-sm" @click="showCostSummaryDialog = true">
|
||||||
<svg class="icon icon-sm"><use xlink:href="#icon-clipboard"/></svg> <!-- Placeholder icon -->
|
<svg class="icon icon-sm">
|
||||||
|
<use xlink:href="#icon-clipboard" />
|
||||||
|
</svg> <!-- Placeholder icon -->
|
||||||
Cost Summary
|
Cost Summary
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-secondary btn-sm" @click="openOcrDialog">
|
<button class="btn btn-secondary btn-sm" @click="openOcrDialog">
|
||||||
<svg class="icon icon-sm"><use xlink:href="#icon-plus"/></svg> <!-- Placeholder, camera_alt not in Valerie -->
|
<svg class="icon icon-sm">
|
||||||
|
<use xlink:href="#icon-plus" />
|
||||||
|
</svg> <!-- Placeholder, camera_alt not in Valerie -->
|
||||||
Add via OCR
|
Add via OCR
|
||||||
</button>
|
</button>
|
||||||
<span class="item-badge ml-1" :class="list.is_complete ? 'badge-settled' : 'badge-pending'">
|
<span class="item-badge ml-1" :class="list.is_complete ? 'badge-settled' : 'badge-pending'">
|
||||||
@ -37,27 +43,15 @@
|
|||||||
<div class="flex items-end flex-wrap" style="gap: 1rem;">
|
<div class="flex items-end flex-wrap" style="gap: 1rem;">
|
||||||
<div class="form-group flex-grow" style="margin-bottom: 0;">
|
<div class="form-group flex-grow" style="margin-bottom: 0;">
|
||||||
<label for="newItemName" class="form-label">Item Name</label>
|
<label for="newItemName" class="form-label">Item Name</label>
|
||||||
<input
|
<input type="text" id="newItemName" v-model="newItem.name" class="form-input" required
|
||||||
type="text"
|
ref="itemNameInputRef" />
|
||||||
id="newItemName"
|
|
||||||
v-model="newItem.name"
|
|
||||||
class="form-input"
|
|
||||||
required
|
|
||||||
ref="itemNameInputRef"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="margin-bottom: 0; min-width: 120px;">
|
<div class="form-group" style="margin-bottom: 0; min-width: 120px;">
|
||||||
<label for="newItemQuantity" class="form-label">Quantity</label>
|
<label for="newItemQuantity" class="form-label">Quantity</label>
|
||||||
<input
|
<input type="number" id="newItemQuantity" v-model="newItem.quantity" class="form-input" min="1" />
|
||||||
type="number"
|
|
||||||
id="newItemQuantity"
|
|
||||||
v-model="newItem.quantity"
|
|
||||||
class="form-input"
|
|
||||||
min="1"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary" :disabled="addingItem">
|
<button type="submit" class="btn btn-primary" :disabled="addingItem">
|
||||||
<span v-if="addingItem" class="spinner-dots-sm" role="status"><span/><span/><span/></span>
|
<span v-if="addingItem" class="spinner-dots-sm" role="status"><span /><span /><span /></span>
|
||||||
Add Item
|
Add Item
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -66,31 +60,23 @@
|
|||||||
|
|
||||||
<!-- Items List -->
|
<!-- Items List -->
|
||||||
<div v-if="list.items.length === 0" class="card empty-state-card">
|
<div v-if="list.items.length === 0" class="card empty-state-card">
|
||||||
<svg class="icon icon-lg" aria-hidden="true"><use xlink:href="#icon-clipboard" /></svg>
|
<svg class="icon icon-lg" aria-hidden="true">
|
||||||
|
<use xlink:href="#icon-clipboard" />
|
||||||
|
</svg>
|
||||||
<h3>No Items Yet!</h3>
|
<h3>No Items Yet!</h3>
|
||||||
<p>This list is empty. Add some items using the form above.</p>
|
<p>This list is empty. Add some items using the form above.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul v-else class="item-list">
|
<ul v-else class="item-list">
|
||||||
<li
|
<li v-for="item in list.items" :key="item.id" class="list-item"
|
||||||
v-for="item in list.items"
|
:class="{ 'completed': item.is_complete, 'is-swiped': item.swiped }" @touchstart="handleTouchStart"
|
||||||
:key="item.id"
|
@touchmove="handleTouchMove" @touchend="handleTouchEnd">
|
||||||
class="list-item"
|
|
||||||
:class="{ 'completed': item.is_complete, 'is-swiped': item.swiped }"
|
|
||||||
@touchstart="handleTouchStart($event, item)"
|
|
||||||
@touchmove="handleTouchMove($event, item)"
|
|
||||||
@touchend="handleTouchEnd(item)"
|
|
||||||
>
|
|
||||||
<div class="list-item-content">
|
<div class="list-item-content">
|
||||||
<div class="list-item-main">
|
<div class="list-item-main">
|
||||||
<label class="checkbox-label mb-0 flex-shrink-0">
|
<label class="checkbox-label mb-0 flex-shrink-0">
|
||||||
<input
|
<input type="checkbox" :checked="item.is_complete"
|
||||||
type="checkbox"
|
|
||||||
:checked="item.is_complete"
|
|
||||||
@change="confirmUpdateItem(item, ($event.target as HTMLInputElement).checked)"
|
@change="confirmUpdateItem(item, ($event.target as HTMLInputElement).checked)"
|
||||||
:disabled="item.updating"
|
:disabled="item.updating" :aria-label="item.name" />
|
||||||
:aria-label="item.name"
|
|
||||||
/>
|
|
||||||
<span class="checkmark"></span>
|
<span class="checkmark"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="item-text flex-grow">
|
<div class="item-text flex-grow">
|
||||||
@ -98,28 +84,19 @@
|
|||||||
<small v-if="item.quantity" class="item-caption">Quantity: {{ item.quantity }}</small>
|
<small v-if="item.quantity" class="item-caption">Quantity: {{ item.quantity }}</small>
|
||||||
<div v-if="item.is_complete" class="form-group mt-1" style="max-width: 150px; margin-bottom: 0;">
|
<div v-if="item.is_complete" class="form-group mt-1" style="max-width: 150px; margin-bottom: 0;">
|
||||||
<label :for="`price-${item.id}`" class="sr-only">Price for {{ item.name }}</label>
|
<label :for="`price-${item.id}`" class="sr-only">Price for {{ item.name }}</label>
|
||||||
<input
|
<input :id="`price-${item.id}`" type="number" v-model.number="item.priceInput"
|
||||||
:id="`price-${item.id}`"
|
class="form-input form-input-sm" placeholder="Price" step="0.01" @blur="updateItemPrice(item)"
|
||||||
type="number"
|
@keydown.enter.prevent="($event.target as HTMLInputElement).blur()" />
|
||||||
v-model.number="item.priceInput"
|
|
||||||
class="form-input form-input-sm"
|
|
||||||
placeholder="Price"
|
|
||||||
step="0.01"
|
|
||||||
@blur="updateItemPrice(item)"
|
|
||||||
@keydown.enter.prevent="($event.target as HTMLInputElement).blur()"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Non-swipe actions can be added here or handled by swipe -->
|
<!-- Non-swipe actions can be added here or handled by swipe -->
|
||||||
<div class="list-item-actions">
|
<div class="list-item-actions">
|
||||||
<button
|
<button class="btn btn-danger btn-sm btn-icon-only" @click.stop="confirmDeleteItem(item)"
|
||||||
class="btn btn-danger btn-sm btn-icon-only"
|
:disabled="item.deleting" aria-label="Delete item">
|
||||||
@click.stop="confirmDeleteItem(item)"
|
<svg class="icon icon-sm">
|
||||||
:disabled="item.deleting"
|
<use xlink:href="#icon-trash"></use>
|
||||||
aria-label="Delete item"
|
</svg>
|
||||||
>
|
|
||||||
<svg class="icon icon-sm"><use xlink:href="#icon-trash"></use></svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -133,11 +110,13 @@
|
|||||||
<div class="modal-container" ref="ocrModalRef" style="min-width: 400px;">
|
<div class="modal-container" ref="ocrModalRef" style="min-width: 400px;">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h3>Add Items via OCR</h3>
|
<h3>Add Items via OCR</h3>
|
||||||
<button class="close-button" @click="closeOcrDialog" aria-label="Close"><svg class="icon"><use xlink:href="#icon-close"/></svg></button>
|
<button class="close-button" @click="closeOcrDialog" aria-label="Close"><svg class="icon">
|
||||||
|
<use xlink:href="#icon-close" />
|
||||||
|
</svg></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div v-if="ocrLoading" class="text-center">
|
<div v-if="ocrLoading" class="text-center">
|
||||||
<div class="spinner-dots"><span/><span/><span/></div>
|
<div class="spinner-dots"><span /><span /><span /></div>
|
||||||
<p>Processing image...</p>
|
<p>Processing image...</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="ocrItems.length > 0">
|
<div v-else-if="ocrItems.length > 0">
|
||||||
@ -147,7 +126,9 @@
|
|||||||
<div class="list-item-content flex items-center" style="gap: 0.5rem;">
|
<div class="list-item-content flex items-center" style="gap: 0.5rem;">
|
||||||
<input type="text" v-model="ocrItem.name" class="form-input flex-grow" required />
|
<input type="text" v-model="ocrItem.name" class="form-input flex-grow" required />
|
||||||
<button class="btn btn-danger btn-sm btn-icon-only" @click="ocrItems.splice(index, 1)">
|
<button class="btn btn-danger btn-sm btn-icon-only" @click="ocrItems.splice(index, 1)">
|
||||||
<svg class="icon icon-sm"><use xlink:href="#icon-trash"/></svg>
|
<svg class="icon icon-sm">
|
||||||
|
<use xlink:href="#icon-trash" />
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -155,20 +136,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else class="form-group">
|
<div v-else class="form-group">
|
||||||
<label for="ocrFile" class="form-label">Upload Image</label>
|
<label for="ocrFile" class="form-label">Upload Image</label>
|
||||||
<input type="file" id="ocrFile" class="form-input" accept="image/*" @change="handleOcrFileUpload" ref="ocrFileInputRef"/>
|
<input type="file" id="ocrFile" class="form-input" accept="image/*" @change="handleOcrFileUpload"
|
||||||
|
ref="ocrFileInputRef" />
|
||||||
<p v-if="ocrError" class="form-error-text mt-1">{{ ocrError }}</p>
|
<p v-if="ocrError" class="form-error-text mt-1">{{ ocrError }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-neutral" @click="closeOcrDialog">Cancel</button>
|
<button type="button" class="btn btn-neutral" @click="closeOcrDialog">Cancel</button>
|
||||||
<button
|
<button v-if="ocrItems.length > 0" type="button" class="btn btn-primary ml-2" @click="addOcrItems"
|
||||||
v-if="ocrItems.length > 0"
|
:disabled="addingOcrItems">
|
||||||
type="button"
|
<span v-if="addingOcrItems" class="spinner-dots-sm"><span /><span /><span /></span>
|
||||||
class="btn btn-primary ml-2"
|
|
||||||
@click="addOcrItems"
|
|
||||||
:disabled="addingOcrItems"
|
|
||||||
>
|
|
||||||
<span v-if="addingOcrItems" class="spinner-dots-sm"><span/><span/><span/></span>
|
|
||||||
Add Items
|
Add Items
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -180,11 +157,14 @@
|
|||||||
<div class="modal-container" ref="costSummaryModalRef" style="min-width: 550px;">
|
<div class="modal-container" ref="costSummaryModalRef" style="min-width: 550px;">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h3>List Cost Summary</h3>
|
<h3>List Cost Summary</h3>
|
||||||
<button class="close-button" @click="showCostSummaryDialog = false" aria-label="Close"><svg class="icon"><use xlink:href="#icon-close"/></svg></button>
|
<button class="close-button" @click="showCostSummaryDialog = false" aria-label="Close"><svg class="icon">
|
||||||
|
<use xlink:href="#icon-close" />
|
||||||
|
</svg></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div v-if="costSummaryLoading" class="text-center">
|
<div v-if="costSummaryLoading" class="text-center">
|
||||||
<div class="spinner-dots"><span/><span/><span/></div><p>Loading summary...</p>
|
<div class="spinner-dots"><span /><span /><span /></div>
|
||||||
|
<p>Loading summary...</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="costSummaryError" class="alert alert-error">{{ costSummaryError }}</div>
|
<div v-else-if="costSummaryError" class="alert alert-error">{{ costSummaryError }}</div>
|
||||||
<div v-else-if="listCostSummary">
|
<div v-else-if="listCostSummary">
|
||||||
@ -210,7 +190,8 @@
|
|||||||
<td class="text-right">{{ formatCurrency(userShare.items_added_value) }}</td>
|
<td class="text-right">{{ formatCurrency(userShare.items_added_value) }}</td>
|
||||||
<td class="text-right">{{ formatCurrency(userShare.amount_due) }}</td>
|
<td class="text-right">{{ formatCurrency(userShare.amount_due) }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<span class="item-badge" :class="parseFloat(String(userShare.balance)) >= 0 ? 'badge-settled' : 'badge-pending'">
|
<span class="item-badge"
|
||||||
|
:class="parseFloat(String(userShare.balance)) >= 0 ? 'badge-settled' : 'badge-pending'">
|
||||||
{{ formatCurrency(userShare.balance) }}
|
{{ formatCurrency(userShare.balance) }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
@ -232,10 +213,14 @@
|
|||||||
<div class="modal-container confirm-modal" ref="confirmModalRef">
|
<div class="modal-container confirm-modal" ref="confirmModalRef">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h3>Confirmation</h3>
|
<h3>Confirmation</h3>
|
||||||
<button class="close-button" @click="cancelConfirmation" aria-label="Close"><svg class="icon"><use xlink:href="#icon-close"/></svg></button>
|
<button class="close-button" @click="cancelConfirmation" aria-label="Close"><svg class="icon">
|
||||||
|
<use xlink:href="#icon-close" />
|
||||||
|
</svg></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<svg class="icon icon-lg mb-2" style="color: var(--warning);"><use xlink:href="#icon-alert-triangle"/></svg>
|
<svg class="icon icon-lg mb-2" style="color: var(--warning);">
|
||||||
|
<use xlink:href="#icon-alert-triangle" />
|
||||||
|
</svg>
|
||||||
<p>{{ confirmDialogMessage }}</p>
|
<p>{{ confirmDialogMessage }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@ -304,7 +289,7 @@ const list = ref<List | null>(null);
|
|||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const error = ref<string | null>(null);
|
const error = ref<string | null>(null);
|
||||||
const addingItem = ref(false);
|
const addingItem = ref(false);
|
||||||
const pollingInterval = ref<NodeJS.Timeout | null>(null);
|
const pollingInterval = ref<ReturnType<typeof setInterval> | null>(null);
|
||||||
const lastListUpdate = ref<string | null>(null);
|
const lastListUpdate = ref<string | null>(null);
|
||||||
const lastItemUpdate = ref<string | null>(null);
|
const lastItemUpdate = ref<string | null>(null);
|
||||||
|
|
||||||
@ -318,7 +303,7 @@ const ocrLoading = ref(false);
|
|||||||
const ocrItems = ref<{ name: string }[]>([]); // Items extracted from OCR
|
const ocrItems = ref<{ name: string }[]>([]); // Items extracted from OCR
|
||||||
const addingOcrItems = ref(false);
|
const addingOcrItems = ref(false);
|
||||||
const ocrError = ref<string | null>(null);
|
const ocrError = ref<string | null>(null);
|
||||||
const ocrFileInputRef = ref<HTMLInputElement|null>(null);
|
const ocrFileInputRef = ref<HTMLInputElement | null>(null);
|
||||||
const { files: ocrFiles, reset: resetOcrFileDialog } = useFileDialog({
|
const { files: ocrFiles, reset: resetOcrFileDialog } = useFileDialog({
|
||||||
accept: 'image/*',
|
accept: 'image/*',
|
||||||
multiple: false,
|
multiple: false,
|
||||||
@ -581,8 +566,8 @@ const handleOcrUpload = async (file: File) => {
|
|||||||
const response = await apiClient.post(API_ENDPOINTS.OCR.PROCESS, formData, {
|
const response = await apiClient.post(API_ENDPOINTS.OCR.PROCESS, formData, {
|
||||||
headers: { 'Content-Type': 'multipart/form-data' }
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
});
|
});
|
||||||
ocrItems.value = response.data.extracted_items.map((nameStr: string) => ({ name: nameStr.trim() })).filter(item => item.name);
|
ocrItems.value = response.data.extracted_items.map((nameStr: string) => ({ name: nameStr.trim() })).filter((item: { name: string }) => item.name);
|
||||||
if(ocrItems.value.length === 0) {
|
if (ocrItems.value.length === 0) {
|
||||||
ocrError.value = "No items extracted from the image.";
|
ocrError.value = "No items extracted from the image.";
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -656,24 +641,23 @@ useEventListener(window, 'keydown', (event: KeyboardEvent) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Swipe detection (basic)
|
// Swipe detection (basic)
|
||||||
// let touchStartX = 0; // Commented out as unused
|
let touchStartX = 0;
|
||||||
// const SWIPE_THRESHOLD = 50; // pixels // Commented out as unused
|
const SWIPE_THRESHOLD = 50; // pixels
|
||||||
|
|
||||||
const handleTouchStart = (event: TouchEvent, /* item: Item */) => { // Commented out unused item
|
const handleTouchStart = (event: TouchEvent) => {
|
||||||
touchStartX = event.changedTouches[0].clientX;
|
touchStartX = event.changedTouches[0].clientX;
|
||||||
// Add class for visual feedback during swipe if desired
|
// Add class for visual feedback during swipe if desired
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTouchMove = (/* event: TouchEvent, item: Item */) => { // Commented out unused event and item
|
const handleTouchMove = () => {
|
||||||
// Can be used for interactive swipe effect
|
// Can be used for interactive swipe effect
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTouchEnd = (/* item: Item */) => { // Commented out unused item
|
const handleTouchEnd = () => {
|
||||||
// Not implemented: Valerie UI swipe example implies JS adds/removes 'is-swiped'
|
// Not implemented: Valerie UI swipe example implies JS adds/removes 'is-swiped'
|
||||||
// For a simple demo, one might toggle it here based on a more complex gesture
|
// For a simple demo, one might toggle it here based on a more complex gesture
|
||||||
// This would require more state per item and logic
|
// This would require more state per item and logic
|
||||||
// For now, swipe actions are not visually implemented
|
// For now, swipe actions are not visually implemented
|
||||||
// item.swiped = !item.swiped; // Example of toggling. A real implementation would be more complex.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -695,16 +679,45 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.page-padding { padding: 1rem; }
|
.page-padding {
|
||||||
.mb-1 { margin-bottom: 0.5rem; }
|
padding: 1rem;
|
||||||
.mb-2 { margin-bottom: 1rem; }
|
}
|
||||||
.mb-3 { margin-bottom: 1.5rem; }
|
|
||||||
.mt-1 { margin-top: 0.5rem; }
|
.mb-1 {
|
||||||
.mt-2 { margin-top: 1rem; }
|
margin-bottom: 0.5rem;
|
||||||
.ml-1 { margin-left: 0.25rem; }
|
}
|
||||||
.ml-2 { margin-left: 0.5rem; }
|
|
||||||
.text-right { text-align: right; }
|
.mb-2 {
|
||||||
.flex-grow { flex-grow: 1; }
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-3 {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-1 {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-2 {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml-1 {
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml-2 {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-grow {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.item-caption {
|
.item-caption {
|
||||||
display: block;
|
display: block;
|
||||||
@ -712,27 +725,36 @@ onUnmounted(() => {
|
|||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-decoration-line-through {
|
.text-decoration-line-through {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
.form-input-sm { /* For price input */
|
|
||||||
|
.form-input-sm {
|
||||||
|
/* For price input */
|
||||||
padding: 0.4rem 0.6rem;
|
padding: 0.4rem 0.6rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cost-overview p {
|
.cost-overview p {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
font-size: 1.05rem;
|
font-size: 1.05rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-error-text {
|
.form-error-text {
|
||||||
color: var(--danger);
|
color: var(--danger);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-item.completed .item-text {
|
.list-item.completed .item-text {
|
||||||
/* text-decoration: line-through; is handled by span class */
|
/* text-decoration: line-through; is handled by span class */
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-item-actions {
|
.list-item-actions {
|
||||||
margin-left: auto; /* Pushes actions to the right */
|
margin-left: auto;
|
||||||
padding-left: 1rem; /* Space before actions */
|
/* Pushes actions to the right */
|
||||||
|
padding-left: 1rem;
|
||||||
|
/* Space before actions */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -8,29 +8,26 @@
|
|||||||
<form @submit.prevent="onSubmit" class="form-layout">
|
<form @submit.prevent="onSubmit" class="form-layout">
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
<label for="name" class="form-label">Full Name</label>
|
<label for="name" class="form-label">Full Name</label>
|
||||||
<input type="text" id="name" v-model="name" class="form-input" required autocomplete="name"/>
|
<input type="text" id="name" v-model="name" class="form-input" required autocomplete="name" />
|
||||||
<p v-if="formErrors.name" class="form-error-text">{{ formErrors.name }}</p>
|
<p v-if="formErrors.name" class="form-error-text">{{ formErrors.name }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
<label for="email" class="form-label">Email</label>
|
<label for="email" class="form-label">Email</label>
|
||||||
<input type="email" id="email" v-model="email" class="form-input" required autocomplete="email"/>
|
<input type="email" id="email" v-model="email" class="form-input" required autocomplete="email" />
|
||||||
<p v-if="formErrors.email" class="form-error-text">{{ formErrors.email }}</p>
|
<p v-if="formErrors.email" class="form-error-text">{{ formErrors.email }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
<label for="password" class="form-label">Password</label>
|
<label for="password" class="form-label">Password</label>
|
||||||
<div class="input-with-icon-append">
|
<div class="input-with-icon-append">
|
||||||
<input
|
<input :type="isPwdVisible ? 'text' : 'password'" id="password" v-model="password" class="form-input"
|
||||||
:type="isPwdVisible ? 'text' : 'password'"
|
required autocomplete="new-password" />
|
||||||
id="password"
|
<button type="button" @click="isPwdVisible = !isPwdVisible" class="icon-append-btn"
|
||||||
v-model="password"
|
aria-label="Toggle password visibility">
|
||||||
class="form-input"
|
<svg class="icon icon-sm">
|
||||||
required
|
<use :xlink:href="isPwdVisible ? '#icon-settings' : '#icon-settings'"></use>
|
||||||
autocomplete="new-password"
|
</svg> <!-- Placeholder for visibility icons -->
|
||||||
/>
|
|
||||||
<button type="button" @click="isPwdVisible = !isPwdVisible" class="icon-append-btn" aria-label="Toggle password visibility">
|
|
||||||
<svg class="icon icon-sm"><use :xlink:href="isPwdVisible ? '#icon-settings' : '#icon-settings'"></use></svg> <!-- Placeholder for visibility icons -->
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="formErrors.password" class="form-error-text">{{ formErrors.password }}</p>
|
<p v-if="formErrors.password" class="form-error-text">{{ formErrors.password }}</p>
|
||||||
@ -38,21 +35,15 @@
|
|||||||
|
|
||||||
<div class="form-group mb-3">
|
<div class="form-group mb-3">
|
||||||
<label for="confirmPassword" class="form-label">Confirm Password</label>
|
<label for="confirmPassword" class="form-label">Confirm Password</label>
|
||||||
<input
|
<input :type="isPwdVisible ? 'text' : 'password'" id="confirmPassword" v-model="confirmPassword"
|
||||||
:type="isPwdVisible ? 'text' : 'password'"
|
class="form-input" required autocomplete="new-password" />
|
||||||
id="confirmPassword"
|
|
||||||
v-model="confirmPassword"
|
|
||||||
class="form-input"
|
|
||||||
required
|
|
||||||
autocomplete="new-password"
|
|
||||||
/>
|
|
||||||
<p v-if="formErrors.confirmPassword" class="form-error-text">{{ formErrors.confirmPassword }}</p>
|
<p v-if="formErrors.confirmPassword" class="form-error-text">{{ formErrors.confirmPassword }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="formErrors.general" class="alert alert-error form-error-text">{{ formErrors.general }}</p>
|
<p v-if="formErrors.general" class="alert alert-error form-error-text">{{ formErrors.general }}</p>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary w-full mt-2" :disabled="loading">
|
<button type="submit" class="btn btn-primary w-full mt-2" :disabled="loading">
|
||||||
<span v-if="loading" class="spinner-dots-sm" role="status"><span/><span/><span/></span>
|
<span v-if="loading" class="spinner-dots-sm" role="status"><span /><span /><span /></span>
|
||||||
Sign Up
|
Sign Up
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@ -68,7 +59,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useAuthStore } from 'stores/auth'; // Assuming path
|
import { useAuthStore } from '@/stores/auth'; // Assuming path is correct
|
||||||
import { useNotificationStore } from '@/stores/notifications';
|
import { useNotificationStore } from '@/stores/notifications';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -143,10 +134,12 @@ const onSubmit = async () => {
|
|||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.signup-card {
|
.signup-card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* form-layout, w-full, mt-2, text-center styles were removed as utility classes from Valerie UI are used directly or are available. */
|
/* form-layout, w-full, mt-2, text-center styles were removed as utility classes from Valerie UI are used directly or are available. */
|
||||||
|
|
||||||
.link-styled {
|
.link-styled {
|
||||||
@ -155,15 +148,20 @@ const onSubmit = async () => {
|
|||||||
border-bottom: 2px solid transparent;
|
border-bottom: 2px solid transparent;
|
||||||
transition: border-color var(--transition-speed) var(--transition-ease-out);
|
transition: border-color var(--transition-speed) var(--transition-ease-out);
|
||||||
}
|
}
|
||||||
.link-styled:hover, .link-styled:focus {
|
|
||||||
|
.link-styled:hover,
|
||||||
|
.link-styled:focus {
|
||||||
border-bottom-color: var(--primary);
|
border-bottom-color: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-error-text {
|
.form-error-text {
|
||||||
color: var(--danger);
|
color: var(--danger);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
}
|
}
|
||||||
.alert.form-error-text { /* For general error message */
|
|
||||||
|
.alert.form-error-text {
|
||||||
|
/* For general error message */
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
@ -172,9 +170,11 @@ const onSubmit = async () => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-with-icon-append .form-input {
|
.input-with-icon-append .form-input {
|
||||||
padding-right: 3rem;
|
padding-right: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-append-btn {
|
.icon-append-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
@ -191,9 +191,14 @@ const onSubmit = async () => {
|
|||||||
color: var(--dark);
|
color: var(--dark);
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
.icon-append-btn:hover, .icon-append-btn:focus {
|
|
||||||
|
.icon-append-btn:hover,
|
||||||
|
.icon-append-btn:focus {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
background-color: rgba(0,0,0,0.03);
|
background-color: rgba(0, 0, 0, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-append-btn .icon {
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
.icon-append-btn .icon { margin: 0; }
|
|
||||||
</style>
|
</style>
|
Loading…
Reference in New Issue
Block a user