Functional Login Page
This commit is contained in:
parent
f40f5c2d20
commit
59bedf73da
40 changed files with 8326 additions and 40 deletions
14
components.json
Normal file
14
components.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://shadcn-svelte.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.js",
|
||||||
|
"css": "src/app.pcss",
|
||||||
|
"baseColor": "slate"
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "$lib/components",
|
||||||
|
"utils": "$lib/utils"
|
||||||
|
},
|
||||||
|
"typescript": true
|
||||||
|
}
|
4864
package-lock.json
generated
Normal file
4864
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
73
package.json
73
package.json
|
@ -1,29 +1,48 @@
|
||||||
{
|
{
|
||||||
"name": "navidrome-alternate-ui",
|
"name": "navidrome-alternate-ui",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"lint": "eslint ."
|
"lint": "eslint .",
|
||||||
},
|
"ui": "npx shadcn-svelte@latest"
|
||||||
"devDependencies": {
|
},
|
||||||
"@sveltejs/adapter-auto": "^3.0.0",
|
"devDependencies": {
|
||||||
"@sveltejs/kit": "^2.0.0",
|
"@sveltejs/adapter-auto": "^3.2.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
"@sveltejs/kit": "^2.5.7",
|
||||||
"@types/eslint": "^8.56.0",
|
"@sveltejs/vite-plugin-svelte": "^3.1.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
"@types/eslint": "^8.56.0",
|
||||||
"@typescript-eslint/parser": "^7.0.0",
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
"eslint": "^8.56.0",
|
"@typescript-eslint/parser": "^7.0.0",
|
||||||
"eslint-plugin-svelte": "^2.36.0-next.4",
|
"autoprefixer": "^10.4.16",
|
||||||
"svelte": "^5.0.0-next.1",
|
"eslint": "^8.56.0",
|
||||||
"svelte-check": "^3.6.0",
|
"eslint-plugin-svelte": "^2.36.0-next.13",
|
||||||
"tslib": "^2.4.1",
|
"postcss": "^8.4.32",
|
||||||
"typescript": "^5.0.0",
|
"postcss-load-config": "^5.0.2",
|
||||||
"vite": "^5.0.3"
|
"svelte": "^5.0.0-next",
|
||||||
},
|
"svelte-check": "^3.6.0",
|
||||||
"type": "module"
|
"svelte-loading-spinners": "^0.3.6",
|
||||||
|
"tailwindcss": "^3.3.6",
|
||||||
|
"tslib": "^2.4.1",
|
||||||
|
"typescript": "^5.0.0",
|
||||||
|
"vite": "^5.0.3"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
|
"bits-ui": "^0.21.4",
|
||||||
|
"clsx": "^2.1.0",
|
||||||
|
"formsnap": "^1.0.0",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
|
"radix-icons-svelte": "^1.2.1",
|
||||||
|
"sveltekit-superforms": "^2.12.5",
|
||||||
|
"tailwind-merge": "^2.3.0",
|
||||||
|
"tailwind-variants": "^0.2.1",
|
||||||
|
"ts-md5": "^1.3.1",
|
||||||
|
"zod": "^3.22.5"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
13
postcss.config.cjs
Normal file
13
postcss.config.cjs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
const tailwindcss = require("tailwindcss");
|
||||||
|
const autoprefixer = require("autoprefixer");
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
plugins: [
|
||||||
|
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
|
||||||
|
tailwindcss(),
|
||||||
|
//But others, like autoprefixer, need to run after,
|
||||||
|
autoprefixer,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
|
@ -6,7 +6,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body data-sveltekit-preload-data="hover">
|
<body data-sveltekit-preload-data="hover" class="h-screen light dark">
|
||||||
<div style="display: contents">%sveltekit.body%</div>
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
59
src/app.pcss
Normal file
59
src/app.pcss
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 191.6 100% 95%;
|
||||||
|
--foreground: 191.6 5% 0%;
|
||||||
|
--card: 191.6 50% 90%;
|
||||||
|
--card-foreground: 191.6 5% 10%;
|
||||||
|
--popover: 191.6 100% 95%;
|
||||||
|
--popover-foreground: 191.6 100% 0%;
|
||||||
|
--primary: 191.6 91.4% 36.5%;
|
||||||
|
--primary-foreground: 0 0% 100%;
|
||||||
|
--secondary: 191.6 30% 70%;
|
||||||
|
--secondary-foreground: 0 0% 0%;
|
||||||
|
--muted: 153.6 30% 85%;
|
||||||
|
--muted-foreground: 191.6 5% 35%;
|
||||||
|
--accent: 153.6 30% 80%;
|
||||||
|
--accent-foreground: 191.6 5% 10%;
|
||||||
|
--destructive: 0 100% 30%;
|
||||||
|
--destructive-foreground: 191.6 5% 90%;
|
||||||
|
--border: 191.6 30% 50%;
|
||||||
|
--input: 191.6 30% 18%;
|
||||||
|
--ring: 191.6 91.4% 36.5%;
|
||||||
|
--radius: 0.75rem;
|
||||||
|
}
|
||||||
|
.dark {
|
||||||
|
--background: 191.6 50% 5%;
|
||||||
|
--foreground: 191.6 5% 90%;
|
||||||
|
--card: 191.6 50% 0%;
|
||||||
|
--card-foreground: 191.6 5% 90%;
|
||||||
|
--popover: 191.6 50% 5%;
|
||||||
|
--popover-foreground: 191.6 5% 90%;
|
||||||
|
--primary: 191.6 91.4% 36.5%;
|
||||||
|
--primary-foreground: 0 0% 100%;
|
||||||
|
--secondary: 191.6 30% 10%;
|
||||||
|
--secondary-foreground: 0 0% 100%;
|
||||||
|
--muted: 153.6 30% 15%;
|
||||||
|
--muted-foreground: 191.6 5% 60%;
|
||||||
|
--accent: 153.6 30% 15%;
|
||||||
|
--accent-foreground: 191.6 5% 90%;
|
||||||
|
--destructive: 0 100% 30%;
|
||||||
|
--destructive-foreground: 191.6 5% 90%;
|
||||||
|
--border: 191.6 30% 18%;
|
||||||
|
--input: 191.6 30% 18%;
|
||||||
|
--ring: 191.6 91.4% 36.5%;
|
||||||
|
--radius: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
25
src/lib/components/ui/button/button.svelte
Normal file
25
src/lib/components/ui/button/button.svelte
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Button as ButtonPrimitive } from "bits-ui";
|
||||||
|
import { type Events, type Props, buttonVariants } from "./index.js";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = Props;
|
||||||
|
type $$Events = Events;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let variant: $$Props["variant"] = "default";
|
||||||
|
export let size: $$Props["size"] = "default";
|
||||||
|
export let builders: $$Props["builders"] = [];
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ButtonPrimitive.Root
|
||||||
|
{builders}
|
||||||
|
class={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
type="button"
|
||||||
|
{...$$restProps}
|
||||||
|
on:click
|
||||||
|
on:keydown
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</ButtonPrimitive.Root>
|
49
src/lib/components/ui/button/index.ts
Normal file
49
src/lib/components/ui/button/index.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import { type VariantProps, tv } from "tailwind-variants";
|
||||||
|
import type { Button as ButtonPrimitive } from "bits-ui";
|
||||||
|
import Root from "./button.svelte";
|
||||||
|
|
||||||
|
const buttonVariants = tv({
|
||||||
|
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
|
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||||
|
outline:
|
||||||
|
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||||
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-10 px-4 py-2",
|
||||||
|
sm: "h-9 rounded-md px-3",
|
||||||
|
lg: "h-11 rounded-md px-8",
|
||||||
|
icon: "h-10 w-10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
type Variant = VariantProps<typeof buttonVariants>["variant"];
|
||||||
|
type Size = VariantProps<typeof buttonVariants>["size"];
|
||||||
|
|
||||||
|
type Props = ButtonPrimitive.Props & {
|
||||||
|
variant?: Variant;
|
||||||
|
size?: Size;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Events = ButtonPrimitive.Events;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
type Props,
|
||||||
|
type Events,
|
||||||
|
//
|
||||||
|
Root as Button,
|
||||||
|
type Props as ButtonProps,
|
||||||
|
type Events as ButtonEvents,
|
||||||
|
buttonVariants,
|
||||||
|
};
|
13
src/lib/components/ui/card/card-content.svelte
Normal file
13
src/lib/components/ui/card/card-content.svelte
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cn("p-6 pt-0", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
13
src/lib/components/ui/card/card-description.svelte
Normal file
13
src/lib/components/ui/card/card-description.svelte
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLParagraphElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p class={cn("text-sm text-muted-foreground", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</p>
|
13
src/lib/components/ui/card/card-footer.svelte
Normal file
13
src/lib/components/ui/card/card-footer.svelte
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cn("flex items-center p-6 pt-0", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
13
src/lib/components/ui/card/card-header.svelte
Normal file
13
src/lib/components/ui/card/card-header.svelte
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cn("flex flex-col space-y-1.5 p-6", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
21
src/lib/components/ui/card/card-title.svelte
Normal file
21
src/lib/components/ui/card/card-title.svelte
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import type { HeadingLevel } from "./index.js";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLHeadingElement> & {
|
||||||
|
tag?: HeadingLevel;
|
||||||
|
};
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let tag: $$Props["tag"] = "h3";
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element
|
||||||
|
this={tag}
|
||||||
|
class={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</svelte:element>
|
16
src/lib/components/ui/card/card.svelte
Normal file
16
src/lib/components/ui/card/card.svelte
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
24
src/lib/components/ui/card/index.ts
Normal file
24
src/lib/components/ui/card/index.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import Root from "./card.svelte";
|
||||||
|
import Content from "./card-content.svelte";
|
||||||
|
import Description from "./card-description.svelte";
|
||||||
|
import Footer from "./card-footer.svelte";
|
||||||
|
import Header from "./card-header.svelte";
|
||||||
|
import Title from "./card-title.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Content,
|
||||||
|
Description,
|
||||||
|
Footer,
|
||||||
|
Header,
|
||||||
|
Title,
|
||||||
|
//
|
||||||
|
Root as Card,
|
||||||
|
Content as CardContent,
|
||||||
|
Description as CardDescription,
|
||||||
|
Footer as CardFooter,
|
||||||
|
Header as CardHeader,
|
||||||
|
Title as CardTitle,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
10
src/lib/components/ui/form/form-button.svelte
Normal file
10
src/lib/components/ui/form/form-button.svelte
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import * as Button from "$lib/components/ui/button/index.js";
|
||||||
|
|
||||||
|
type $$Props = Button.Props;
|
||||||
|
type $$Events = Button.Events;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Button.Root type="submit" on:click on:keydown {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</Button.Root>
|
17
src/lib/components/ui/form/form-description.svelte
Normal file
17
src/lib/components/ui/form/form-description.svelte
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import * as FormPrimitive from "formsnap";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||||
|
let className: string | undefined | null = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormPrimitive.Description
|
||||||
|
class={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
let:descriptionAttrs
|
||||||
|
>
|
||||||
|
<slot {descriptionAttrs} />
|
||||||
|
</FormPrimitive.Description>
|
25
src/lib/components/ui/form/form-element-field.svelte
Normal file
25
src/lib/components/ui/form/form-element-field.svelte
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<script lang="ts" context="module">
|
||||||
|
import type { FormPathLeaves, SuperForm } from "sveltekit-superforms";
|
||||||
|
type T = Record<string, unknown>;
|
||||||
|
type U = FormPathLeaves<T>;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPathLeaves<T>">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import * as FormPrimitive from "formsnap";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = FormPrimitive.ElementFieldProps<T, U> & HTMLAttributes<HTMLElement>;
|
||||||
|
|
||||||
|
export let form: SuperForm<T>;
|
||||||
|
export let name: U;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormPrimitive.ElementField {form} {name} let:constraints let:errors let:tainted let:value>
|
||||||
|
<div class={cn("space-y-2", className)}>
|
||||||
|
<slot {constraints} {errors} {tainted} {value} />
|
||||||
|
</div>
|
||||||
|
</FormPrimitive.ElementField>
|
26
src/lib/components/ui/form/form-field-errors.svelte
Normal file
26
src/lib/components/ui/form/form-field-errors.svelte
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import * as FormPrimitive from "formsnap";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = FormPrimitive.FieldErrorsProps & {
|
||||||
|
errorClasses?: string | undefined | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
export let errorClasses: $$Props["class"] = undefined;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormPrimitive.FieldErrors
|
||||||
|
class={cn("text-sm font-medium text-destructive", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
let:errors
|
||||||
|
let:fieldErrorsAttrs
|
||||||
|
let:errorAttrs
|
||||||
|
>
|
||||||
|
<slot {errors} {fieldErrorsAttrs} {errorAttrs}>
|
||||||
|
{#each errors as error}
|
||||||
|
<div {...errorAttrs} class={cn(errorClasses)}>{error}</div>
|
||||||
|
{/each}
|
||||||
|
</slot>
|
||||||
|
</FormPrimitive.FieldErrors>
|
25
src/lib/components/ui/form/form-field.svelte
Normal file
25
src/lib/components/ui/form/form-field.svelte
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<script lang="ts" context="module">
|
||||||
|
import type { FormPath, SuperForm } from "sveltekit-superforms";
|
||||||
|
type T = Record<string, unknown>;
|
||||||
|
type U = FormPath<T>;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import * as FormPrimitive from "formsnap";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = FormPrimitive.FieldProps<T, U> & HTMLAttributes<HTMLElement>;
|
||||||
|
|
||||||
|
export let form: SuperForm<T>;
|
||||||
|
export let name: U;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormPrimitive.Field {form} {name} let:constraints let:errors let:tainted let:value>
|
||||||
|
<div class={cn("space-y-2", className)}>
|
||||||
|
<slot {constraints} {errors} {tainted} {value} />
|
||||||
|
</div>
|
||||||
|
</FormPrimitive.Field>
|
30
src/lib/components/ui/form/form-fieldset.svelte
Normal file
30
src/lib/components/ui/form/form-fieldset.svelte
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<script lang="ts" context="module">
|
||||||
|
import type { FormPath, SuperForm } from "sveltekit-superforms";
|
||||||
|
type T = Record<string, unknown>;
|
||||||
|
type U = FormPath<T>;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||||
|
import * as FormPrimitive from "formsnap";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = FormPrimitive.FieldsetProps<T, U>;
|
||||||
|
|
||||||
|
export let form: SuperForm<T>;
|
||||||
|
export let name: U;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormPrimitive.Fieldset
|
||||||
|
{form}
|
||||||
|
{name}
|
||||||
|
let:constraints
|
||||||
|
let:errors
|
||||||
|
let:tainted
|
||||||
|
let:value
|
||||||
|
class={cn("space-y-2", className)}
|
||||||
|
>
|
||||||
|
<slot {constraints} {errors} {tainted} {value} />
|
||||||
|
</FormPrimitive.Fieldset>
|
17
src/lib/components/ui/form/form-label.svelte
Normal file
17
src/lib/components/ui/form/form-label.svelte
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Label as LabelPrimitive } from "bits-ui";
|
||||||
|
import { getFormControl } from "formsnap";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import { Label } from "$lib/components/ui/label/index.js";
|
||||||
|
|
||||||
|
type $$Props = LabelPrimitive.Props;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
|
||||||
|
const { labelAttrs } = getFormControl();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Label {...$labelAttrs} class={cn("data-[fs-error]:text-destructive", className)} {...$$restProps}>
|
||||||
|
<slot {labelAttrs} />
|
||||||
|
</Label>
|
17
src/lib/components/ui/form/form-legend.svelte
Normal file
17
src/lib/components/ui/form/form-legend.svelte
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import * as FormPrimitive from "formsnap";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = FormPrimitive.LegendProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormPrimitive.Legend
|
||||||
|
{...$$restProps}
|
||||||
|
class={cn("text-sm font-medium leading-none data-[fs-error]:text-destructive", className)}
|
||||||
|
let:legendAttrs
|
||||||
|
>
|
||||||
|
<slot {legendAttrs} />
|
||||||
|
</FormPrimitive.Legend>
|
33
src/lib/components/ui/form/index.ts
Normal file
33
src/lib/components/ui/form/index.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import * as FormPrimitive from "formsnap";
|
||||||
|
import Description from "./form-description.svelte";
|
||||||
|
import Label from "./form-label.svelte";
|
||||||
|
import FieldErrors from "./form-field-errors.svelte";
|
||||||
|
import Field from "./form-field.svelte";
|
||||||
|
import Fieldset from "./form-fieldset.svelte";
|
||||||
|
import Legend from "./form-legend.svelte";
|
||||||
|
import ElementField from "./form-element-field.svelte";
|
||||||
|
import Button from "./form-button.svelte";
|
||||||
|
|
||||||
|
const Control = FormPrimitive.Control;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Field,
|
||||||
|
Control,
|
||||||
|
Label,
|
||||||
|
Button,
|
||||||
|
FieldErrors,
|
||||||
|
Description,
|
||||||
|
Fieldset,
|
||||||
|
Legend,
|
||||||
|
ElementField,
|
||||||
|
//
|
||||||
|
Field as FormField,
|
||||||
|
Control as FormControl,
|
||||||
|
Description as FormDescription,
|
||||||
|
Label as FormLabel,
|
||||||
|
FieldErrors as FormFieldErrors,
|
||||||
|
Fieldset as FormFieldset,
|
||||||
|
Legend as FormLegend,
|
||||||
|
ElementField as FormElementField,
|
||||||
|
Button as FormButton,
|
||||||
|
};
|
28
src/lib/components/ui/input/index.ts
Normal file
28
src/lib/components/ui/input/index.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import Root from "./input.svelte";
|
||||||
|
|
||||||
|
export type FormInputEvent<T extends Event = Event> = T & {
|
||||||
|
currentTarget: EventTarget & HTMLInputElement;
|
||||||
|
};
|
||||||
|
export type InputEvents = {
|
||||||
|
blur: FormInputEvent<FocusEvent>;
|
||||||
|
change: FormInputEvent<Event>;
|
||||||
|
click: FormInputEvent<MouseEvent>;
|
||||||
|
focus: FormInputEvent<FocusEvent>;
|
||||||
|
focusin: FormInputEvent<FocusEvent>;
|
||||||
|
focusout: FormInputEvent<FocusEvent>;
|
||||||
|
keydown: FormInputEvent<KeyboardEvent>;
|
||||||
|
keypress: FormInputEvent<KeyboardEvent>;
|
||||||
|
keyup: FormInputEvent<KeyboardEvent>;
|
||||||
|
mouseover: FormInputEvent<MouseEvent>;
|
||||||
|
mouseenter: FormInputEvent<MouseEvent>;
|
||||||
|
mouseleave: FormInputEvent<MouseEvent>;
|
||||||
|
paste: FormInputEvent<ClipboardEvent>;
|
||||||
|
input: FormInputEvent<InputEvent>;
|
||||||
|
wheel: FormInputEvent<WheelEvent>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as Input,
|
||||||
|
};
|
41
src/lib/components/ui/input/input.svelte
Normal file
41
src/lib/components/ui/input/input.svelte
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLInputAttributes } from "svelte/elements";
|
||||||
|
import type { InputEvents } from "./index.js";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLInputAttributes;
|
||||||
|
type $$Events = InputEvents;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let value: $$Props["value"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
|
||||||
|
// Workaround for https://github.com/sveltejs/svelte/issues/9305
|
||||||
|
// Fixed in Svelte 5, but not backported to 4.x.
|
||||||
|
export let readonly: $$Props["readonly"] = undefined;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input
|
||||||
|
class={cn(
|
||||||
|
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
bind:value
|
||||||
|
{readonly}
|
||||||
|
on:blur
|
||||||
|
on:change
|
||||||
|
on:click
|
||||||
|
on:focus
|
||||||
|
on:focusin
|
||||||
|
on:focusout
|
||||||
|
on:keydown
|
||||||
|
on:keypress
|
||||||
|
on:keyup
|
||||||
|
on:mouseover
|
||||||
|
on:mouseenter
|
||||||
|
on:mouseleave
|
||||||
|
on:paste
|
||||||
|
on:input
|
||||||
|
on:wheel
|
||||||
|
{...$$restProps}
|
||||||
|
/>
|
7
src/lib/components/ui/label/index.ts
Normal file
7
src/lib/components/ui/label/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import Root from "./label.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as Label,
|
||||||
|
};
|
21
src/lib/components/ui/label/label.svelte
Normal file
21
src/lib/components/ui/label/label.svelte
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Label as LabelPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = LabelPrimitive.Props;
|
||||||
|
type $$Events = LabelPrimitive.Events;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
class={cn(
|
||||||
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
on:mousedown
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</LabelPrimitive.Root>
|
52
src/lib/opensubsonic.ts
Normal file
52
src/lib/opensubsonic.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import {Md5} from "ts-md5";
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
|
|
||||||
|
export module OpenSubsonic {
|
||||||
|
export let username = "";
|
||||||
|
export let password = "";
|
||||||
|
let token = "";
|
||||||
|
let salt = "";
|
||||||
|
export let base = "https://navidrome.neshweb.net";
|
||||||
|
const apiVer = "1.16.1"; // Version supported by Navidrome. Variable for easier updating
|
||||||
|
const clientName = "Lytter";
|
||||||
|
|
||||||
|
export function getApiPath(path: string) {
|
||||||
|
if (username === "") {
|
||||||
|
let cookie = Cookies.get("subsonicUsername");
|
||||||
|
if (typeof cookie !== "undefined") {
|
||||||
|
username = cookie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cookie = Cookies.get("subsonicToken");
|
||||||
|
if (typeof cookie !== "undefined") {
|
||||||
|
token = cookie;
|
||||||
|
|
||||||
|
cookie = Cookies.get("subsonicSalt");
|
||||||
|
if (typeof cookie !== "undefined") {
|
||||||
|
salt = cookie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
generateToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${base}/rest/${path}?u=${username}&s=${salt}&t=${token}&v=${apiVer}&c=${clientName}&f=json`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateToken() {
|
||||||
|
salt = "staticfornow";
|
||||||
|
token = Md5.hashStr(password + salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function storeCredentials() {
|
||||||
|
let options = {
|
||||||
|
expires: 7,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
};
|
||||||
|
Cookies.set("subsonicToken", token, options);
|
||||||
|
Cookies.set("subsonicSalt", salt, options);
|
||||||
|
Cookies.set("subsonicUsername", username, options);
|
||||||
|
}
|
||||||
|
}
|
62
src/lib/utils.ts
Normal file
62
src/lib/utils.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { type ClassValue, clsx } from "clsx";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
import { cubicOut } from "svelte/easing";
|
||||||
|
import type { TransitionConfig } from "svelte/transition";
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlyAndScaleParams = {
|
||||||
|
y?: number;
|
||||||
|
x?: number;
|
||||||
|
start?: number;
|
||||||
|
duration?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const flyAndScale = (
|
||||||
|
node: Element,
|
||||||
|
params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 }
|
||||||
|
): TransitionConfig => {
|
||||||
|
const style = getComputedStyle(node);
|
||||||
|
const transform = style.transform === "none" ? "" : style.transform;
|
||||||
|
|
||||||
|
const scaleConversion = (
|
||||||
|
valueA: number,
|
||||||
|
scaleA: [number, number],
|
||||||
|
scaleB: [number, number]
|
||||||
|
) => {
|
||||||
|
const [minA, maxA] = scaleA;
|
||||||
|
const [minB, maxB] = scaleB;
|
||||||
|
|
||||||
|
const percentage = (valueA - minA) / (maxA - minA);
|
||||||
|
const valueB = percentage * (maxB - minB) + minB;
|
||||||
|
|
||||||
|
return valueB;
|
||||||
|
};
|
||||||
|
|
||||||
|
const styleToString = (
|
||||||
|
style: Record<string, number | string | undefined>
|
||||||
|
): string => {
|
||||||
|
return Object.keys(style).reduce((str, key) => {
|
||||||
|
if (style[key] === undefined) return str;
|
||||||
|
return str + `${key}:${style[key]};`;
|
||||||
|
}, "");
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
duration: params.duration ?? 200,
|
||||||
|
delay: 0,
|
||||||
|
css: (t) => {
|
||||||
|
const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]);
|
||||||
|
const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]);
|
||||||
|
const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]);
|
||||||
|
|
||||||
|
return styleToString({
|
||||||
|
transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`,
|
||||||
|
opacity: t
|
||||||
|
});
|
||||||
|
},
|
||||||
|
easing: cubicOut
|
||||||
|
};
|
||||||
|
};
|
13
src/routes/+layout.server.ts
Normal file
13
src/routes/+layout.server.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import {redirect} from "@sveltejs/kit";
|
||||||
|
|
||||||
|
export function load({ cookies, url }) {
|
||||||
|
let auth = cookies.get("subsonicToken");
|
||||||
|
if (typeof auth === "undefined" && url.pathname !== "/login") {
|
||||||
|
cookies.set("preLoginRoute", `${url.pathname}`, {
|
||||||
|
path: "/login",
|
||||||
|
secure: false,
|
||||||
|
httpOnly: false,
|
||||||
|
});
|
||||||
|
throw redirect(302, '/login');
|
||||||
|
}
|
||||||
|
}
|
5
src/routes/+layout.svelte
Normal file
5
src/routes/+layout.svelte
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<script>
|
||||||
|
import "../app.pcss";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<slot></slot>
|
26
src/routes/login/+page.server.ts
Normal file
26
src/routes/login/+page.server.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import type { PageServerLoad, Actions } from "./$types.js";
|
||||||
|
import { fail } from "@sveltejs/kit";
|
||||||
|
import { superValidate } from "sveltekit-superforms";
|
||||||
|
import { formSchema } from "./schema";
|
||||||
|
import { zod } from "sveltekit-superforms/adapters";
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async () => {
|
||||||
|
return {
|
||||||
|
form: await superValidate(zod(formSchema)),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions: Actions = {
|
||||||
|
default: async (event) => {
|
||||||
|
const form = await superValidate(event, zod(formSchema));
|
||||||
|
if (!form.valid) {
|
||||||
|
return fail(400, {
|
||||||
|
form,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
form,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
21
src/routes/login/+page.svelte
Normal file
21
src/routes/login/+page.svelte
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import * as Card from "$lib/components/ui/card";
|
||||||
|
import LoginForm from "./login-form.svelte";
|
||||||
|
import type { PageData } from "./$types.js";
|
||||||
|
export let data: PageData;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="h-full flex flex-col items-center justify-center">
|
||||||
|
<Card.Root class="border-2 p-2">
|
||||||
|
<Card.Header>
|
||||||
|
<Card.Title class="text-center">Login</Card.Title>
|
||||||
|
<Card.Description></Card.Description>
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Content>
|
||||||
|
<LoginForm data={data.form}></LoginForm>
|
||||||
|
</Card.Content>
|
||||||
|
<Card.Footer>
|
||||||
|
<p class="text-xs text-secondary pt-4">Sign Up (not implemented)</p>
|
||||||
|
</Card.Footer>
|
||||||
|
</Card.Root>
|
||||||
|
</div>
|
85
src/routes/login/login-form.svelte
Normal file
85
src/routes/login/login-form.svelte
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<svelte:options runes={true} />
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Input } from "$lib/components/ui/input";
|
||||||
|
import * as Form from "$lib/components/ui/form";
|
||||||
|
import { formSchema, type FormSchema } from "./schema";
|
||||||
|
import {OpenSubsonic} from "$lib/opensubsonic";
|
||||||
|
import {
|
||||||
|
type SuperValidated,
|
||||||
|
type Infer,
|
||||||
|
superForm,
|
||||||
|
} from "sveltekit-superforms";
|
||||||
|
import { zodClient } from "sveltekit-superforms/adapters";
|
||||||
|
import {Circle2} from "svelte-loading-spinners";
|
||||||
|
import {Md5} from "ts-md5";
|
||||||
|
import Cookies from 'js-cookie'
|
||||||
|
import {goto} from "$app/navigation";
|
||||||
|
|
||||||
|
let { data }: SuperValidated<Infer<FormSchema>> = $props<{ data: SuperValidated<Infer<FormSchema>> }>();
|
||||||
|
|
||||||
|
const form = superForm(data, {
|
||||||
|
validators: zodClient(formSchema),
|
||||||
|
onUpdated() {
|
||||||
|
OpenSubsonic.username = previousForm.get("username");
|
||||||
|
OpenSubsonic.password = previousForm.get("password");
|
||||||
|
let path = OpenSubsonic.getApiPath("ping");
|
||||||
|
fetch(path).then(async (data) => {
|
||||||
|
let res = (await data.json())["subsonic-response"];
|
||||||
|
if (res.status === "ok") {
|
||||||
|
OpenSubsonic.storeCredentials();
|
||||||
|
loading = false;
|
||||||
|
let route = Cookies.get("preLoginRoute")
|
||||||
|
Cookies.remove("preLoginRoute", { path: "/login" })
|
||||||
|
goto(route);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
error = true;
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSubmit({ formData }) {
|
||||||
|
loading = true;
|
||||||
|
previousForm = formData;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let previousForm: FormData = $state({});
|
||||||
|
|
||||||
|
const { form: formData, enhance } = form;
|
||||||
|
|
||||||
|
let error = $state(false);
|
||||||
|
let loading = $state(false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form method="POST" use:enhance>
|
||||||
|
<Form.Field {form} name="username" class="px-3">
|
||||||
|
<Form.Control let:attrs>
|
||||||
|
<Form.Label>Username:</Form.Label>
|
||||||
|
<Input {...attrs} bind:value={$formData.username}></Input>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.FieldErrors></Form.FieldErrors>
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field {form} name="password" class="px-3">
|
||||||
|
<Form.Control let:attrs>
|
||||||
|
<Form.Label>Password:</Form.Label>
|
||||||
|
<Input {...attrs} type="password" bind:value={$formData.password}></Input>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.FieldErrors></Form.FieldErrors>
|
||||||
|
</Form.Field>
|
||||||
|
<div class="flex flex-row justify-center pt-2">
|
||||||
|
{#if loading}
|
||||||
|
<Circle2
|
||||||
|
size="40" unit="px"
|
||||||
|
colorOuter="#0892B4" durationOuter="1s"
|
||||||
|
colorCenter="white" durationCenter="3s"
|
||||||
|
colorInner="#9cc1c9" durationInner="2s"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<Form.Button>Login</Form.Button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if error}
|
||||||
|
<p class="pt-2 text-sm text-destructive">Username or Password incorrect</p>
|
||||||
|
{/if}
|
||||||
|
</form>
|
10
src/routes/login/schema.ts
Normal file
10
src/routes/login/schema.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const formSchema = z.object({
|
||||||
|
username: z.string().min(2).max(64),
|
||||||
|
password: z.string().min(2).max(64),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FormSchema = typeof formSchema;
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import adapter from '@sveltejs/adapter-auto';
|
import adapter from "@sveltejs/adapter-auto";
|
||||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||||
// for more information about preprocessors
|
// for more information about preprocessors
|
||||||
preprocess: vitePreprocess(),
|
preprocess: [vitePreprocess({})],
|
||||||
|
|
||||||
kit: {
|
kit: {
|
||||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||||
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
||||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||||
adapter: adapter()
|
adapter: adapter(),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|
64
tailwind.config.js
Normal file
64
tailwind.config.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { fontFamily } from "tailwindcss/defaultTheme";
|
||||||
|
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
const config = {
|
||||||
|
darkMode: ["class"],
|
||||||
|
content: ["./src/**/*.{html,js,svelte,ts}"],
|
||||||
|
safelist: ["dark"],
|
||||||
|
theme: {
|
||||||
|
container: {
|
||||||
|
center: true,
|
||||||
|
padding: "2rem",
|
||||||
|
screens: {
|
||||||
|
"2xl": "1400px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
border: "hsl(var(--border) / <alpha-value>)",
|
||||||
|
input: "hsl(var(--input) / <alpha-value>)",
|
||||||
|
ring: "hsl(var(--ring) / <alpha-value>)",
|
||||||
|
background: "hsl(var(--background) / <alpha-value>)",
|
||||||
|
foreground: "hsl(var(--foreground) / <alpha-value>)",
|
||||||
|
primary: {
|
||||||
|
DEFAULT: "hsl(var(--primary) / <alpha-value>)",
|
||||||
|
foreground: "hsl(var(--primary-foreground) / <alpha-value>)"
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: "hsl(var(--secondary) / <alpha-value>)",
|
||||||
|
foreground: "hsl(var(--secondary-foreground) / <alpha-value>)"
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: "hsl(var(--destructive) / <alpha-value>)",
|
||||||
|
foreground: "hsl(var(--destructive-foreground) / <alpha-value>)"
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: "hsl(var(--muted) / <alpha-value>)",
|
||||||
|
foreground: "hsl(var(--muted-foreground) / <alpha-value>)"
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: "hsl(var(--accent) / <alpha-value>)",
|
||||||
|
foreground: "hsl(var(--accent-foreground) / <alpha-value>)"
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: "hsl(var(--popover) / <alpha-value>)",
|
||||||
|
foreground: "hsl(var(--popover-foreground) / <alpha-value>)"
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
DEFAULT: "hsl(var(--card) / <alpha-value>)",
|
||||||
|
foreground: "hsl(var(--card-foreground) / <alpha-value>)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
lg: "var(--radius)",
|
||||||
|
md: "calc(var(--radius) - 2px)",
|
||||||
|
sm: "calc(var(--radius) - 4px)"
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: [...fontFamily.sans]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
|
@ -2,5 +2,8 @@ import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [sveltekit()]
|
plugins: [sveltekit()],
|
||||||
|
server: {
|
||||||
|
host: '::'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue