Enhance CreateStoreDialog component with improved layout and functionality

- Update form structure to allow for better scrolling and overflow handling.
- Introduce collapsible section for creating new shipping zones, improving user experience.
- Refactor error display and footer layout for clearer interaction during store creation.
- Add scroll indicator for better navigation within the form.
- Implement logic to check if content is scrollable, enhancing usability.

These changes streamline the store creation process, providing a more intuitive and responsive interface for users.
This commit is contained in:
padreug 2025-09-08 23:31:44 +02:00
parent 68f1e527eb
commit 91c3dec064

View file

@ -1,13 +1,14 @@
<template> <template>
<Dialog :open="isOpen" @update:open="(open) => !open && $emit('close')"> <Dialog :open="isOpen" @update:open="(open) => !open && $emit('close')">
<DialogContent class="sm:max-w-2xl"> <DialogContent class="sm:max-w-2xl max-h-[90vh] overflow-hidden flex flex-col p-0">
<DialogHeader> <DialogHeader class="flex-shrink-0 p-6 pb-0">
<DialogTitle>Create New Store</DialogTitle> <DialogTitle>Create New Store</DialogTitle>
</DialogHeader> </DialogHeader>
<form @submit="onSubmit" class="space-y-6 py-4" autocomplete="off"> <form @submit="onSubmit" class="flex flex-col flex-1 overflow-hidden" autocomplete="off">
<div class="flex-1 overflow-y-auto space-y-4 p-6 pt-4">
<!-- Basic Store Info --> <!-- Basic Store Info -->
<div class="space-y-4"> <div class="space-y-3">
<FormField v-slot="{ componentField }" name="name"> <FormField v-slot="{ componentField }" name="name">
<FormItem> <FormItem>
<FormLabel>Store Name *</FormLabel> <FormLabel>Store Name *</FormLabel>
@ -20,7 +21,7 @@
spellcheck="false" spellcheck="false"
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription class="text-xs sm:text-sm">
Choose a unique name for your store Choose a unique name for your store
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
@ -48,7 +49,7 @@
<FormLabel>Currency *</FormLabel> <FormLabel>Currency *</FormLabel>
<Select :disabled="isCreating" v-bind="componentField"> <Select :disabled="isCreating" v-bind="componentField">
<FormControl> <FormControl>
<SelectTrigger> <SelectTrigger class="w-full">
<SelectValue placeholder="Select currency" /> <SelectValue placeholder="Select currency" />
</SelectTrigger> </SelectTrigger>
</FormControl> </FormControl>
@ -66,17 +67,19 @@
<!-- Shipping Zones Section --> <!-- Shipping Zones Section -->
<FormField name="selectedZones"> <FormField name="selectedZones">
<FormItem> <FormItem>
<div class="mb-4"> <div class="mb-3">
<FormLabel class="text-base">Shipping Zones *</FormLabel> <FormLabel class="text-base">Shipping Zones *</FormLabel>
<FormDescription> <FormDescription class="text-xs sm:text-sm">
Select existing zones or create new ones for your store Select existing zones or create new ones for your store
</FormDescription> </FormDescription>
</div> </div>
<!-- Existing Zones --> <!-- Existing Zones -->
<div v-if="availableZones.length > 0" class="space-y-3"> <div v-if="availableZones.length > 0" class="space-y-2">
<FormLabel class="text-sm font-medium">Available Zones:</FormLabel> <FormLabel class="text-sm font-medium">Available Zones:</FormLabel>
<div class="space-y-2 max-h-32 overflow-y-auto"> <div class="space-y-2 max-h-24 overflow-y-auto border rounded-md p-2 relative">
<!-- Scroll indicator -->
<div v-if="availableZones.length > 3" class="absolute bottom-0 left-0 right-0 h-4 bg-gradient-to-t from-background to-transparent pointer-events-none" />
<FormField <FormField
v-for="zone in availableZones" v-for="zone in availableZones"
:key="zone.id" :key="zone.id"
@ -94,9 +97,9 @@
:disabled="isCreating" :disabled="isCreating"
/> />
</FormControl> </FormControl>
<FormLabel class="text-sm cursor-pointer font-normal"> <FormLabel class="text-xs sm:text-sm cursor-pointer font-normal">
{{ zone.name }} - {{ zone.cost }} {{ zone.currency }} <span class="block">{{ zone.name }} - {{ zone.cost }} {{ zone.currency }}</span>
<span class="text-muted-foreground ml-1">({{ zone.countries.slice(0, 2).join(', ') }}{{ zone.countries.length > 2 ? '...' : '' }})</span> <span class="text-muted-foreground text-xs">({{ zone.countries.slice(0, 2).join(', ') }}{{ zone.countries.length > 2 ? '...' : '' }})</span>
</FormLabel> </FormLabel>
</FormItem> </FormItem>
</FormField> </FormField>
@ -106,10 +109,19 @@
</FormItem> </FormItem>
</FormField> </FormField>
<!-- Create New Zone --> <!-- Create New Zone (Collapsible) -->
<div class="border-t pt-4"> <div class="border-t pt-3 mb-2">
<Label class="text-sm font-medium">Create New Zone:</Label> <button
<div class="space-y-3 mt-2"> type="button"
@click="showNewZoneForm = !showNewZoneForm"
class="flex items-center justify-between w-full text-sm font-medium py-2 hover:text-primary transition-colors"
>
<span>Create New Zone</span>
<ChevronDown
:class="['w-4 h-4 transition-transform', showNewZoneForm ? 'rotate-180' : '']"
/>
</button>
<div v-show="showNewZoneForm" class="space-y-3 mt-2">
<FormField v-slot="{ componentField }" name="newZone.name"> <FormField v-slot="{ componentField }" name="newZone.name">
<FormItem> <FormItem>
<FormLabel>Zone Name</FormLabel> <FormLabel>Zone Name</FormLabel>
@ -156,7 +168,7 @@
</Select> </Select>
</FormControl> </FormControl>
<div v-if="values.newZone?.selectedCountries && values.newZone.selectedCountries.length > 0" class="mt-2"> <div v-if="values.newZone?.selectedCountries && values.newZone.selectedCountries.length > 0" class="mt-2">
<div class="flex flex-wrap gap-1"> <div class="flex flex-wrap gap-1 max-h-20 overflow-y-auto">
<Badge <Badge
v-for="country in (values.newZone?.selectedCountries || [])" v-for="country in (values.newZone?.selectedCountries || [])"
:key="country" :key="country"
@ -183,35 +195,43 @@
</div> </div>
</div> </div>
<!-- Error Display --> <!-- Error Display -->
<div v-if="createError" class="text-sm text-destructive"> <div v-if="createError" class="text-sm text-destructive">
{{ createError }} {{ createError }}
</div>
</div> </div>
<div class="flex justify-end space-x-2 pt-4"> <!-- Footer inside form for submit to work -->
<Button <div class="flex-shrink-0 border-t bg-background p-6 pt-4">
type="button" <div class="flex justify-end space-x-2">
@click="$emit('close')" <Button
variant="outline" type="button"
:disabled="isCreating" @click="$emit('close')"
> variant="outline"
Cancel :disabled="isCreating"
</Button> >
<Button Cancel
type="submit" </Button>
:disabled="isCreating || !isFormValid" <Button
> type="submit"
<span v-if="isCreating">Creating...</span> :disabled="isCreating || !isFormValid"
<span v-else>Create Store</span> >
</Button> <span v-if="isCreating">Creating...</span>
<span v-else>Create Store</span>
</Button>
</div>
</div> </div>
</form> </form>
<!-- Scroll indicator for main form -->
<div v-if="showScrollIndicator" class="absolute bottom-20 left-0 right-0 flex justify-center pointer-events-none animate-bounce">
<ChevronDown class="w-5 h-5 text-muted-foreground" />
</div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch, nextTick } from 'vue' import { ref, computed, watch, nextTick, onMounted } from 'vue'
import { useForm } from 'vee-validate' import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod' import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod' import * as z from 'zod'
@ -231,7 +251,7 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from '@/components/ui/form' } from '@/components/ui/form'
import { Plus } from 'lucide-vue-next' import { Plus, ChevronDown } from 'lucide-vue-next'
import type { NostrmarketAPI, Zone, CreateStallRequest } from '../services/nostrmarketAPI' import type { NostrmarketAPI, Zone, CreateStallRequest } from '../services/nostrmarketAPI'
import { auth } from '@/composables/useAuthService' import { auth } from '@/composables/useAuthService'
import { useToast } from '@/core/composables/useToast' import { useToast } from '@/core/composables/useToast'
@ -257,6 +277,8 @@ const isCreating = ref(false)
const createError = ref<string | null>(null) const createError = ref<string | null>(null)
const availableCurrencies = ref<string[]>(['sat']) const availableCurrencies = ref<string[]>(['sat'])
const availableZones = ref<Zone[]>([]) const availableZones = ref<Zone[]>([])
const showNewZoneForm = ref(false)
const showScrollIndicator = ref(false)
const countries = ref([ const countries = ref([
'Free (digital)', 'Worldwide', 'Europe', 'Australia', 'Austria', 'Belgium', 'Brazil', 'Canada', 'Free (digital)', 'Worldwide', 'Europe', 'Australia', 'Austria', 'Belgium', 'Brazil', 'Canada',
'Denmark', 'Finland', 'France', 'Germany', 'Greece', 'Hong Kong', 'Hungary', 'Denmark', 'Finland', 'France', 'Germany', 'Greece', 'Hong Kong', 'Hungary',
@ -431,6 +453,16 @@ const createStore = async (formData: any) => {
} }
} }
// Check if content is scrollable
const checkScrollable = () => {
nextTick(() => {
const content = document.querySelector('.overflow-y-auto')
if (content) {
showScrollIndicator.value = content.scrollHeight > content.clientHeight
}
})
}
// Initialize data when dialog opens // Initialize data when dialog opens
watch(() => props.isOpen, async (isOpen) => { watch(() => props.isOpen, async (isOpen) => {
if (isOpen) { if (isOpen) {
@ -466,6 +498,9 @@ watch(() => props.isOpen, async (isOpen) => {
loadAvailableCurrencies(), loadAvailableCurrencies(),
loadAvailableZones() loadAvailableZones()
]) ])
// Check if scrollable after loading
checkScrollable()
} }
}) })
</script> </script>