Admin panel updated with shadcn-vue components
This commit is contained in:
@@ -1,19 +1,58 @@
|
||||
<script setup>
|
||||
import AdminLayout from "@/Layouts/AdminLayout.vue";
|
||||
import CreateDialog from "@/Components/Dialogs/CreateDialog.vue";
|
||||
import UpdateDialog from "@/Components/Dialogs/UpdateDialog.vue";
|
||||
import { Head, Link, useForm } from "@inertiajs/vue3";
|
||||
import { ref, computed } from "vue";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import {
|
||||
faPlus,
|
||||
faFlask,
|
||||
faBolt,
|
||||
faArrowsRotate,
|
||||
faToggleOn,
|
||||
faToggleOff,
|
||||
faPaperPlane,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
PlusIcon,
|
||||
FlaskConicalIcon,
|
||||
MailIcon,
|
||||
PencilIcon,
|
||||
SendIcon,
|
||||
MoreVerticalIcon,
|
||||
} from "lucide-vue-next";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/Components/ui/card";
|
||||
import { Button } from "@/Components/ui/button";
|
||||
import { Input } from "@/Components/ui/input";
|
||||
import { Label } from "@/Components/ui/label";
|
||||
import { Badge } from "@/Components/ui/badge";
|
||||
import { Switch } from "@/Components/ui/switch";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/Components/ui/table";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/Components/ui/dropdown-menu";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/Components/ui/dialog";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/Components/ui/select";
|
||||
|
||||
const props = defineProps({
|
||||
profiles: { type: Array, default: () => [] },
|
||||
@@ -93,13 +132,15 @@ function submitEdit() {
|
||||
if (form.password && form.password.trim() !== "") {
|
||||
payload.password = form.password.trim();
|
||||
}
|
||||
form.transform(() => payload).put(route("admin.mail-profiles.update", editTarget.value.id), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
editOpen.value = false;
|
||||
editTarget.value = null;
|
||||
},
|
||||
});
|
||||
form
|
||||
.transform(() => payload)
|
||||
.put(route("admin.mail-profiles.update", editTarget.value.id), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
editOpen.value = false;
|
||||
editTarget.value = null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function toggleActive(p) {
|
||||
@@ -131,251 +172,266 @@ const statusClass = (p) => {
|
||||
<template>
|
||||
<AdminLayout title="Mail profili">
|
||||
<Head title="Mail profili" />
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-xl font-semibold text-gray-800 flex items-center gap-3">
|
||||
Mail profili
|
||||
<span class="text-xs font-medium text-gray-400">({{ profiles.length }})</span>
|
||||
</h1>
|
||||
<button
|
||||
@click="openCreate"
|
||||
class="inline-flex items-center gap-2 px-4 py-2 rounded-md bg-indigo-600 text-white text-sm font-medium hover:bg-indigo-500 shadow"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faPlus" class="w-4 h-4" /> Nov profil
|
||||
</button>
|
||||
</div>
|
||||
<div class="rounded-lg border bg-white overflow-hidden shadow-sm">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 text-gray-600 text-xs uppercase tracking-wider">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-left">Ime</th>
|
||||
<th class="px-3 py-2 text-left">Host</th>
|
||||
<th class="px-3 py-2">Port</th>
|
||||
<th class="px-3 py-2">Enc</th>
|
||||
<th class="px-3 py-2">Aktivno</th>
|
||||
<th class="px-3 py-2">Test</th>
|
||||
<th class="px-3 py-2">Zadnji uspeh</th>
|
||||
<th class="px-3 py-2">Napaka</th>
|
||||
<th class="px-3 py-2">Akcije</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="p in profiles"
|
||||
:key="p.id"
|
||||
class="border-t last:border-b hover:bg-gray-50"
|
||||
>
|
||||
<td class="px-3 py-2 font-medium text-gray-800">{{ p.name }}</td>
|
||||
<td class="px-3 py-2">{{ p.host }}</td>
|
||||
<td class="px-3 py-2 text-center">{{ p.port }}</td>
|
||||
<td class="px-3 py-2 text-center">{{ p.encryption || "—" }}</td>
|
||||
<td class="px-3 py-2 text-center">
|
||||
<button
|
||||
@click="toggleActive(p)"
|
||||
class="text-indigo-600 hover:text-indigo-800"
|
||||
:title="p.active ? 'Onemogoči' : 'Omogoči'"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="p.active ? faToggleOn : faToggleOff"
|
||||
class="w-5 h-5"
|
||||
/>
|
||||
</button>
|
||||
</td>
|
||||
<td class="px-3 py-2 text-center">
|
||||
<span :class="['font-medium', statusClass(p)]">{{
|
||||
p.test_status || "—"
|
||||
}}</span>
|
||||
</td>
|
||||
<td class="px-3 py-2 text-xs text-gray-500">
|
||||
{{ p.last_success_at ? new Date(p.last_success_at).toLocaleString() : "—" }}
|
||||
</td>
|
||||
<td
|
||||
class="px-3 py-2 text-xs text-rose-600 max-w-[160px] truncate"
|
||||
:title="p.last_error_message"
|
||||
>
|
||||
{{ p.last_error_message || "—" }}
|
||||
</td>
|
||||
<td class="px-3 py-2 flex items-center gap-2">
|
||||
<button
|
||||
@click="testConnection(p)"
|
||||
class="inline-flex items-center gap-1 text-xs px-2 py-1 rounded border text-amber-600 border-amber-300 bg-amber-50 hover:bg-amber-100"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faFlask" class="w-3.5 h-3.5" /> Test
|
||||
</button>
|
||||
<button
|
||||
@click="sendTestEmail(p)"
|
||||
class="inline-flex items-center gap-1 text-xs px-2 py-1 rounded border text-emerald-700 border-emerald-300 bg-emerald-50 hover:bg-emerald-100"
|
||||
title="Pošlji testni email"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faPaperPlane" class="w-3.5 h-3.5" /> Pošlji test
|
||||
</button>
|
||||
<button
|
||||
@click="openEdit(p)"
|
||||
class="inline-flex items-center gap-1 text-xs px-2 py-1 rounded border text-indigo-600 border-indigo-300 bg-indigo-50 hover:bg-indigo-100"
|
||||
title="Uredi profil"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faArrowsRotate" class="w-3.5 h-3.5" /> Uredi
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<CreateDialog
|
||||
:show="createOpen"
|
||||
title="Nov Mail profil"
|
||||
max-width="2xl"
|
||||
confirm-text="Shrani"
|
||||
:processing="form.processing"
|
||||
@close="closeCreate"
|
||||
@confirm="submitCreate"
|
||||
>
|
||||
<form @submit.prevent="submitCreate" id="create-mail-profile" class="space-y-5">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex items-start gap-3">
|
||||
<div
|
||||
class="inline-flex items-center justify-center h-10 w-10 rounded-lg bg-primary/10 text-primary"
|
||||
>
|
||||
<MailIcon class="h-5 w-5" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle>Mail profili</CardTitle>
|
||||
<CardDescription
|
||||
>Upravljajte SMTP profile za pošiljanje e-pošte ({{
|
||||
profiles.length
|
||||
}})</CardDescription
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<Button @click="openCreate">
|
||||
<PlusIcon class="h-4 w-4 mr-2" />
|
||||
Nov profil
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Ime</TableHead>
|
||||
<TableHead>Host</TableHead>
|
||||
<TableHead class="text-center">Port</TableHead>
|
||||
<TableHead class="text-center">Enc</TableHead>
|
||||
<TableHead class="text-center">Aktivno</TableHead>
|
||||
<TableHead class="text-center">Status</TableHead>
|
||||
<TableHead>Zadnji uspeh</TableHead>
|
||||
<TableHead>Napaka</TableHead>
|
||||
<TableHead class="text-right">Akcije</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="p in profiles" :key="p.id">
|
||||
<TableCell class="font-medium">{{ p.name }}</TableCell>
|
||||
<TableCell>{{ p.host }}</TableCell>
|
||||
<TableCell class="text-center">{{ p.port }}</TableCell>
|
||||
<TableCell class="text-center">
|
||||
<Badge v-if="p.encryption" variant="outline">{{
|
||||
p.encryption.toUpperCase()
|
||||
}}</Badge>
|
||||
<span v-else class="text-muted-foreground">—</span>
|
||||
</TableCell>
|
||||
<TableCell class="text-center">
|
||||
<Switch
|
||||
:default-value="p.active"
|
||||
@update:model-value="() => toggleActive(p)"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell class="text-center">
|
||||
<Badge
|
||||
v-if="p.test_status === 'success'"
|
||||
variant="default"
|
||||
class="bg-green-100 text-green-800 hover:bg-green-100"
|
||||
>{{ p.test_status }}</Badge
|
||||
>
|
||||
<Badge v-else-if="p.test_status === 'failed'" variant="destructive">{{
|
||||
p.test_status
|
||||
}}</Badge>
|
||||
<Badge
|
||||
v-else-if="p.test_status === 'queued'"
|
||||
variant="secondary"
|
||||
class="bg-amber-100 text-amber-800 hover:bg-amber-100"
|
||||
>{{ p.test_status }}</Badge
|
||||
>
|
||||
<span v-else class="text-muted-foreground">—</span>
|
||||
</TableCell>
|
||||
<TableCell class="text-sm text-muted-foreground">
|
||||
{{
|
||||
p.last_success_at ? new Date(p.last_success_at).toLocaleString() : "—"
|
||||
}}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class="text-sm text-destructive max-w-[200px] truncate"
|
||||
:title="p.last_error_message"
|
||||
>
|
||||
{{ p.last_error_message || "—" }}
|
||||
</TableCell>
|
||||
<TableCell class="text-right">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="ghost" size="sm">
|
||||
<MoreVerticalIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Akcije</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem @click="testConnection(p)">
|
||||
<FlaskConicalIcon class="h-4 w-4 mr-2" />
|
||||
Test povezavo
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="sendTestEmail(p)">
|
||||
<SendIcon class="h-4 w-4 mr-2" />
|
||||
Pošlji testni email
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem @click="openEdit(p)">
|
||||
<PencilIcon class="h-4 w-4 mr-2" />
|
||||
Uredi profil
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Dialog :open="createOpen" @update:open="(val) => (createOpen = val)">
|
||||
<DialogContent class="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Nov Mail profil</DialogTitle>
|
||||
<DialogDescription
|
||||
>Ustvarite nov SMTP profil za pošiljanje e-pošte</DialogDescription
|
||||
>
|
||||
</DialogHeader>
|
||||
<form @submit.prevent="submitCreate" class="space-y-4">
|
||||
<div class="grid gap-4 grid-cols-2">
|
||||
<div class="col-span-1">
|
||||
<label class="label">Ime</label>
|
||||
<input v-model="form.name" type="text" class="input" />
|
||||
<div class="space-y-2">
|
||||
<Label for="create-name">Ime</Label>
|
||||
<Input id="create-name" v-model="form.name" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Host</label>
|
||||
<input v-model="form.host" type="text" class="input" />
|
||||
<div class="space-y-2">
|
||||
<Label for="create-host">Host</Label>
|
||||
<Input id="create-host" v-model="form.host" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Port</label>
|
||||
<input v-model="form.port" type="number" class="input" />
|
||||
<div class="space-y-2">
|
||||
<Label for="create-port">Port</Label>
|
||||
<Input id="create-port" v-model.number="form.port" type="number" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Encryption</label>
|
||||
<select v-model="form.encryption" class="input">
|
||||
<option value="">(None)</option>
|
||||
<option value="tls">TLS</option>
|
||||
<option value="ssl">SSL</option>
|
||||
</select>
|
||||
<div class="space-y-2">
|
||||
<Label for="create-encryption">Encryption</Label>
|
||||
<Select v-model="form.encryption">
|
||||
<SelectTrigger id="create-encryption">
|
||||
<SelectValue placeholder="Izberi..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem :value="null">None</SelectItem>
|
||||
<SelectItem value="tls">TLS</SelectItem>
|
||||
<SelectItem value="ssl">SSL</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Username</label>
|
||||
<input v-model="form.username" type="text" class="input" />
|
||||
<div class="space-y-2">
|
||||
<Label for="create-username">Username</Label>
|
||||
<Input id="create-username" v-model="form.username" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Password</label>
|
||||
<input
|
||||
<div class="space-y-2">
|
||||
<Label for="create-password">Password</Label>
|
||||
<Input
|
||||
id="create-password"
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
class="input"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">From naslov</label>
|
||||
<input v-model="form.from_address" type="email" class="input" />
|
||||
<div class="space-y-2">
|
||||
<Label for="create-from-address">From naslov</Label>
|
||||
<Input id="create-from-address" v-model="form.from_address" type="email" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">From ime</label>
|
||||
<input v-model="form.from_name" type="text" class="input" />
|
||||
<div class="space-y-2">
|
||||
<Label for="create-from-name">From ime</Label>
|
||||
<Input id="create-from-name" v-model="form.from_name" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Prioriteta</label>
|
||||
<input v-model="form.priority" type="number" class="input" />
|
||||
<div class="space-y-2">
|
||||
<Label for="create-priority">Prioriteta</Label>
|
||||
<Input id="create-priority" v-model.number="form.priority" type="number" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</CreateDialog>
|
||||
<!-- Edit Modal -->
|
||||
<UpdateDialog
|
||||
:show="editOpen"
|
||||
title="Uredi Mail profil"
|
||||
max-width="2xl"
|
||||
confirm-text="Shrani"
|
||||
:processing="form.processing"
|
||||
@close="closeEdit"
|
||||
@confirm="submitEdit"
|
||||
>
|
||||
<form @submit.prevent="submitEdit" id="edit-mail-profile" class="space-y-5">
|
||||
</form>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="closeCreate" :disabled="form.processing"
|
||||
>Prekliči</Button
|
||||
>
|
||||
<Button @click="submitCreate" :disabled="form.processing">Shrani</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Dialog :open="editOpen" @update:open="(val) => (editOpen = val)">
|
||||
<DialogContent class="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Uredi Mail profil</DialogTitle>
|
||||
<DialogDescription>Posodobite nastavitve SMTP profila</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form @submit.prevent="submitEdit" class="space-y-4">
|
||||
<div class="grid gap-4 grid-cols-2">
|
||||
<div>
|
||||
<label class="label">Ime</label>
|
||||
<input v-model="form.name" type="text" class="input" />
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-name">Ime</Label>
|
||||
<Input id="edit-name" v-model="form.name" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Host</label>
|
||||
<input v-model="form.host" type="text" class="input" />
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-host">Host</Label>
|
||||
<Input id="edit-host" v-model="form.host" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Port</label>
|
||||
<input v-model="form.port" type="number" class="input" />
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-port">Port</Label>
|
||||
<Input id="edit-port" v-model.number="form.port" type="number" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Encryption</label>
|
||||
<select v-model="form.encryption" class="input">
|
||||
<option value="">(None)</option>
|
||||
<option value="tls">TLS</option>
|
||||
<option value="ssl">SSL</option>
|
||||
</select>
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-encryption">Encryption</Label>
|
||||
<Select v-model="form.encryption">
|
||||
<SelectTrigger id="edit-encryption">
|
||||
<SelectValue placeholder="Izberi..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem :value="null">None</SelectItem>
|
||||
<SelectItem value="tls">TLS</SelectItem>
|
||||
<SelectItem value="ssl">SSL</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Username</label>
|
||||
<input v-model="form.username" type="text" class="input" />
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-username">Username</Label>
|
||||
<Input id="edit-username" v-model="form.username" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Password (nova, če spreminjaš)</label>
|
||||
<input v-model="form.password" type="password" class="input" autocomplete="new-password" />
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-password">Password (nova, če spreminjaš)</Label>
|
||||
<Input
|
||||
id="edit-password"
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">From naslov</label>
|
||||
<input v-model="form.from_address" type="email" class="input" />
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-from-address">From naslov</Label>
|
||||
<Input id="edit-from-address" v-model="form.from_address" type="email" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">From ime</label>
|
||||
<input v-model="form.from_name" type="text" class="input" />
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-from-name">From ime</Label>
|
||||
<Input id="edit-from-name" v-model="form.from_name" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Prioriteta</label>
|
||||
<input v-model="form.priority" type="number" class="input" />
|
||||
<div class="space-y-2">
|
||||
<Label for="edit-priority">Prioriteta</Label>
|
||||
<Input id="edit-priority" v-model.number="form.priority" type="number" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500">Pusti geslo prazno, če želiš obdržati obstoječe.</p>
|
||||
</form>
|
||||
</UpdateDialog>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Pusti geslo prazno, če želiš obdržati obstoječe.
|
||||
</p>
|
||||
</form>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="closeEdit" :disabled="form.processing"
|
||||
>Prekliči</Button
|
||||
>
|
||||
<Button @click="submitEdit" :disabled="form.processing">Shrani</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Utility replacements for @apply not processed in SFC scope build pipeline */
|
||||
.input {
|
||||
width: 100%;
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid var(--tw-color-gray-300, #d1d5db);
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
.input:focus {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
--tw-ring-color: #6366f1;
|
||||
border-color: #6366f1;
|
||||
box-shadow: 0 0 0 1px #6366f1;
|
||||
}
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
color: #6b7280;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
.animate-fade-in {
|
||||
animation: fade-in 0.25s ease;
|
||||
}
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
Reference in New Issue
Block a user