From c0e085ea107b47d4329d3830e4451d6ea2482bee Mon Sep 17 00:00:00 2001 From: Stavros Date: Sun, 19 Jan 2025 13:40:06 +0200 Subject: [PATCH] Initial Commit --- .gitignore | 2 + Dockerfile | 49 + cmd/root.go | 24 + docker-compose.yml | 36 + go.mod | 43 + go.sum | 105 ++ internal/api/api.go | 122 ++ internal/assets/assets.go | 8 + internal/types/types.go | 10 + main.go | 7 + site/.gitignore | 24 + site/.prettierignore | 3 + site/.prettierrc | 1 + site/bun.lockb | Bin 0 -> 100781 bytes site/eslint.config.js | 28 + site/index.html | 13 + site/package-lock.json | 2249 +++++++++++++++++++++++ site/package.json | 41 + site/postcss.config.cjs | 14 + site/public/.gitkeep | 0 site/src/App.tsx | 17 + site/src/components/layouts/layout.tsx | 12 + site/src/context/user-context.tsx | 42 + site/src/main.tsx | 44 + site/src/pages/continue-page.tsx | 53 + site/src/pages/login-page.tsx | 99 + site/src/pages/logout-page.tsx | 59 + site/src/pages/not-found-page.tsx | 18 + site/src/schemas/user-context-schema.ts | 7 + site/src/vite-env.d.ts | 1 + site/tsconfig.app.json | 26 + site/tsconfig.json | 7 + site/tsconfig.node.json | 24 + site/vite.config.ts | 7 + 34 files changed, 3195 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 cmd/root.go create mode 100644 docker-compose.yml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/api/api.go create mode 100644 internal/assets/assets.go create mode 100644 internal/types/types.go create mode 100644 main.go create mode 100644 site/.gitignore create mode 100644 site/.prettierignore create mode 100644 site/.prettierrc create mode 100755 site/bun.lockb create mode 100644 site/eslint.config.js create mode 100644 site/index.html create mode 100644 site/package-lock.json create mode 100644 site/package.json create mode 100644 site/postcss.config.cjs create mode 100644 site/public/.gitkeep create mode 100644 site/src/App.tsx create mode 100644 site/src/components/layouts/layout.tsx create mode 100644 site/src/context/user-context.tsx create mode 100644 site/src/main.tsx create mode 100644 site/src/pages/continue-page.tsx create mode 100644 site/src/pages/login-page.tsx create mode 100644 site/src/pages/logout-page.tsx create mode 100644 site/src/pages/not-found-page.tsx create mode 100644 site/src/schemas/user-context-schema.ts create mode 100644 site/src/vite-env.d.ts create mode 100644 site/tsconfig.app.json create mode 100644 site/tsconfig.json create mode 100644 site/tsconfig.node.json create mode 100644 site/vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0711cb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# dist +internal/assets/dist \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ce8f87f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,49 @@ +# Site builder +FROM oven/bun:1.1.45-alpine AS site-builder + +WORKDIR /site + +COPY ./site/package.json ./ +COPY ./site/bun.lockb ./ + +RUN bun install + +COPY ./site/public ./public +COPY ./site/src ./src +COPY ./site/eslint.config.js ./ +COPY ./site/index.html ./ +COPY ./site/tsconfig.json ./ +COPY ./site/tsconfig.app.json ./ +COPY ./site/tsconfig.node.json ./ +COPY ./site/vite.config.ts ./ +COPY ./site/postcss.config.cjs ./ + +RUN bun run build + +# Builder +FROM golang:1.23-alpine3.21 AS builder + +WORKDIR /tinyauth + +COPY go.mod ./ +COPY go.sum ./ + +RUN go mod download + +COPY ./main.go ./ +COPY ./cmd ./cmd +COPY ./internal ./internal +COPY --from=site-builder /site/dist ./internal/assets/dist + +RUN go build + +# Runner +FROM busybox:1.37-musl AS runner + +WORKDIR /tinyauth + +COPY --from=builder /tinyauth/tinyauth ./ + +EXPOSE 3000 + +CMD ["./tinyauth"] \ No newline at end of file diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..5fab0ed --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,24 @@ +package cmd + +import ( + "os" + "tinyauth/internal/api" + + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "tinyauth", + Short: "A dead simple login page for your apps.", + Long: `Tinyauth is an extremely simple traefik forward-auth login screen that makes securing your apps easy.`, + Run: func(cmd *cobra.Command, args []string) { + api.Run() + }, +} + +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..df88f97 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +services: + traefik: + container_name: traefik + image: traefik:v3.3 + command: --api.insecure=true --providers.docker + ports: + - 80:80 + - 8080:8080 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + labels: + traefik.http.middlewares.basic-auth.basicauth.users: user:$$2y$$05$$HgkRucUeFKgZ7ZsPkIJY6uAX2Nh8ZAeIlJ5Rpq.05yYBPsITTnnLu + traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth + + nginx: + container_name: nginx + image: nginx:latest + ports: + - 8000:80 + labels: + traefik.enable: true + traefik.http.routers.nginx.rule: Host(`nginx.dev.local`) + traefik.http.services.nginx.loadbalancer.server.port: 80 + traefik.http.routers.nginx.middlewares: tinyauth + + tinyauth: + container_name: tinyauth + build: + context: . + dockerfile: Dockerfile + ports: + - 3000:3000 + labels: + traefik.enable: true + traefik.http.routers.tinyauth.rule: Host(`tinyauth.dev.local`) + traefik.http.services.tinyauth.loadbalancer.server.port: 3000 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4d14baa --- /dev/null +++ b/go.mod @@ -0,0 +1,43 @@ +module tinyauth + +go 1.23.2 + +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/spf13/cobra v1.8.1 +) + +require ( + github.com/bytedance/sonic v1.12.7 // indirect + github.com/bytedance/sonic/loader v0.2.3 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sessions v1.0.2 // indirect + github.com/gin-contrib/sse v1.0.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.24.0 // indirect + github.com/goccy/go-json v0.10.4 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/gorilla/context v1.1.2 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/gorilla/sessions v1.2.2 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.13.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/protobuf v1.36.3 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..56548b1 --- /dev/null +++ b/go.sum @@ -0,0 +1,105 @@ +github.com/bytedance/sonic v1.12.7 h1:CQU8pxOy9HToxhndH0Kx/S1qU/CuS9GnKYrGioDcU1Q= +github.com/bytedance/sonic v1.12.7/go.mod h1:tnbal4mxOMju17EGfknm2XyYcpyCnIROYOEYuemj13I= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0= +github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA= +github.com/gin-contrib/sessions v1.0.2/go.mod h1:KxKxWqWP5LJVDCInulOl4WbLzK2KSPlLesfZ66wRvMs= +github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= +github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg= +github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= +github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= +github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= +github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA= +golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/internal/api/api.go b/internal/api/api.go new file mode 100644 index 0000000..995e2a0 --- /dev/null +++ b/internal/api/api.go @@ -0,0 +1,122 @@ +package api + +import ( + "fmt" + "io/fs" + "net/http" + "os" + "strings" + "tinyauth/internal/assets" + "tinyauth/internal/types" + + "github.com/gin-contrib/sessions" + "github.com/gin-contrib/sessions/cookie" + "github.com/gin-gonic/gin" + "github.com/google/go-querystring/query" +) + +func Run() { + router := gin.Default() + dist, _ := fs.Sub(assets.Assets, "dist") + fileServer := http.FileServer(http.FS(dist)) + store := cookie.NewStore([]byte("secret")) + store.Options(sessions.Options{ + Domain: ".dev.local", + Path: "/", + }) + router.Use(sessions.Sessions("tinyauth", store)) + + router.Use(func(c *gin.Context) { + if !strings.HasPrefix(c.Request.URL.Path, "/api") { + _, err := fs.Stat(dist, strings.TrimPrefix(c.Request.URL.Path, "/")) + if os.IsNotExist(err) { + c.Request.URL.Path = "/" + } + fileServer.ServeHTTP(c.Writer, c.Request) + c.Abort() + } + }) + + router.GET("/api/auth", func (c *gin.Context) { + session := sessions.Default(c) + value := session.Get("tinyauth") + + if value == nil || value != "true" { + uri := c.Request.Header.Get("X-Forwarded-Uri") + proto := c.Request.Header.Get("X-Forwarded-Proto") + host := c.Request.Header.Get("X-Forwarded-Host") + queries := types.LoginQuery{ + RedirectURI: fmt.Sprintf("%s://%s%s", proto, host, uri), + } + values, _ := query.Values(queries) + c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("http://tinyauth.dev.local?%s", values.Encode())) + } + + c.JSON(200, gin.H{ + "status": 200, + "message": "Authorized", + }) + }) + + router.POST("/api/login", func (c *gin.Context) { + var login types.LoginRequest + + err := c.BindJSON(&login) + + if err != nil { + c.JSON(400, gin.H{ + "status": 400, + "message": "Bad Request", + }) + return + } + + if login.Email != "user@example.com" || login.Password != "password" { + c.JSON(401, gin.H{ + "status": 401, + "message": "Unauthorized", + }) + return + } + + session := sessions.Default(c) + session.Set("tinyauth", "true") + session.Save() + + c.JSON(200, gin.H{ + "status": 200, + "message": "Logged in", + }) + }) + + router.POST("/api/logout", func (c *gin.Context) { + session := sessions.Default(c) + session.Delete("tinyauth") + session.Save() + + c.JSON(200, gin.H{ + "status": 200, + "message": "Logged out", + }) + }) + + router.GET("/api/status", func (c *gin.Context) { + session := sessions.Default(c) + value := session.Get("tinyauth") + + if value == nil || value != "true" { + c.JSON(200, gin.H{ + "status": 200, + "isLoggedIn": false, + }) + return + } + + c.JSON(200, gin.H{ + "status": 200, + "isLoggedIn": true, + }) + }) + + router.Run(":3000") +} \ No newline at end of file diff --git a/internal/assets/assets.go b/internal/assets/assets.go new file mode 100644 index 0000000..6dbfd2a --- /dev/null +++ b/internal/assets/assets.go @@ -0,0 +1,8 @@ +package assets + +import ( + "embed" +) + +//go:embed dist +var Assets embed.FS \ No newline at end of file diff --git a/internal/types/types.go b/internal/types/types.go new file mode 100644 index 0000000..542e45f --- /dev/null +++ b/internal/types/types.go @@ -0,0 +1,10 @@ +package types + +type LoginQuery struct { + RedirectURI string `url:"redirect_uri"` +} + +type LoginRequest struct { + Email string `json:"email"` + Password string `json:"password"` +} \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..5fc16c9 --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "tinyauth/cmd" + +func main() { + cmd.Execute() +} diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/site/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/site/.prettierignore b/site/.prettierignore new file mode 100644 index 0000000..ca8cdd0 --- /dev/null +++ b/site/.prettierignore @@ -0,0 +1,3 @@ +# Ignore artifacts: +dist +node_modules \ No newline at end of file diff --git a/site/.prettierrc b/site/.prettierrc new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/site/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/site/bun.lockb b/site/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..12d6e31911e9fe8a0d5f7b37d8a74336138f5b8f GIT binary patch literal 100781 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 t={cX2i=~Xs9W= + + + + + + Tinyauth + + +

