Initial commit
This commit is contained in:
commit
684b057b27
33 changed files with 1821 additions and 0 deletions
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# air
|
||||
tmp
|
||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"recommendations": ["svelte.svelte-vscode"]
|
||||
}
|
||||
52
backend/.air.toml
Normal file
52
backend/.air.toml
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
root = "."
|
||||
testdata_dir = "testdata"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "./tmp/main"
|
||||
cmd = "go build -o tmp/main"
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata", "ui"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
post_cmd = []
|
||||
pre_cmd = []
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
stop_on_error = false
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
silent = false
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = true
|
||||
|
||||
[proxy]
|
||||
app_port = 0
|
||||
enabled = false
|
||||
proxy_port = 0
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
||||
30
backend/Dockerfile
Normal file
30
backend/Dockerfile
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FROM golang:alpine
|
||||
|
||||
# Set necessary environmet variables needed for our image
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0 \
|
||||
GOOS=linux \
|
||||
GOARCH=amd64
|
||||
|
||||
# Move to working directory /build
|
||||
WORKDIR /build
|
||||
|
||||
# Copy the code from /app to the build folder into the container
|
||||
COPY . .
|
||||
|
||||
# Configure the build (go.mod and go.sum are already copied with prior step)
|
||||
RUN go mod download
|
||||
|
||||
# Build the application
|
||||
RUN go build -o main .
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy binary from build to main folder
|
||||
RUN cp /build/main .
|
||||
|
||||
# Export necessary port
|
||||
EXPOSE 8080
|
||||
|
||||
# Command to run when starting the container
|
||||
CMD ["/app/main"]
|
||||
15
backend/go.mod
Normal file
15
backend/go.mod
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
module xtablo-backend
|
||||
|
||||
go 1.23.4
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.2.0
|
||||
github.com/olivere/vite v0.0.0-20241125063354-5c2fc1f1ddc2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/rs/zerolog v1.33.0
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
)
|
||||
25
backend/go.sum
Normal file
25
backend/go.sum
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
||||
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
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/olivere/vite v0.0.0-20241125063354-5c2fc1f1ddc2 h1:yrFRHF77HTyASeJG/11+Zflj7Z5OVT+oIkeUc/EIwpI=
|
||||
github.com/olivere/vite v0.0.0-20241125063354-5c2fc1f1ddc2/go.mod h1:GcOsJRAsACfTzrwnVKPHHQb2IpqJo2o7OEGht882nuA=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
11
backend/internal/frontend/dist/.vite/manifest.json
vendored
Normal file
11
backend/internal/frontend/dist/.vite/manifest.json
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"src/main.ts": {
|
||||
"file": "assets/main-B7j1Bbjq.js",
|
||||
"name": "main",
|
||||
"src": "src/main.ts",
|
||||
"isEntry": true,
|
||||
"css": [
|
||||
"assets/main-D3T09nt8.css"
|
||||
]
|
||||
}
|
||||
}
|
||||
5
backend/internal/frontend/dist/assets/main-B7j1Bbjq.js
vendored
Normal file
5
backend/internal/frontend/dist/assets/main-B7j1Bbjq.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
backend/internal/frontend/dist/assets/main-D3T09nt8.css
vendored
Normal file
1
backend/internal/frontend/dist/assets/main-D3T09nt8.css
vendored
Normal file
File diff suppressed because one or more lines are too long
8
backend/internal/frontend/file.go
Normal file
8
backend/internal/frontend/file.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package frontend
|
||||
|
||||
import (
|
||||
"embed"
|
||||
)
|
||||
|
||||
//go:embed all:dist
|
||||
var DistFS embed.FS
|
||||
129
backend/internal/spahandler/handler.go
Normal file
129
backend/internal/spahandler/handler.go
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
package spahandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/olivere/vite"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"xtablo-backend/internal/frontend"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
distFS fs.FS
|
||||
}
|
||||
|
||||
func NewHandler() *handler {
|
||||
distFS, err := fs.Sub(frontend.DistFS, "dist")
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("creating sub-filesystem for 'dist' directory: %w", err))
|
||||
}
|
||||
return &handler{
|
||||
distFS: distFS,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) GetAssets() http.HandlerFunc {
|
||||
return http.FileServerFS(h.distFS).ServeHTTP
|
||||
}
|
||||
|
||||
func (h *handler) GetSpa() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Err(err).Msg(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
|
||||
viteFragment, err := vite.HTMLFragment(vite.Config{
|
||||
FS: h.distFS,
|
||||
IsDev: false,
|
||||
})
|
||||
if err != nil {
|
||||
log.Err(err).Msg(err.Error())
|
||||
http.Error(w, "Error instantiating vite fragment", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl, err := template.New("index").Parse(indexTmpl)
|
||||
if err != nil {
|
||||
http.Error(w, "Error parsing template", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err = tmpl.Execute(w, map[string]interface{}{
|
||||
"Vite": viteFragment,
|
||||
}); err != nil {
|
||||
http.Error(w, "Error executing template", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Serve the public files generated by Vite. By default, these files are
|
||||
// referenced in the DOM with a root-relative URL format (e.g. '/file.ext').
|
||||
http.ServeFileFS(w, r, h.distFS, filepath.Base(r.URL.Path))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) GetDevSpa() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Err(err).Msg(err.Error())
|
||||
}
|
||||
}()
|
||||
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
|
||||
viteFragment, err := vite.HTMLFragment(vite.Config{
|
||||
FS: os.DirFS("ui"),
|
||||
IsDev: true,
|
||||
ViteURL: "http://localhost:5173",
|
||||
ViteTemplate: vite.SvelteTs,
|
||||
ViteEntry: "/src/main.ts",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "Error instantiating vite fragment", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl, err := template.New("index").Parse(indexTmpl)
|
||||
if err != nil {
|
||||
http.Error(w, "Error parsing template", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err = tmpl.Execute(w, map[string]interface{}{
|
||||
"Vite": viteFragment,
|
||||
}); err != nil {
|
||||
http.Error(w, "Error executing template", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Serve files in the public directory. By default, these files are
|
||||
// referenced in the DOM with a root-relative URL format (e.g. '/file.ext').
|
||||
http.ServeFileFS(w, r, os.DirFS("./public"), filepath.Base(r.URL.Path))
|
||||
}
|
||||
}
|
||||
|
||||
var indexTmpl = `<!doctype html>
|
||||
<html lang="en" class="h-full scroll-smooth">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
{{ .Vite.Tags }}
|
||||
</head>
|
||||
<body class="w-100">
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
57
backend/main.go
Normal file
57
backend/main.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
chi "github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// Here we are implementing the NotImplemented handler. Whenever an API endpoint is hit
|
||||
// we will simply return the message "Not Implemented"
|
||||
var NotImplemented = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("Not Implemented"))
|
||||
})
|
||||
|
||||
func main() {
|
||||
// Prepare logger
|
||||
zerolog.TimeFieldFormat = time.DateTime
|
||||
|
||||
var (
|
||||
isDev = flag.Bool("dev", false, "run in development mode")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
mux := chi.NewRouter()
|
||||
|
||||
if *isDev {
|
||||
registerDevRoutes(mux)
|
||||
} else {
|
||||
registerProdRoutes(mux)
|
||||
}
|
||||
|
||||
// Our API is going to consist of three routes
|
||||
// /status - which we will call to make sure that our API is up and running
|
||||
// /products - which will retrieve a list of products that the user can leave feedback on
|
||||
// /products/{slug}/feedback - which will capture user feedback on products
|
||||
mux.Get("/status", NotImplemented)
|
||||
mux.Get("/products", NotImplemented)
|
||||
mux.Post("/products/{slug}/feedback", NotImplemented)
|
||||
|
||||
server := &http.Server{
|
||||
Addr: "0.0.0.0:8443",
|
||||
Handler: mux,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: time.Minute,
|
||||
}
|
||||
|
||||
log.Info().Msg("Listening on port 8443...")
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
log.Error().Msg(err.Error())
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
36
backend/router.go
Normal file
36
backend/router.go
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
chi "github.com/go-chi/chi/v5"
|
||||
|
||||
"xtablo-backend/internal/spahandler"
|
||||
)
|
||||
|
||||
func registerProdRoutes(mux *chi.Mux) {
|
||||
spaHandler := spahandler.NewHandler()
|
||||
|
||||
// Handle requests for Vite-managed assets.
|
||||
mux.Get("/assets/*", spaHandler.GetAssets())
|
||||
|
||||
// Handle index.html request
|
||||
mux.Get("/*", spaHandler.GetSpa())
|
||||
}
|
||||
|
||||
func registerDevRoutes(mux *chi.Mux) {
|
||||
spaHandler := spahandler.NewHandler()
|
||||
|
||||
// Handle index.html request
|
||||
mux.Get("/*", spaHandler.GetDevSpa())
|
||||
}
|
||||
|
||||
var indexTmpl = `<!doctype html>
|
||||
<html lang="en" class="h-full scroll-smooth">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
{{ .Vite.Tags }}
|
||||
</head>
|
||||
<body class="w-100">
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
8
justfile
Normal file
8
justfile
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
_frontend-dev:
|
||||
cd ui && pnpm run dev
|
||||
|
||||
_backend-dev:
|
||||
air -- -dev
|
||||
|
||||
dev:
|
||||
just _backend-dev & (just _frontend-dev)
|
||||
13
ui/index.html
Normal file
13
ui/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>X-Tablo</title>
|
||||
</head>
|
||||
<body class="w-100">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
28
ui/package.json
Normal file
28
ui/package.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "xtablo-ui",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tsconfig/svelte": "^5.0.4",
|
||||
"svelte": "^5.15.0",
|
||||
"svelte-check": "^4.1.1",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^6.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth0/auth0-spa-js": "^2.1.3",
|
||||
"carbon-components-svelte": "^0.87.0",
|
||||
"carbon-icons-svelte": "^12.13.0",
|
||||
"less": "^4.2.1",
|
||||
"svelte-google-login": "^0.0.3",
|
||||
"tachyons": "4.12.0"
|
||||
}
|
||||
}
|
||||
1062
ui/pnpm-lock.yaml
Normal file
1062
ui/pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
42
ui/src/App.svelte
Normal file
42
ui/src/App.svelte
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<script lang="ts">
|
||||
import Login from "./Login.svelte";
|
||||
import { Theme } from "carbon-components-svelte";
|
||||
import type { Auth0Client } from "@auth0/auth0-spa-js";
|
||||
|
||||
import { onMount } from "svelte";
|
||||
import auth from "./authService";
|
||||
import { isAuthenticated, user, user_tasks, tasks } from "./store";
|
||||
import config from "./auth_config";
|
||||
|
||||
let auth0Client: Auth0Client;
|
||||
|
||||
onMount(async () => {
|
||||
auth0Client = await auth.createClient();
|
||||
|
||||
isAuthenticated.set(await auth0Client.isAuthenticated());
|
||||
user.set(await auth0Client.getUser());
|
||||
});
|
||||
|
||||
function login() {
|
||||
auth.loginWithPopup(auth0Client, config);
|
||||
}
|
||||
|
||||
function logout() {
|
||||
auth.logout(auth0Client);
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="w-100 pa3">
|
||||
<Theme
|
||||
render="toggle"
|
||||
toggle={{
|
||||
themes: ["g10", "g100"],
|
||||
labelA: "Light Mode",
|
||||
labelB: "Dark Mode",
|
||||
size: "sm",
|
||||
}}
|
||||
persist
|
||||
persistKey="__carbon-theme"
|
||||
/>
|
||||
<Login handleLogin={login} />
|
||||
</main>
|
||||
85
ui/src/Login.svelte
Normal file
85
ui/src/Login.svelte
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Content,
|
||||
Form,
|
||||
Link,
|
||||
Modal,
|
||||
PasswordInput,
|
||||
RadioButton,
|
||||
RadioButtonGroup,
|
||||
TextInput,
|
||||
} from "carbon-components-svelte";
|
||||
import RightArrow from "carbon-icons-svelte/lib/ArrowRight.svelte";
|
||||
|
||||
let { handleLogin }: { handleLogin: () => void } = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row items-center justify-center ma4">
|
||||
<div class="login-block ma6">
|
||||
<form class="measure center">
|
||||
<fieldset id="sign_up" class="ba b--transparent ph0 mh0">
|
||||
<legend class="f2 fw5 mh0 mb1">Log In to X-Tablo</legend>
|
||||
<span
|
||||
>Don't have an account, create one for free <Link
|
||||
href="#0"
|
||||
class="f6 link dim">here</Link
|
||||
>
|
||||
</span>
|
||||
<div class="mt3">
|
||||
<label class="db fw6 lh-copy f6" for="email-address">Email</label>
|
||||
<TextInput
|
||||
class="pa2 bg-transparent ba w-100"
|
||||
type="email"
|
||||
id="email-address"
|
||||
name="email-address"
|
||||
></TextInput>
|
||||
</div>
|
||||
<div class="mv3">
|
||||
<label class="db fw6 lh-copy f6" for="password">Password</label>
|
||||
<TextInput
|
||||
class="pa2 bg-transparent ba w-100"
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
></TextInput>
|
||||
</div>
|
||||
<Checkbox labelText="Remember ID" checked />
|
||||
</fieldset>
|
||||
<div class="">
|
||||
<Button
|
||||
on:click={handleLogin}
|
||||
class="b ph3 pv2 input-reset ba db w-50 grow"
|
||||
type="submit"
|
||||
icon={RightArrow}>Log In</Button
|
||||
>
|
||||
</div>
|
||||
<div class="lh-copy mt3">
|
||||
<Link href="#0" class="f6 link dim db">Sign up</Link>
|
||||
<Link href="#0" class="f6 link dim db">Forgot your password?</Link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<script
|
||||
src="https://unpkg.com/@lottiefiles/lottie-player@2.0.8/dist/lottie-player.js"
|
||||
></script><lottie-player
|
||||
src="https://lottie.host/36551324-1e54-499f-8410-4695a457a128/rrkpkFcJVP.json"
|
||||
background="##FFFFFF"
|
||||
speed="1"
|
||||
style="width: 400px; height: 400px"
|
||||
loop
|
||||
autoplay
|
||||
direction="1"
|
||||
mode="normal"
|
||||
></lottie-player>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
.login-block {
|
||||
width: 350px;
|
||||
}
|
||||
</style>
|
||||
10
ui/src/app.css
Normal file
10
ui/src/app.css
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
:app {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
1
ui/src/assets/svelte.svg
Normal file
1
ui/src/assets/svelte.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
46
ui/src/authService.ts
Normal file
46
ui/src/authService.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import {
|
||||
createAuth0Client,
|
||||
Auth0Client,
|
||||
type Auth0ClientOptions,
|
||||
} from "@auth0/auth0-spa-js";
|
||||
import { user, isAuthenticated, popupOpen } from "./store";
|
||||
import config from "./auth_config";
|
||||
|
||||
async function createClient() {
|
||||
let auth0Client = await createAuth0Client({
|
||||
domain: config.domain,
|
||||
clientId: config.clientId,
|
||||
});
|
||||
|
||||
return auth0Client;
|
||||
}
|
||||
|
||||
async function loginWithPopup(
|
||||
client: Auth0Client,
|
||||
options: Auth0ClientOptions
|
||||
) {
|
||||
popupOpen.set(true);
|
||||
try {
|
||||
await client.loginWithPopup(options);
|
||||
|
||||
user.set(await client.getUser());
|
||||
isAuthenticated.set(true);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line
|
||||
console.error(e);
|
||||
} finally {
|
||||
popupOpen.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
function logout(client: Auth0Client) {
|
||||
return client.logout();
|
||||
}
|
||||
|
||||
const auth = {
|
||||
createClient,
|
||||
loginWithPopup,
|
||||
logout,
|
||||
};
|
||||
|
||||
export default auth;
|
||||
6
ui/src/auth_config.ts
Normal file
6
ui/src/auth_config.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
const config = {
|
||||
domain: "dev-iw7iwywee7bt0yer.eu.auth0.com",
|
||||
clientId: "hxPBFRIRqQKqXoIdEcrODEhLhZPmSwm3",
|
||||
};
|
||||
|
||||
export default config;
|
||||
10
ui/src/lib/Counter.svelte
Normal file
10
ui/src/lib/Counter.svelte
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<script lang="ts">
|
||||
let count: number = $state(0)
|
||||
const increment = () => {
|
||||
count += 1
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={increment}>
|
||||
count is {count}
|
||||
</button>
|
||||
13
ui/src/main.ts
Normal file
13
ui/src/main.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import "vite/modulepreload-polyfill";
|
||||
import "carbon-components-svelte/css/all.css";
|
||||
|
||||
import { mount } from "svelte";
|
||||
import "tachyons";
|
||||
import "./app.css";
|
||||
import App from "./App.svelte";
|
||||
|
||||
const app = mount(App, {
|
||||
target: document.getElementById("app")!,
|
||||
});
|
||||
|
||||
export default app;
|
||||
1
ui/src/public/vite.svg
Normal file
1
ui/src/public/vite.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
19
ui/src/store.ts
Normal file
19
ui/src/store.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { writable, derived, type Writable } from "svelte/store";
|
||||
|
||||
export const isAuthenticated = writable(false);
|
||||
export const user: Writable<{ email?: string | undefined } | undefined> =
|
||||
writable();
|
||||
export const popupOpen = writable(false);
|
||||
export const error = writable();
|
||||
|
||||
export const tasks: Writable<{ user: string }[]> = writable([]);
|
||||
|
||||
export const user_tasks = derived([tasks, user], ([$tasks, $user]) => {
|
||||
let logged_in_user_tasks: any[] = [];
|
||||
|
||||
if ($user && $user.email) {
|
||||
logged_in_user_tasks = $tasks.filter((task) => task.user === $user.email);
|
||||
}
|
||||
|
||||
return logged_in_user_tasks;
|
||||
});
|
||||
3
ui/src/vite-env.d.ts
vendored
Normal file
3
ui/src/vite-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-svgr/client" />
|
||||
7
ui/svelte.config.js
Normal file
7
ui/svelte.config.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
export default {
|
||||
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
}
|
||||
20
ui/tsconfig.app.json
Normal file
20
ui/tsconfig.app.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||
* Note that setting allowJs false does not prevent the use
|
||||
* of JS in `.svelte` files.
|
||||
*/
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force"
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.svelte"]
|
||||
}
|
||||
8
ui/tsconfig.json
Normal file
8
ui/tsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"files": [],
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
24
ui/tsconfig.node.json
Normal file
24
ui/tsconfig.node.json
Normal file
|
|
@ -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"]
|
||||
}
|
||||
17
ui/vite.config.ts
Normal file
17
ui/vite.config.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { defineConfig } from "vite";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [svelte()],
|
||||
build: {
|
||||
// generates .vite/manifest.json in outDir
|
||||
manifest: true,
|
||||
emptyOutDir: true,
|
||||
outDir: "../backend/internal/frontend/dist",
|
||||
rollupOptions: {
|
||||
// overwrite default .html entry
|
||||
input: "/src/main.ts",
|
||||
},
|
||||
},
|
||||
});
|
||||
Loading…
Reference in a new issue