Initial commit

This commit is contained in:
Arthur Belleville 2025-01-18 22:40:32 +01:00
commit 684b057b27
No known key found for this signature in database
33 changed files with 1821 additions and 0 deletions

26
.gitignore vendored Normal file
View 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
View file

@ -0,0 +1,3 @@
{
"recommendations": ["svelte.svelte-vscode"]
}

52
backend/.air.toml Normal file
View 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
View 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
View 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
View 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=

View 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"
]
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,8 @@
package frontend
import (
"embed"
)
//go:embed all:dist
var DistFS embed.FS

View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

42
ui/src/App.svelte Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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",
},
},
});