+ + + diff --git a/site/package-lock.json b/site/package-lock.json new file mode 100644 index 0000000..b348680 --- /dev/null +++ b/site/package-lock.json @@ -0,0 +1,2249 @@ +{ + "name": "site", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "site", + "version": "0.0.0", + "dependencies": { + "@mantine/core": "^7.16.0", + "@mantine/hooks": "^7.16.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@eslint/js": "^9.17.0", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^9.17.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.16", + "globals": "^15.14.0", + "postcss": "^8.5.1", + "postcss-preset-mantine": "^1.17.0", + "postcss-simple-vars": "^7.0.1", + "prettier": "3.4.2", + "typescript": "~5.6.2", + "typescript-eslint": "^8.18.2", + "vite": "^6.0.5" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.5", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.10.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.18.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.5", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.5", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.10.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.28", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@mantine/core": { + "version": "7.16.0", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.28", + "clsx": "^2.1.1", + "react-number-format": "^5.4.3", + "react-remove-scroll": "^2.6.2", + "react-textarea-autosize": "8.5.6", + "type-fest": "^4.27.0" + }, + "peerDependencies": { + "@mantine/hooks": "7.16.0", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/hooks": { + "version": "7.16.0", + "license": "MIT", + "peerDependencies": { + "react": "^18.x || ^19.x" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.30.1", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.30.1", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@swc/core": { + "version": "1.10.7", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.17" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.10.7", + "@swc/core-darwin-x64": "1.10.7", + "@swc/core-linux-arm-gnueabihf": "1.10.7", + "@swc/core-linux-arm64-gnu": "1.10.7", + "@swc/core-linux-arm64-musl": "1.10.7", + "@swc/core-linux-x64-gnu": "1.10.7", + "@swc/core-linux-x64-musl": "1.10.7", + "@swc/core-win32-arm64-msvc": "1.10.7", + "@swc/core-win32-ia32-msvc": "1.10.7", + "@swc/core-win32-x64-msvc": "1.10.7" + }, + "peerDependencies": { + "@swc/helpers": "*" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.10.7", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.10.7", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.17", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.18", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.5", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.20.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/type-utils": "8.20.0", + "@typescript-eslint/utils": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.20.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.20.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.20.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.20.0", + "@typescript-eslint/utils": "8.20.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.20.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.20.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.20.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.20.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.20.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "3.7.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@swc/core": "^1.7.26" + }, + "peerDependencies": { + "vite": "^4 || ^5 || ^6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.24.2", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.18.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.10.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.18.0", + "@eslint/plugin-kit": "^0.2.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.18", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.18.0", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "dev": true, + "license": "ISC" + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.14.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "1.21.7", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.1", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-mixins": { + "version": "9.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.11", + "postcss-js": "^4.0.0", + "postcss-simple-vars": "^7.0.0", + "sugarss": "^4.0.1" + }, + "engines": { + "node": ">=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-preset-mantine": { + "version": "1.17.0", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-mixins": "^9.0.4", + "postcss-nested": "^6.0.1" + }, + "peerDependencies": { + "postcss": ">=8.0.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-simple-vars": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.1" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-number-format": { + "version": "5.4.3", + "license": "MIT", + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.6.2", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-textarea-autosize": { + "version": "8.5.6", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.30.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.30.1", + "@rollup/rollup-android-arm64": "4.30.1", + "@rollup/rollup-darwin-arm64": "4.30.1", + "@rollup/rollup-darwin-x64": "4.30.1", + "@rollup/rollup-freebsd-arm64": "4.30.1", + "@rollup/rollup-freebsd-x64": "4.30.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.30.1", + "@rollup/rollup-linux-arm-musleabihf": "4.30.1", + "@rollup/rollup-linux-arm64-gnu": "4.30.1", + "@rollup/rollup-linux-arm64-musl": "4.30.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.30.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.30.1", + "@rollup/rollup-linux-riscv64-gnu": "4.30.1", + "@rollup/rollup-linux-s390x-gnu": "4.30.1", + "@rollup/rollup-linux-x64-gnu": "4.30.1", + "@rollup/rollup-linux-x64-musl": "4.30.1", + "@rollup/rollup-win32-arm64-msvc": "4.30.1", + "@rollup/rollup-win32-ia32-msvc": "4.30.1", + "@rollup/rollup-win32-x64-msvc": "4.30.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sugarss": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tabbable": { + "version": "6.2.0", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "4.32.0", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.20.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.20.0", + "@typescript-eslint/parser": "8.20.0", + "@typescript-eslint/utils": "8.20.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-composed-ref": { + "version": "1.4.0", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.0", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.24.2", + "postcss": "^8.4.49", + "rollup": "^4.23.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yaml": { + "version": "2.7.0", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/site/package.json b/site/package.json new file mode 100644 index 0000000..8bbe81e --- /dev/null +++ b/site/package.json @@ -0,0 +1,41 @@ +{ + "name": "site", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@mantine/core": "^7.16.0", + "@mantine/form": "^7.16.0", + "@mantine/hooks": "^7.16.0", + "@mantine/notifications": "^7.16.0", + "@tanstack/react-query": "4", + "axios": "^1.7.9", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router": "^7.1.3", + "zod": "^3.24.1" + }, + "devDependencies": { + "@eslint/js": "^9.17.0", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^9.17.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.16", + "globals": "^15.14.0", + "postcss": "^8.5.1", + "postcss-preset-mantine": "^1.17.0", + "postcss-simple-vars": "^7.0.1", + "prettier": "3.4.2", + "typescript": "~5.6.2", + "typescript-eslint": "^8.18.2", + "vite": "^6.0.5" + } +} diff --git a/site/postcss.config.cjs b/site/postcss.config.cjs new file mode 100644 index 0000000..e817f56 --- /dev/null +++ b/site/postcss.config.cjs @@ -0,0 +1,14 @@ +module.exports = { + plugins: { + "postcss-preset-mantine": {}, + "postcss-simple-vars": { + variables: { + "mantine-breakpoint-xs": "36em", + "mantine-breakpoint-sm": "48em", + "mantine-breakpoint-md": "62em", + "mantine-breakpoint-lg": "75em", + "mantine-breakpoint-xl": "88em", + }, + }, + }, +}; diff --git a/site/public/.gitkeep b/site/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/site/src/App.tsx b/site/src/App.tsx new file mode 100644 index 0000000..ba7956b --- /dev/null +++ b/site/src/App.tsx @@ -0,0 +1,17 @@ +import { Navigate } from "react-router"; +import { useUserContext } from "./context/user-context"; +import { LogoutPage } from "./pages/logout-page"; + +export const App = () => { + const queryString = window.location.search; + const params = new URLSearchParams(queryString); + const redirectUri = params.get("redirect_uri"); + + const { isLoggedIn } = useUserContext(); + + if (!isLoggedIn) { + return ; + } + + return ; +}; diff --git a/site/src/components/layouts/layout.tsx b/site/src/components/layouts/layout.tsx new file mode 100644 index 0000000..070a523 --- /dev/null +++ b/site/src/components/layouts/layout.tsx @@ -0,0 +1,12 @@ +import { Center, Flex } from "@mantine/core"; +import { ReactNode } from "react"; + +export const Layout = ({ children }: { children: ReactNode }) => { + return ( +
+ + {children} + +
+ ); +}; diff --git a/site/src/context/user-context.tsx b/site/src/context/user-context.tsx new file mode 100644 index 0000000..eabeca6 --- /dev/null +++ b/site/src/context/user-context.tsx @@ -0,0 +1,42 @@ +import { useQuery } from "@tanstack/react-query"; +import React, { createContext, useContext } from "react"; +import { UserContextSchemaType } from "../schemas/user-context-schema"; +import axios from "axios"; + +const UserContext = createContext(null); + +export const UserContextProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { + data: userContext, + isLoading, + error, + } = useQuery({ + queryKey: ["isLoggedIn"], + queryFn: async () => { + const res = await axios.get("/api/status"); + return res.data; + }, + }); + + if (error && !isLoading) { + throw error; + } + + return ( + {children} + ); +}; + +export const useUserContext = () => { + const context = useContext(UserContext); + + if (context === null) { + throw new Error("useUserContext must be used within a UserContextProvider"); + } + + return context; +}; diff --git a/site/src/main.tsx b/site/src/main.tsx new file mode 100644 index 0000000..c0964a3 --- /dev/null +++ b/site/src/main.tsx @@ -0,0 +1,44 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { App } from "./App.tsx"; +import { MantineProvider } from "@mantine/core"; +import { Notifications } from "@mantine/notifications"; +import "@mantine/core/styles.css"; +import "@mantine/notifications/styles.css"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { BrowserRouter, Route } from "react-router"; +import { Routes } from "react-router"; +import { UserContextProvider } from "./context/user-context.tsx"; +import { LoginPage } from "./pages/login-page.tsx"; +import { LogoutPage } from "./pages/logout-page.tsx"; +import { ContinuePage } from "./pages/continue-page.tsx"; +import { NotFoundPage } from "./pages/not-found-page.tsx"; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + suspense: true, + }, + }, +}); + +createRoot(document.getElementById("root")!).render( + + + + + + + + } /> + } /> + } /> + } /> + } /> + + + + + + , +); diff --git a/site/src/pages/continue-page.tsx b/site/src/pages/continue-page.tsx new file mode 100644 index 0000000..56e41ba --- /dev/null +++ b/site/src/pages/continue-page.tsx @@ -0,0 +1,53 @@ +import { Button, Paper, Text } from "@mantine/core"; +import { notifications } from "@mantine/notifications"; +import { Navigate } from "react-router"; +import { useUserContext } from "../context/user-context"; +import { Layout } from "../components/layouts/layout"; + +export const ContinuePage = () => { + const queryString = window.location.search; + const params = new URLSearchParams(queryString); + const redirectUri = params.get("redirect_uri"); + + const { isLoggedIn } = useUserContext(); + + if (!isLoggedIn) { + return ; + } + + const redirect = () => { + notifications.show({ + title: "Redirecting", + message: "You should be redirected to the app soon", + color: "blue", + }); + setTimeout(() => { + window.location.replace(redirectUri!); + }, 500); + }; + + return ( + + + {redirectUri ? ( + <> + + Continue + + Click the button to continue to your app. + + + ) : ( + <> + + Logged in + + You are now signed in and can use your apps. + + )} + + + ); +}; diff --git a/site/src/pages/login-page.tsx b/site/src/pages/login-page.tsx new file mode 100644 index 0000000..17e6e17 --- /dev/null +++ b/site/src/pages/login-page.tsx @@ -0,0 +1,99 @@ +import { Button, Paper, PasswordInput, TextInput, Title } from "@mantine/core"; +import { useForm, zodResolver } from "@mantine/form"; +import { notifications } from "@mantine/notifications"; +import { useMutation } from "@tanstack/react-query"; +import axios from "axios"; +import { z } from "zod"; +import { useUserContext } from "../context/user-context"; +import { Navigate } from "react-router"; +import { Layout } from "../components/layouts/layout"; + +export const LoginPage = () => { + const queryString = window.location.search; + const params = new URLSearchParams(queryString); + const redirectUri = params.get("redirect_uri"); + + const { isLoggedIn } = useUserContext(); + + if (isLoggedIn) { + return ; + } + + const schema = z.object({ + email: z.string().email({ message: "Invalid email" }), + password: z.string(), + }); + + type FormValues = z.infer; + + const form = useForm({ + mode: "uncontrolled", + initialValues: { + email: "", + password: "", + }, + validate: zodResolver(schema), + }); + + const loginMutation = useMutation({ + mutationFn: (login: FormValues) => { + return axios.post("/api/login", login); + }, + onError: () => { + notifications.show({ + title: "Failed to login", + message: "Check your email and password", + color: "red", + }); + }, + onSuccess: () => { + notifications.show({ + title: "Logged in", + message: "Welcome back!", + color: "green", + }); + setTimeout(() => { + window.location.replace(`/continue?redirect_uri=${redirectUri}`); + }); + }, + }); + + const handleSubmit = (values: FormValues) => { + loginMutation.mutate(values); + }; + + return ( + + Welcome back! + +
+ + + + +
+
+ ); +}; diff --git a/site/src/pages/logout-page.tsx b/site/src/pages/logout-page.tsx new file mode 100644 index 0000000..b0b8336 --- /dev/null +++ b/site/src/pages/logout-page.tsx @@ -0,0 +1,59 @@ +import { Button, Paper, Text } from "@mantine/core"; +import { notifications } from "@mantine/notifications"; +import { useMutation } from "@tanstack/react-query"; +import axios from "axios"; +import { useUserContext } from "../context/user-context"; +import { Navigate } from "react-router"; +import { Layout } from "../components/layouts/layout"; + +export const LogoutPage = () => { + const { isLoggedIn } = useUserContext(); + + if (!isLoggedIn) { + return ; + } + + const logoutMutation = useMutation({ + mutationFn: () => { + return axios.post("/api/logout"); + }, + onError: () => { + notifications.show({ + title: "Failed to logout", + message: "Please try again", + color: "red", + }); + }, + onSuccess: () => { + notifications.show({ + title: "Logged out", + message: "Goodbye!", + color: "green", + }); + setTimeout(() => { + window.location.reload(); + }, 500); + }, + }); + + return ( + + + + Logout + + + You are already logged in, click the button below to log out. + + + + + ); +}; diff --git a/site/src/pages/not-found-page.tsx b/site/src/pages/not-found-page.tsx new file mode 100644 index 0000000..30f2587 --- /dev/null +++ b/site/src/pages/not-found-page.tsx @@ -0,0 +1,18 @@ +import { Button, Paper, Text } from "@mantine/core"; +import { Layout } from "../components/layouts/layout"; + +export const NotFoundPage = () => { + return ( + + + + Not found + + The page you are looking for does not exist. + + + + ); +}; diff --git a/site/src/schemas/user-context-schema.ts b/site/src/schemas/user-context-schema.ts new file mode 100644 index 0000000..a60a7da --- /dev/null +++ b/site/src/schemas/user-context-schema.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; + +export const userContextSchema = z.object({ + isLoggedIn: z.boolean(), +}); + +export type UserContextSchemaType = z.infer; diff --git a/site/src/vite-env.d.ts b/site/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/site/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/site/tsconfig.app.json b/site/tsconfig.app.json new file mode 100644 index 0000000..358ca9b --- /dev/null +++ b/site/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/site/tsconfig.json b/site/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/site/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/site/tsconfig.node.json b/site/tsconfig.node.json new file mode 100644 index 0000000..db0becc --- /dev/null +++ b/site/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/site/vite.config.ts b/site/vite.config.ts new file mode 100644 index 0000000..1f378e7 --- /dev/null +++ b/site/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react-swc"; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +});