add(infra): docker build
This commit is contained in:
parent
5adada0422
commit
84bd8355d6
10 changed files with 79 additions and 303 deletions
30
Dockerfile
Normal file
30
Dockerfile
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache git build-base sqlite-dev
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
|
||||
RUN go build -o maot-shortner ./cmd/maot-shortner
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
RUN apk add --no-cache ca-certificates sqlite-libs
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/maot-shortner .
|
||||
|
||||
COPY --from=builder /app/web/static ./web/static
|
||||
|
||||
RUN mkdir -p /app/data
|
||||
|
||||
EXPOSE 3032
|
||||
ENV DATABASE_PATH=/app/data/shortener.db
|
||||
|
||||
VOLUME ["/app/data"]
|
||||
|
||||
CMD ["./maot-shortner"]
|
||||
4
Makefile
4
Makefile
|
|
@ -22,3 +22,7 @@ tailwind-watch:
|
|||
dev:
|
||||
make tailwind-clean
|
||||
make -j3 tailwind-watch templ server
|
||||
|
||||
# Build production docker image
|
||||
build:
|
||||
docker build -f Dockerfile -t maot-shortner:latest .
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maot-shortner/internal/database"
|
||||
"maot-shortner/internal/handlers"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
|
@ -12,9 +14,14 @@ type Server struct {
|
|||
}
|
||||
|
||||
func NewServer(db *database.Database) *Server {
|
||||
appPort := os.Getenv("APP_PORT")
|
||||
if appPort == "" {
|
||||
appPort = "3032"
|
||||
}
|
||||
|
||||
return &Server{
|
||||
Server: http.Server{
|
||||
Addr: ":3032",
|
||||
Addr: fmt.Sprintf(":%s", appPort),
|
||||
Handler: createMux(db),
|
||||
},
|
||||
db: db,
|
||||
|
|
|
|||
19
docker-compose.yml
Normal file
19
docker-compose.yml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: maot-shortner:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3032:8080"
|
||||
environment:
|
||||
- DATABASE_PATH=/app/data/shortener.db
|
||||
- APP_PORT=8080
|
||||
volumes:
|
||||
- data:/app/data
|
||||
|
||||
volumes:
|
||||
data:
|
||||
|
|
@ -2,10 +2,12 @@ package database
|
|||
|
||||
import (
|
||||
"log"
|
||||
"maot-shortner/internal/domain"
|
||||
"os"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"maot-shortner/internal/domain"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
|
|
@ -15,9 +17,19 @@ type Database struct {
|
|||
func InitDatabase() (database *Database, err error) {
|
||||
log.Println("Starting database...")
|
||||
|
||||
db, err := gorm.Open(sqlite.Open("shortener.db"), &gorm.Config{})
|
||||
db.AutoMigrate(domain.ImportModels()...)
|
||||
dbPath := os.Getenv("DATABASE_PATH")
|
||||
if dbPath == "" {
|
||||
dbPath = "/app/data/shortener.db"
|
||||
}
|
||||
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = db.AutoMigrate(domain.ImportModels()...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
database = &Database{db}
|
||||
return database, err
|
||||
return database, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@
|
|||
--color-amber-800: oklch(47.3% 0.137 46.201);
|
||||
--color-amber-900: oklch(41.4% 0.112 45.904);
|
||||
--color-amber-950: oklch(27.9% 0.077 45.635);
|
||||
--color-yellow-500: oklch(79.5% 0.184 86.047);
|
||||
--color-green-50: oklch(98.2% 0.018 155.826);
|
||||
--color-green-500: oklch(72.3% 0.219 149.579);
|
||||
--color-green-950: oklch(26.6% 0.065 152.934);
|
||||
|
|
@ -45,7 +44,6 @@
|
|||
--color-sky-950: oklch(29.3% 0.066 243.157);
|
||||
--color-blue-500: oklch(62.3% 0.214 259.815);
|
||||
--color-gray-300: oklch(87.2% 0.01 258.338);
|
||||
--color-gray-500: oklch(55.1% 0.027 264.364);
|
||||
--color-zinc-50: oklch(98.5% 0 0);
|
||||
--color-zinc-200: oklch(92% 0.004 286.32);
|
||||
--color-zinc-800: oklch(27.4% 0.006 286.033);
|
||||
|
|
@ -243,12 +241,6 @@
|
|||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
.inset-0 {
|
||||
inset: calc(var(--spacing) * 0);
|
||||
}
|
||||
.top-0 {
|
||||
top: calc(var(--spacing) * 0);
|
||||
}
|
||||
.top-1\/2 {
|
||||
top: calc(1/2 * 100%);
|
||||
}
|
||||
|
|
@ -264,18 +256,9 @@
|
|||
.right-4 {
|
||||
right: calc(var(--spacing) * 4);
|
||||
}
|
||||
.bottom-0 {
|
||||
bottom: calc(var(--spacing) * 0);
|
||||
}
|
||||
.bottom-4 {
|
||||
bottom: calc(var(--spacing) * 4);
|
||||
}
|
||||
.left-0 {
|
||||
left: calc(var(--spacing) * 0);
|
||||
}
|
||||
.left-1\/2 {
|
||||
left: calc(1/2 * 100%);
|
||||
}
|
||||
.z-50 {
|
||||
z-index: 50;
|
||||
}
|
||||
|
|
@ -306,15 +289,9 @@
|
|||
.mt-0\.5 {
|
||||
margin-top: calc(var(--spacing) * 0.5);
|
||||
}
|
||||
.mt-1 {
|
||||
margin-top: calc(var(--spacing) * 1);
|
||||
}
|
||||
.mt-2 {
|
||||
margin-top: calc(var(--spacing) * 2);
|
||||
}
|
||||
.mr-3 {
|
||||
margin-right: calc(var(--spacing) * 3);
|
||||
}
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
|
@ -340,9 +317,6 @@
|
|||
width: calc(var(--spacing) * 9);
|
||||
height: calc(var(--spacing) * 9);
|
||||
}
|
||||
.h-1 {
|
||||
height: calc(var(--spacing) * 1);
|
||||
}
|
||||
.h-4 {
|
||||
height: calc(var(--spacing) * 4);
|
||||
}
|
||||
|
|
@ -400,19 +374,12 @@
|
|||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
.flex-shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.shrink {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
.shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.-translate-x-1\/2 {
|
||||
--tw-translate-x: calc(calc(1/2 * 100%) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.translate-x-0 {
|
||||
--tw-translate-x: calc(var(--spacing) * 0);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
|
|
@ -425,10 +392,6 @@
|
|||
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.-translate-y-4 {
|
||||
--tw-translate-y: calc(var(--spacing) * -4);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.translate-y-0 {
|
||||
--tw-translate-y: calc(var(--spacing) * 0);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
|
|
@ -490,9 +453,6 @@
|
|||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
.columns-2 {
|
||||
columns: 2;
|
||||
}
|
||||
|
|
@ -552,11 +512,6 @@
|
|||
margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)));
|
||||
}
|
||||
}
|
||||
.truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
@ -626,9 +581,6 @@
|
|||
.bg-background {
|
||||
background-color: var(--background);
|
||||
}
|
||||
.bg-blue-500 {
|
||||
background-color: var(--color-blue-500);
|
||||
}
|
||||
.bg-card {
|
||||
background-color: var(--card);
|
||||
}
|
||||
|
|
@ -647,9 +599,6 @@
|
|||
.bg-gray-300 {
|
||||
background-color: var(--color-gray-300);
|
||||
}
|
||||
.bg-gray-500 {
|
||||
background-color: var(--color-gray-500);
|
||||
}
|
||||
.bg-green-500 {
|
||||
background-color: var(--color-green-500);
|
||||
}
|
||||
|
|
@ -665,9 +614,6 @@
|
|||
background-color: color-mix(in oklab, var(--muted) 50%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-popover {
|
||||
background-color: var(--popover);
|
||||
}
|
||||
.bg-primary {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
|
|
@ -689,9 +635,6 @@
|
|||
.bg-white {
|
||||
background-color: var(--color-white);
|
||||
}
|
||||
.bg-yellow-500 {
|
||||
background-color: var(--color-yellow-500);
|
||||
}
|
||||
.bg-gradient-to-r {
|
||||
--tw-gradient-position: to right in oklab;
|
||||
background-image: linear-gradient(var(--tw-gradient-stops));
|
||||
|
|
@ -737,18 +680,12 @@
|
|||
.pt-2 {
|
||||
padding-top: calc(var(--spacing) * 2);
|
||||
}
|
||||
.pt-5 {
|
||||
padding-top: calc(var(--spacing) * 5);
|
||||
}
|
||||
.pr-8 {
|
||||
padding-right: calc(var(--spacing) * 8);
|
||||
}
|
||||
.pr-9 {
|
||||
padding-right: calc(var(--spacing) * 9);
|
||||
}
|
||||
.pb-4 {
|
||||
padding-bottom: calc(var(--spacing) * 4);
|
||||
}
|
||||
.font-mono {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
|
@ -800,9 +737,6 @@
|
|||
.text-amber-900 {
|
||||
color: var(--color-amber-900);
|
||||
}
|
||||
.text-blue-500 {
|
||||
color: var(--color-blue-500);
|
||||
}
|
||||
.text-card-foreground {
|
||||
color: var(--card-foreground);
|
||||
}
|
||||
|
|
@ -818,24 +752,15 @@
|
|||
.text-emerald-900 {
|
||||
color: var(--color-emerald-900);
|
||||
}
|
||||
.text-green-500 {
|
||||
color: var(--color-green-500);
|
||||
}
|
||||
.text-muted-foreground {
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
.text-popover-foreground {
|
||||
color: var(--popover-foreground);
|
||||
}
|
||||
.text-primary {
|
||||
color: var(--primary);
|
||||
}
|
||||
.text-primary-foreground {
|
||||
color: var(--primary-foreground);
|
||||
}
|
||||
.text-red-500 {
|
||||
color: var(--color-red-500);
|
||||
}
|
||||
.text-red-600 {
|
||||
color: var(--color-red-600);
|
||||
}
|
||||
|
|
@ -851,9 +776,6 @@
|
|||
.text-sky-900 {
|
||||
color: var(--color-sky-900);
|
||||
}
|
||||
.text-yellow-500 {
|
||||
color: var(--color-yellow-500);
|
||||
}
|
||||
.text-zinc-900 {
|
||||
color: var(--color-zinc-900);
|
||||
}
|
||||
|
|
@ -1250,11 +1172,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.md\:max-w-\[420px\] {
|
||||
@media (width >= 48rem) {
|
||||
max-width: 420px;
|
||||
}
|
||||
}
|
||||
.md\:text-sm {
|
||||
@media (width >= 48rem) {
|
||||
font-size: var(--text-sm);
|
||||
|
|
|
|||
1
web/static/js/htmx.min.js
vendored
1
web/static/js/htmx.min.js
vendored
File diff suppressed because one or more lines are too long
1
web/static/js/toast.min.js
vendored
1
web/static/js/toast.min.js
vendored
|
|
@ -1 +0,0 @@
|
|||
(()=>{(function(){if(typeof window.toastHandler>"u"){let a=function(t){if(!t||t.hasAttribute("data-initialized")||(t.setAttribute("data-initialized","true"),window.toasts.has(t)))return;let i=parseInt(t.getAttribute("data-tui-toast-duration")||"0"),r=t.querySelector("[data-tui-toast-progress]"),d=t.querySelector("[data-tui-toast-dismiss]"),e={timer:null,remaining:i,startTime:Date.now(),progress:r,paused:!1};window.toasts.set(t,e);function o(){clearTimeout(e.timer),t.classList.remove("toast-enter-active"),t.classList.add("toast-leave-active"),t.addEventListener("transitionend",()=>{t.remove(),window.toasts.delete(t)},{once:!0})}function u(n){n<=0||(clearTimeout(e.timer),e.startTime=Date.now(),e.remaining=n,e.paused=!1,e.timer=setTimeout(o,n),e.progress&&(e.progress.style.transition=`width ${n}ms linear`,e.progress.offsetWidth,e.progress.style.width="0%"))}function l(){if(!(e.paused||e.remaining<=0)&&(clearTimeout(e.timer),e.remaining-=Date.now()-e.startTime,e.paused=!0,e.progress)){let n=window.getComputedStyle(e.progress).width;e.progress.style.transition="none",e.progress.style.width=n}}function c(){!e.paused||e.remaining<=0||u(e.remaining)}i>0&&(t.addEventListener("mouseenter",l),t.addEventListener("mouseleave",c)),d&&d.addEventListener("click",o),setTimeout(()=>{t.classList.add("toast-enter-active"),e.progress&&(e.progress.style.width="100%"),u(i)},50)},s=function(t=document){let i=[];t instanceof Element&&t.matches("[data-tui-toast]")&&(t.hasAttribute("data-initialized")||i.push(t)),t&&typeof t.querySelectorAll=="function"&&t.querySelectorAll("[data-tui-toast]:not([data-initialized])").forEach(r=>{i.push(r)}),i.forEach(a)};var m=a,f=s;window.toastHandler=!0,window.toasts=new Map,window.templUI=window.templUI||{},window.templUI.toast={init:s},document.addEventListener("DOMContentLoaded",()=>s())}})();})();
|
||||
|
|
@ -1,210 +0,0 @@
|
|||
// templui component toast - version: v0.85.0 installed by templui v0.85.0
|
||||
package toast
|
||||
|
||||
import (
|
||||
"maot-shortner/web/templates/components/button"
|
||||
"maot-shortner/web/templates/components/icon"
|
||||
"maot-shortner/web/resources/utils"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Variant string
|
||||
type Position string
|
||||
|
||||
const (
|
||||
VariantDefault Variant = "default"
|
||||
VariantSuccess Variant = "success"
|
||||
VariantError Variant = "error"
|
||||
VariantWarning Variant = "warning"
|
||||
VariantInfo Variant = "info"
|
||||
)
|
||||
|
||||
const (
|
||||
PositionTopRight Position = "top-right"
|
||||
PositionTopLeft Position = "top-left"
|
||||
PositionTopCenter Position = "top-center"
|
||||
PositionBottomRight Position = "bottom-right"
|
||||
PositionBottomLeft Position = "bottom-left"
|
||||
PositionBottomCenter Position = "bottom-center"
|
||||
)
|
||||
|
||||
type Props struct {
|
||||
ID string
|
||||
Class string
|
||||
Attributes templ.Attributes
|
||||
Title string
|
||||
Description string
|
||||
Variant Variant
|
||||
Position Position
|
||||
Duration int
|
||||
Dismissible bool
|
||||
ShowIndicator bool
|
||||
Icon bool
|
||||
}
|
||||
|
||||
templ Toast(props ...Props) {
|
||||
@ToastCSS()
|
||||
{{ var p Props }}
|
||||
if len(props) > 0 {
|
||||
{{ p = props[0] }}
|
||||
}
|
||||
if p.ID == "" {
|
||||
{{ p.ID = utils.RandomID() }}
|
||||
}
|
||||
{{ p = p.defaults() }}
|
||||
{{ isTop := p.Position == PositionTopRight || p.Position == PositionTopLeft || p.Position == PositionTopCenter }}
|
||||
{{ isBottom := p.Position == PositionBottomRight || p.Position == PositionBottomLeft || p.Position == PositionBottomCenter }}
|
||||
<div
|
||||
id={ p.ID }
|
||||
data-tui-toast
|
||||
data-tui-toast-duration={ strconv.Itoa(p.Duration) }
|
||||
class={ utils.TwMerge(
|
||||
"z-50 fixed pointer-events-auto p-4",
|
||||
"opacity-0 transform transition-all duration-300 ease-out",
|
||||
utils.If(isTop, "top-0"),
|
||||
utils.If(isBottom, "bottom-0"),
|
||||
utils.If(isTop, "translate-y-4"),
|
||||
utils.If(isBottom, "-translate-y-4"),
|
||||
utils.If(p.Position == PositionTopRight || p.Position == PositionBottomRight, "right-0"),
|
||||
utils.If(p.Position == PositionTopLeft || p.Position == PositionBottomLeft, "left-0"),
|
||||
utils.If(p.Position == PositionTopCenter || p.Position == PositionBottomCenter, "left-1/2 -translate-x-1/2"),
|
||||
"w-full md:max-w-[420px]",
|
||||
p.Class,
|
||||
) }
|
||||
{ p.Attributes... }
|
||||
>
|
||||
<div class="w-full bg-popover text-popover-foreground rounded-lg shadow-xs border pt-5 pb-4 px-4 flex items-center justify-center relative overflow-hidden">
|
||||
if p.ShowIndicator {
|
||||
@indicator(p)
|
||||
}
|
||||
if p.Icon {
|
||||
@toastIcon(p)
|
||||
}
|
||||
<span class="flex-1 min-w-0">
|
||||
@title(p)
|
||||
@description(p)
|
||||
</span>
|
||||
if p.Dismissible {
|
||||
@dismissButton()
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ indicator(p Props) {
|
||||
<div class="absolute top-0 left-0 right-0 h-1">
|
||||
<div
|
||||
data-tui-toast-progress
|
||||
class={ utils.TwMerge(
|
||||
"absolute inset-0",
|
||||
typeClass(p.Variant),
|
||||
) }
|
||||
></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ toastIcon(p Props) {
|
||||
if p.Variant == VariantSuccess {
|
||||
@icon.CircleCheck(icon.Props{Size: 22, Class: "text-green-500 mr-3 flex-shrink-0"})
|
||||
} else if p.Variant == VariantError {
|
||||
@icon.CircleX(icon.Props{Size: 22, Class: "text-red-500 mr-3 flex-shrink-0"})
|
||||
} else if p.Variant == VariantWarning {
|
||||
@icon.TriangleAlert(icon.Props{Size: 22, Class: "text-yellow-500 mr-3 flex-shrink-0"})
|
||||
} else if p.Variant == VariantInfo {
|
||||
@icon.Info(icon.Props{Size: 22, Class: "text-blue-500 mr-3 flex-shrink-0"})
|
||||
}
|
||||
}
|
||||
|
||||
templ title(p Props) {
|
||||
if p.Title != "" {
|
||||
<p class="text-sm font-semibold truncate">{ p.Title }</p>
|
||||
}
|
||||
}
|
||||
|
||||
templ description(p Props) {
|
||||
if p.Description != "" {
|
||||
<p class="text-sm opacity-90 mt-1">{ p.Description }</p>
|
||||
}
|
||||
}
|
||||
|
||||
templ dismissButton() {
|
||||
@button.Button(button.Props{
|
||||
Size: button.SizeIcon,
|
||||
Variant: button.VariantGhost,
|
||||
Attributes: templ.Attributes{
|
||||
"aria-label": "Close",
|
||||
"data-tui-toast-dismiss": "",
|
||||
"type": "button",
|
||||
},
|
||||
}) {
|
||||
@icon.X(icon.Props{
|
||||
Size: 18,
|
||||
Class: "opacity-75 hover:opacity-100",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (p Props) defaults() Props {
|
||||
if p.Variant == "" {
|
||||
p.Variant = VariantDefault
|
||||
}
|
||||
if p.Position == "" {
|
||||
p.Position = PositionBottomRight
|
||||
}
|
||||
if p.Duration == 0 {
|
||||
p.Duration = 3000
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func typeClass(t Variant) string {
|
||||
switch t {
|
||||
case VariantDefault:
|
||||
return "bg-gray-500"
|
||||
case VariantSuccess:
|
||||
return "bg-green-500"
|
||||
case VariantError:
|
||||
return "bg-red-500"
|
||||
case VariantWarning:
|
||||
return "bg-yellow-500"
|
||||
case VariantInfo:
|
||||
return "bg-blue-500"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
var cssHandle = templ.NewOnceHandle()
|
||||
|
||||
templ ToastCSS() {
|
||||
@cssHandle.Once() {
|
||||
<style nonce={ templ.GetNonce(ctx) }>
|
||||
[data-tui-toast].toast-enter {
|
||||
opacity: 0;
|
||||
/* Initial vertical offset is handled by classes in the component */
|
||||
}
|
||||
[data-tui-toast].toast-enter-active {
|
||||
opacity: 1;
|
||||
transform: translateY(0); /* Only handle vertical transition */
|
||||
}
|
||||
[data-tui-toast].toast-leave {
|
||||
opacity: 1;
|
||||
transform: translateY(0); /* Start leave from final vertical position */
|
||||
}
|
||||
[data-tui-toast].toast-leave-active {
|
||||
opacity: 0;
|
||||
/* Apply final vertical offset based on position */
|
||||
}
|
||||
[data-tui-toast][class*=" top-"].toast-leave-active {
|
||||
transform: translateY(1rem); /* Move down */
|
||||
}
|
||||
[data-tui-toast][class*=" bottom-"].toast-leave-active {
|
||||
transform: translateY(-1rem); /* Move up */
|
||||
}
|
||||
</style>
|
||||
}
|
||||
}
|
||||
|
||||
templ Script() {
|
||||
<script defer src="static/js/toast.min.js"></script>
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ import (
|
|||
|
||||
templ Home() {
|
||||
@layouts.MainLayout() {
|
||||
// Componente Alpine
|
||||
<div x-data="shortener()" class="min-h-full flex justify-center items-center flex-col gap-6 p-6 w-full">
|
||||
<div class="animate-pulse-slow hover:scale-110 transition-transform duration-300">
|
||||
@icon.Link(icon.Props{Class: "w-24 h-24"})
|
||||
|
|
@ -328,13 +327,13 @@ templ Home() {
|
|||
const toast = { id, title, message, variant, show: false };
|
||||
this.toasts.push(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
setTimeout(() => {
|
||||
const toastIndex = this.toasts.findIndex(t => t.id === id);
|
||||
if (toastIndex !== -1) {
|
||||
this.toasts[toastIndex].show = true;
|
||||
}
|
||||
}, 10);
|
||||
|
||||
|
||||
if (timeout > 0) {
|
||||
setTimeout(() => this.hideToast(id), timeout);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue