Daisy UI #14
69 changed files with 663 additions and 449 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -17,4 +17,5 @@ Thumbs.db
|
|||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
*storybook.log
|
||||
*/dev.db
|
||||
*/dev.db-journal
|
||||
|
|
@ -1,8 +1,14 @@
|
|||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
|
|
||||
"endOfLine": "lf",
|
||||
"arrowParens": "always",
|
||||
"jsxSingleQuote": false,
|
||||
"semi": true,
|
||||
"quoteProps": "as-needed",
|
||||
"tabWidth": 4,
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"overrides": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,15 +2,16 @@
|
|||
const config = {
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|ts|svelte)'],
|
||||
addons: [
|
||||
'@storybook/addon-svelte-csf',
|
||||
'@storybook/addon-essentials',
|
||||
'@chromatic-com/storybook',
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-interactions',
|
||||
'storybook-dark-mode'
|
||||
'@storybook/addon-styling-webpack',
|
||||
'@storybook/addon-svelte-csf',
|
||||
'@storybook/addon-themes',
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/sveltekit',
|
||||
options: {}
|
||||
}
|
||||
options: {},
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
4
.storybook/preview.css
Normal file
4
.storybook/preview.css
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
@import '@flaticon/flaticon-uicons/css/all/all';
|
||||
|
|
@ -1,13 +1,28 @@
|
|||
import { withThemeByDataAttribute } from '@storybook/addon-themes';
|
||||
import './preview.css';
|
||||
|
||||
/** @type { import('@storybook/svelte').Preview } */
|
||||
const preview = {
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i
|
||||
}
|
||||
}
|
||||
}
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
withThemeByDataAttribute({
|
||||
themes: {
|
||||
light: 'light',
|
||||
dark: 'dark',
|
||||
night: 'night',
|
||||
},
|
||||
defaultTheme: 'dark',
|
||||
attributeName: 'data-theme',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default preview;
|
||||
57
README.md
57
README.md
|
|
@ -1,38 +1,69 @@
|
|||
# sv
|
||||
# Hestia
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
||||
Hestia is an early stage project
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
bunx sv create
|
||||
# install dependencies
|
||||
bun install
|
||||
|
||||
# create a new project in my-app
|
||||
bunx sv create my-app
|
||||
# set up local database
|
||||
bun prisma:dev
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `bun install`, start a development server:
|
||||
Once you've created a project and installed dependencies, start a development server:
|
||||
|
||||
```bash
|
||||
bun run dev
|
||||
bun dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
bun run dev -- --open
|
||||
bun dev -- --open
|
||||
|
||||
# to use storybook for components development
|
||||
bun storybook
|
||||
|
||||
# interact with local database
|
||||
bun prisma:studio
|
||||
```
|
||||
|
||||
> You can access the Yoga web-app at `/api/graphql`
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
bun run build
|
||||
bun build
|
||||
```
|
||||
|
||||
## Stack
|
||||
|
||||
- https://svelte.dev/docs/kit/introduction
|
||||
- https://zod.dev/
|
||||
- https://day.js.org/
|
||||
|
||||
### Frontend
|
||||
|
||||
- https://tailwindcss.com/
|
||||
- https://www.flaticon.com/
|
||||
- https://daisyui.com/
|
||||
|
||||
### Backend
|
||||
|
||||
- https://www.prisma.io/
|
||||
- https://pothos-graphql.dev/
|
||||
- https://the-guild.dev/graphql/yoga-server
|
||||
- https://github.com/pinojs/pino
|
||||
|
||||
### Tools
|
||||
|
||||
- https://storybook.js.org/
|
||||
- https://vite.dev/
|
||||
- https://vitest.dev/
|
||||
|
||||
You can preview the production build with `bun run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||
|
|
|
|||
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
3
docs/DAISY.md
Normal file
3
docs/DAISY.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Experienced Issues
|
||||
|
||||
- https://github.com/saadeghi/daisyui/issues/811
|
||||
|
|
@ -18,17 +18,17 @@ export default ts.config(
|
|||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node
|
||||
}
|
||||
}
|
||||
...globals.node,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.svelte'],
|
||||
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: ts.parser
|
||||
}
|
||||
}
|
||||
parser: ts.parser,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
|
|||
12
package.json
12
package.json
|
|
@ -15,7 +15,12 @@
|
|||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build",
|
||||
"test:e2e": "playwright test",
|
||||
"prisma:generate": "prisma generate"
|
||||
"prisma:dev": "prisma migrate dev",
|
||||
"prisma:format": "prisma format",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:push": "prisma db push",
|
||||
"prisma:reset": "prisma migrate reset --force",
|
||||
"prisma:studio": "prisma studio"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^3.2.2",
|
||||
|
|
@ -23,7 +28,9 @@
|
|||
"@playwright/test": "^1.45.3",
|
||||
"@storybook/addon-essentials": "^8.4.7",
|
||||
"@storybook/addon-interactions": "^8.4.7",
|
||||
"@storybook/addon-styling-webpack": "^1.0.1",
|
||||
"@storybook/addon-svelte-csf": "^5.0.0-next.13",
|
||||
"@storybook/addon-themes": "^8.4.7",
|
||||
"@storybook/blocks": "^8.4.7",
|
||||
"@storybook/svelte": "^8.4.7",
|
||||
"@storybook/sveltekit": "^8.4.7",
|
||||
|
|
@ -33,6 +40,7 @@
|
|||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@types/bun": "^1.1.14",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"daisyui": "^4.12.22",
|
||||
"eslint": "^9.7.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.36.0",
|
||||
|
|
@ -42,7 +50,6 @@
|
|||
"prettier-plugin-tailwindcss": "^0.6.5",
|
||||
"prisma": "^6.0.1",
|
||||
"storybook": "^8.4.7",
|
||||
"storybook-dark-mode": "^4.0.2",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"tailwindcss": "^3.4.9",
|
||||
|
|
@ -52,6 +59,7 @@
|
|||
"vitest": "^2.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@flaticon/flaticon-uicons": "^3.3.1",
|
||||
"@lucia-auth/adapter-prisma": "^4.0.1",
|
||||
"@pothos/core": "^4.3.0",
|
||||
"@pothos/plugin-prisma": "^4.4.0",
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { defineConfig } from '@playwright/test';
|
|||
export default defineConfig({
|
||||
webServer: {
|
||||
command: 'npm run build && npm run preview',
|
||||
port: 4173
|
||||
port: 4173,
|
||||
},
|
||||
|
||||
testDir: 'e2e'
|
||||
testDir: 'e2e',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
BIN
prisma/dev.db
BIN
prisma/dev.db
Binary file not shown.
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- The primary key for the `Post` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
|
||||
*/
|
||||
-- RedefineTables
|
||||
PRAGMA defer_foreign_keys=ON;
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Post" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"title" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"published" BOOLEAN DEFAULT false,
|
||||
"authorId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Post" ("authorId", "content", "createdAt", "id", "published", "title", "updatedAt") SELECT "authorId", "content", "createdAt", "id", "published", "title", "updatedAt" FROM "Post";
|
||||
DROP TABLE "Post";
|
||||
ALTER TABLE "new_Post" RENAME TO "Post";
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA defer_foreign_keys=OFF;
|
||||
34
prisma/migrations/20241220011159_init/migration.sql
Normal file
34
prisma/migrations/20241220011159_init/migration.sql
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"email" TEXT,
|
||||
"name" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Session" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"expiresAt" DATETIME NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"userId" TEXT NOT NULL,
|
||||
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Post" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"title" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"published" BOOLEAN DEFAULT false,
|
||||
"authorId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
|
@ -15,34 +15,38 @@ datasource db {
|
|||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
email String? @unique
|
||||
name String
|
||||
password String
|
||||
posts Post[]
|
||||
sessions Session[]
|
||||
id String @id @default(uuid())
|
||||
|
||||
email String? @unique
|
||||
name String
|
||||
password String
|
||||
posts Post[]
|
||||
sessions Session[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @default(uuid())
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
userId String
|
||||
user User @relation(references: [id], fields: [userId])
|
||||
id String @id @default(uuid())
|
||||
|
||||
expiresAt DateTime
|
||||
user User @relation(references: [id], fields: [userId])
|
||||
userId String
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
}
|
||||
|
||||
|
||||
model Post {
|
||||
id String @id @default(uuid())
|
||||
id String @id @default(uuid())
|
||||
|
||||
title String
|
||||
content String
|
||||
published Boolean? @default(false)
|
||||
author User @relation(references: [id], fields: [authorId])
|
||||
authorId String
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
}
|
||||
15
src/app.css
15
src/app.css
|
|
@ -1,19 +1,8 @@
|
|||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
@import '@flaticon/flaticon-uicons/css/all/all';
|
||||
|
||||
:root {
|
||||
@apply text-slate-800;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply font-display text-4xl;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply font-display text-3xl;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply font-display text-2xl;
|
||||
@apply text-base-content;
|
||||
}
|
||||
3
src/app.d.ts
vendored
3
src/app.d.ts
vendored
|
|
@ -2,10 +2,9 @@
|
|||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
|
||||
// interface Error {}
|
||||
interface Locals {
|
||||
user: import("lucia").User | null;
|
||||
user: import('lucia').User | null;
|
||||
session: import('lucia').Session | null;
|
||||
}
|
||||
|
Will need to remove this when we switch to Clerk, maybe add a Todo to not forget it ? Will need to remove this when we switch to Clerk, maybe add a Todo to not forget it ?
|
||||
// interface PageData {}
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
<script module lang="ts">
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import Button from './Button.svelte';
|
||||
import { fn } from '@storybook/test';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Button',
|
||||
component: Button,
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
onClick: fn()
|
||||
},
|
||||
argTypes: {
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['small', 'normal', 'large'],
|
||||
defaultValue: 'normal'
|
||||
},
|
||||
backgroundColor: {
|
||||
control: 'color'
|
||||
},
|
||||
primary: {
|
||||
control: 'boolean',
|
||||
defaultValue: true
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default" args={{ label: 'Button', size: 'normal', primary: true }} />
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLButtonAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
type = 'button',
|
||||
onClick,
|
||||
label,
|
||||
size = 'normal',
|
||||
backgroundColor,
|
||||
primary = false
|
||||
}: {
|
||||
type?: HTMLButtonAttributes['type'];
|
||||
onClick?: () => void;
|
||||
label: string;
|
||||
size?: 'small' | 'normal' | 'large';
|
||||
backgroundColor?: string;
|
||||
primary?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<button
|
||||
{type}
|
||||
onclick={onClick}
|
||||
class={`button button--${size}`}
|
||||
style:background-color={backgroundColor}
|
||||
class:button--primary={primary}
|
||||
class:button--secondary={!primary}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
|
||||
<style>
|
||||
.button {
|
||||
@apply inline-block cursor-pointer rounded-lg border-0 font-semibold leading-none transition-colors hover:opacity-80 hover:shadow-lg;
|
||||
}
|
||||
.button--small {
|
||||
@apply px-2 py-0.5 text-sm;
|
||||
}
|
||||
.button--normal {
|
||||
@apply px-2 py-1 text-base;
|
||||
}
|
||||
.button--large {
|
||||
@apply px-3 py-1.5 text-lg;
|
||||
}
|
||||
.button--primary {
|
||||
@apply bg-red-600 text-white;
|
||||
}
|
||||
.button--secondary {
|
||||
@apply bg-blue-600 text-white;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
<script module lang="ts">
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import Input from './Input.svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Input',
|
||||
component: Input,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
type: {
|
||||
control: 'select',
|
||||
options: [
|
||||
'number',
|
||||
'button',
|
||||
'checkbox',
|
||||
'color',
|
||||
'date',
|
||||
'datetime-local',
|
||||
'email',
|
||||
'file',
|
||||
'hidden',
|
||||
'image',
|
||||
'month',
|
||||
'password',
|
||||
'radio',
|
||||
'range',
|
||||
'reset',
|
||||
'search',
|
||||
'submit',
|
||||
'tel',
|
||||
'text',
|
||||
'time',
|
||||
'url',
|
||||
'week'
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Text" args={{ label: 'Text', name: 'text', type: 'text' }} />
|
||||
<Story name="Password" args={{ label: 'Password', name: 'pass', type: 'password' }} />
|
||||
<Story name="Email" args={{ label: 'Email', name: 'email', type: 'email' }} />
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
type InputProps = {
|
||||
label?: string;
|
||||
name: string;
|
||||
type?: HTMLInputTypeAttribute;
|
||||
} & HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
let { label, name, type = 'text', ...props }: InputProps = $props();
|
||||
</script>
|
||||
|
||||
<div {...props} class={twMerge('hestia-input', props.class)}>
|
||||
{#if label}
|
||||
<label for={name}>{label}</label>
|
||||
<div class="line"></div>
|
||||
{/if}
|
||||
<input {name} id={name} {type} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.line {
|
||||
@apply h-8 border border-l-slate-200;
|
||||
}
|
||||
.hestia-input {
|
||||
@apply flex w-fit items-center gap-2 rounded-lg bg-slate-100 p-2 text-slate-700 outline-blue-400 focus-within:outline;
|
||||
}
|
||||
.hestia-input > label {
|
||||
@apply font-display text-lg;
|
||||
}
|
||||
.hestia-input > input {
|
||||
all: unset;
|
||||
@apply text-lg;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
<script lang="ts">
|
||||
let { title }: { title: string } = $props();
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<div class="navbar">
|
||||
<div>
|
||||
<h2>Hestia</h2>
|
||||
</div>
|
||||
<div>
|
||||
<h1>{title}</h1>
|
||||
</div>
|
||||
<div>
|
||||
<p>Welcome!</p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
.navbar {
|
||||
@apply flex items-center justify-between border-b border-slate-200 bg-slate-50 px-6 py-2 font-display drop-shadow;
|
||||
}
|
||||
|
||||
.navbar h1 {
|
||||
@apply text-2xl;
|
||||
}
|
||||
|
||||
.navbar h2 {
|
||||
@apply text-xl;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -3,9 +3,8 @@
|
|||
import Navbar from './Navbar.svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Navbar',
|
||||
title: 'Navigation/Navbar',
|
||||
component: Navbar,
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
12
src/lib/components/Navigation/Navbar/Navbar.svelte
Normal file
12
src/lib/components/Navigation/Navbar/Navbar.svelte
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<script lang="ts">
|
||||
let { title }: { title: string } = $props();
|
||||
</script>
|
||||
|
||||
<header class="navbar justify-between bg-base-200 px-4">
|
||||
<h2 class="prose prose-xl">Hestia</h2>
|
||||
<h1 class="prose prose-2xl">{title}</h1>
|
||||
<p class="prose prose-lg">Welcome!</p>
|
||||
|
Make me think that we should probably start using the library i18n, it will help with translation and not hard coding text in the code Make me think that we should probably start using the library i18n, it will help with translation and not hard coding text in the code
Yes, it was part of the sveltekit start project but I went with a simpler setup since it was a lot at once. #19 Yes, it was part of the sveltekit start project but I went with a simpler setup since it was a lot at once. #19
|
||||
</header>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
3
src/lib/components/Navigation/Navbar/index.ts
Normal file
3
src/lib/components/Navigation/Navbar/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Navbar from './Navbar.svelte';
|
||||
|
||||
export default Navbar;
|
||||
5
src/lib/components/Navigation/Tabs/Tab.svelte
Normal file
5
src/lib/components/Navigation/Tabs/Tab.svelte
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<script lang="ts">
|
||||
let { active, label, onclick } = $props();
|
||||
</script>
|
||||
|
||||
<input aria-label={label} type="radio" role="tab" class="tab" class:tab-active={active} {onclick} />
|
||||
27
src/lib/components/Navigation/Tabs/Tabs.stories.svelte
Normal file
27
src/lib/components/Navigation/Tabs/Tabs.stories.svelte
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<script module lang="ts">
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import Tabs from './Tabs.svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Navigation/Tabs',
|
||||
component: Tabs,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['xs', 'sm', 'rg', 'lg'],
|
||||
},
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['none', 'bordered', 'lifted', 'boxed'],
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
{#snippet template(args: Partial<ComponentProps<typeof Tabs>>)}
|
||||
<Tabs tabs={['Tab 1', 'Tab 2']} {...args} />
|
||||
{/snippet}
|
||||
|
||||
<Story name="Default" args={{}} children={template} />
|
||||
36
src/lib/components/Navigation/Tabs/Tabs.svelte
Normal file
36
src/lib/components/Navigation/Tabs/Tabs.svelte
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<script lang="ts">
|
||||
import type { DaisySize } from '$lib/types';
|
||||
import Tab from './Tab.svelte';
|
||||
|
||||
type Props = {
|
||||
size?: DaisySize;
|
||||
tabs: string[];
|
||||
selected?: number;
|
||||
variant?: 'none' | 'bordered' | 'lifted' | 'boxed';
|
||||
};
|
||||
|
||||
let { size, tabs, selected: value = $bindable(0), variant = 'none' }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
role="tablist"
|
||||
class="tabs w-full"
|
||||
class:tabs-xs={size === 'xs'}
|
||||
class:tabs-sm={size === 'sm'}
|
||||
class:tabs-lg={size === 'lg'}
|
||||
class:tabs-bordered={variant === 'bordered'}
|
||||
class:tabs-lifted={variant === 'lifted'}
|
||||
class:tabs-boxed={variant === 'boxed'}
|
||||
>
|
||||
{#each tabs as tab, index}
|
||||
{#key [tab, value]}
|
||||
<Tab
|
||||
active={index === value}
|
||||
label={tab}
|
||||
onclick={() => {
|
||||
value = index;
|
||||
}}
|
||||
/>
|
||||
{/key}
|
||||
{/each}
|
||||
</div>
|
||||
4
src/lib/components/Navigation/Tabs/index.ts
Normal file
4
src/lib/components/Navigation/Tabs/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import Tabs from './Tabs.svelte';
|
||||
|
||||
export default Tabs;
|
||||
export { default as Tabs } from './Tabs.svelte';
|
||||
4
src/lib/components/Navigation/index.ts
Normal file
4
src/lib/components/Navigation/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import Navbar from './Navbar';
|
||||
import Tabs from './Tabs';
|
||||
|
||||
export { Navbar, Tabs };
|
||||
44
src/lib/components/common/Button/Button.stories.svelte
Normal file
44
src/lib/components/common/Button/Button.stories.svelte
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<script module lang="ts">
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import Button from './Button.svelte';
|
||||
import { fn } from '@storybook/test';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Actions/Button',
|
||||
component: Button,
|
||||
args: {
|
||||
onClick: fn(),
|
||||
},
|
||||
argTypes: {
|
||||
color: {
|
||||
control: 'select',
|
||||
options: [
|
||||
'neutral',
|
||||
'primary',
|
||||
'secondary',
|
||||
'accent',
|
||||
'ghost',
|
||||
'link',
|
||||
'info',
|
||||
'success',
|
||||
'warning',
|
||||
'error',
|
||||
],
|
||||
},
|
||||
outline: {
|
||||
control: 'boolean',
|
||||
},
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['Default', 'xs', 'sm', 'lg'],
|
||||
defaultValue: 'Default',
|
||||
},
|
||||
type: {
|
||||
control: 'select',
|
||||
options: ['button', 'reset', 'submit'],
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default" args={{ label: 'Button', color: 'primary' }} />
|
||||
57
src/lib/components/common/Button/Button.svelte
Normal file
57
src/lib/components/common/Button/Button.svelte
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<script lang="ts">
|
||||
import type { DaisyColor, DaisySize } from '$lib/types';
|
||||
import type { HTMLButtonAttributes } from 'svelte/elements';
|
||||
|
||||
interface Props {
|
||||
block?: boolean;
|
||||
color?: DaisyColor;
|
||||
glass?: boolean;
|
||||
label: string;
|
||||
outline?: boolean;
|
||||
onClick?: () => void;
|
||||
responsive?: boolean;
|
||||
size?: DaisySize;
|
||||
type?: HTMLButtonAttributes['type'];
|
||||
wide?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
block = false,
|
||||
color,
|
||||
glass = false,
|
||||
label,
|
||||
outline = false,
|
||||
onClick,
|
||||
responsive = false,
|
||||
size,
|
||||
type = 'button',
|
||||
wide = false,
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<button
|
||||
{type}
|
||||
onclick={onClick}
|
||||
class:btn-outline={outline}
|
||||
class:btn-block={block}
|
||||
class:btn-wide={wide}
|
||||
class:glass
|
||||
class:btn-xs={size === 'xs'}
|
||||
class:btn-sm={size === 'sm'}
|
||||
class:btn-lg={size === 'lg'}
|
||||
class:btn-neutral={color === 'neutral'}
|
||||
class:btn-primary={color === 'primary'}
|
||||
class:btn-secondary={color === 'secondary'}
|
||||
class:btn-accent={color === 'accent'}
|
||||
class:btn-ghost={color === 'ghost'}
|
||||
class:btn-link={color === 'link'}
|
||||
class:btn-info={color === 'info'}
|
||||
class:btn-success={color === 'success'}
|
||||
class:btn-warning={color === 'warning'}
|
||||
class:btn-error={color === 'error'}
|
||||
class={`btn ${responsive && 'btn-xs sm:btn-sm md:btn-md lg:btn-lg'}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
|
||||
<style></style>
|
||||
3
src/lib/components/common/Button/index.ts
Normal file
3
src/lib/components/common/Button/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Button from './Button.svelte';
|
||||
|
||||
export default Button;
|
||||
|
|
@ -3,9 +3,8 @@
|
|||
import Loader from './Loader.svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Loader',
|
||||
title: 'Feedback/Loader',
|
||||
component: Loader,
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
3
src/lib/components/common/Loader/index.ts
Normal file
3
src/lib/components/common/Loader/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Loader from './Loader.svelte';
|
||||
|
||||
export default Loader;
|
||||
45
src/lib/components/common/TextInput/TextInput.stories.svelte
Normal file
45
src/lib/components/common/TextInput/TextInput.stories.svelte
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<script module lang="ts">
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import TextInput from './TextInput.svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Data Input/Text Input',
|
||||
component: TextInput,
|
||||
argTypes: {
|
||||
color: {
|
||||
control: 'select',
|
||||
options: [
|
||||
'primary',
|
||||
'secondary',
|
||||
'accent',
|
||||
'ghost',
|
||||
'link',
|
||||
'info',
|
||||
'success',
|
||||
'warning',
|
||||
'error',
|
||||
],
|
||||
},
|
||||
bordered: {
|
||||
control: 'boolean',
|
||||
},
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['Default', 'xs', 'sm', 'lg'],
|
||||
defaultValue: 'Default',
|
||||
},
|
||||
type: {
|
||||
control: 'select',
|
||||
options: ['email', 'password', 'search', 'tel', 'text', 'url'],
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
{#snippet icon()}
|
||||
<i class="fi fi-rr-user"></i>
|
||||
{/snippet}
|
||||
|
||||
<Story name="Text Label" args={{ color: 'primary', name: 'text', start: 'Text' }} />
|
||||
<Story name="Icon Start" args={{ color: 'secondary', name: 'text', start: icon }} />
|
||||
<Story name="Icon End" args={{ color: 'secondary', name: 'text', end: icon }} />
|
||||
68
src/lib/components/common/TextInput/TextInput.svelte
Normal file
68
src/lib/components/common/TextInput/TextInput.svelte
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<script lang="ts">
|
||||
import type { DaisyColor, DaisySize } from '$lib/types';
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { HTMLInputTypeAttribute } from 'svelte/elements';
|
||||
import { fade as fadeTransition } from 'svelte/transition';
|
||||
|
||||
type Props = {
|
||||
bordered?: boolean;
|
||||
color?: Exclude<DaisyColor, 'neutral'>;
|
||||
disabled?: boolean;
|
||||
fade?: boolean;
|
||||
start?: Snippet | string;
|
||||
end?: Snippet | string;
|
||||
name: string;
|
||||
placeholder?: string;
|
||||
size?: DaisySize;
|
||||
type?: Extract<
|
||||
HTMLInputTypeAttribute,
|
||||
'email' | 'password' | 'search' | 'tel' | 'text' | 'url'
|
||||
>;
|
||||
};
|
||||
|
||||
let {
|
||||
bordered = false,
|
||||
color,
|
||||
disabled,
|
||||
fade,
|
||||
start,
|
||||
end,
|
||||
name,
|
||||
placeholder,
|
||||
size,
|
||||
type = 'text',
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<label
|
||||
transition:fadeTransition={{ duration: fade ? 200 : 0 }}
|
||||
class="input flex w-full items-center gap-2"
|
||||
class:input-bordered={bordered}
|
||||
class:input-xs={size === 'xs'}
|
||||
class:input-sm={size === 'sm'}
|
||||
class:input-lg={size === 'lg'}
|
||||
class:input-primary={color === 'primary'}
|
||||
class:input-secondary={color === 'secondary'}
|
||||
class:input-accent={color === 'accent'}
|
||||
class:input-ghost={color === 'ghost'}
|
||||
class:input-link={color === 'link'}
|
||||
class:input-info={color === 'info'}
|
||||
class:input-success={color === 'success'}
|
||||
class:input-warning={color === 'warning'}
|
||||
class:input-error={color === 'error'}
|
||||
>
|
||||
{#if typeof start === 'string'}
|
||||
{start}
|
||||
{:else}
|
||||
{@render start?.()}
|
||||
{/if}
|
||||
<input {disabled} {name} {placeholder} {type} class="grow" />
|
||||
{#if typeof end === 'string'}
|
||||
{end}
|
||||
{:else}
|
||||
{@render end?.()}
|
||||
{/if}
|
||||
</label>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
3
src/lib/components/common/TextInput/index.ts
Normal file
3
src/lib/components/common/TextInput/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import TextInput from './TextInput.svelte';
|
||||
|
||||
export default TextInput;
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './Navbar.svelte';
|
||||
|
|
@ -8,7 +8,7 @@ export interface Configuration {
|
|||
export const LoadConfig = (): Configuration => {
|
||||
const { success, data, error } = z
|
||||
.object({
|
||||
VITE_APP_VERSION: z.string().default('development')
|
||||
VITE_APP_VERSION: z.string().default('development'),
|
||||
})
|
||||
.safeParse(import.meta.env);
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ export const LoadConfig = (): Configuration => {
|
|||
}
|
||||
|
||||
return {
|
||||
app_version: data!.VITE_APP_VERSION
|
||||
app_version: data!.VITE_APP_VERSION,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -19,5 +19,5 @@ export const yogaLogger: YogaLogger = {
|
|||
error(...args) {
|
||||
// @ts-expect-error types dont match
|
||||
logger.error(...args);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -7,14 +7,14 @@ const adapter = new PrismaAdapter(prisma.session, prisma.user);
|
|||
export const auth = new Lucia(adapter, {
|
||||
sessionCookie: {
|
||||
attributes: {
|
||||
secure: process.env.NODE_ENV === 'production'
|
||||
}
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
},
|
||||
},
|
||||
getUserAttributes: (attributes) => {
|
||||
return {
|
||||
email: attributes.email
|
||||
email: attributes.email,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
declare module 'lucia' {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,6 @@ export const builder = new SchemaBuilder<PothosType>({
|
|||
// use where clause from prismaRelatedConnection for totalCount (defaults to true)
|
||||
filterConnectionTotalCount: true,
|
||||
// warn when not using a query parameter correctly
|
||||
onUnusedQuery: process.env.NODE_ENV === 'production' ? null : 'warn'
|
||||
}
|
||||
onUnusedQuery: process.env.NODE_ENV === 'production' ? null : 'warn',
|
||||
},
|
||||
});
|
||||
|
|
@ -10,5 +10,5 @@ export const DateScalar = builder.scalarType('Date', {
|
|||
throw new Error('Cyka blyat');
|
||||
}
|
||||
return new Date(date);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -5,7 +5,7 @@ builder.queryType({});
|
|||
builder.queryField('version', (t) =>
|
||||
t.string({
|
||||
description: 'Application version',
|
||||
resolve: (parent, args, context) => context.config.app_version
|
||||
resolve: (parent, args, context) => context.config.app_version,
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,39 +9,39 @@ export const Post = builder.prismaObject('Post', {
|
|||
published: t.exposeBoolean('published'),
|
||||
author: t.relation('author'),
|
||||
createdAt: t.expose('createdAt', {
|
||||
type: 'Date'
|
||||
type: 'Date',
|
||||
}),
|
||||
updatedAt: t.expose('updatedAt', {
|
||||
type: 'Date'
|
||||
})
|
||||
})
|
||||
type: 'Date',
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
const CreatePost = builder.inputType('CreatePost', {
|
||||
fields: (t) => ({
|
||||
title: t.string({
|
||||
required: true
|
||||
required: true,
|
||||
}),
|
||||
content: t.string({
|
||||
required: true
|
||||
required: true,
|
||||
}),
|
||||
published: t.boolean(),
|
||||
authorId: t.id({
|
||||
required: true
|
||||
})
|
||||
})
|
||||
required: true,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
const UpdatePost = builder.inputType('UpdatePost', {
|
||||
fields: (t) => ({
|
||||
id: t.id({
|
||||
required: true
|
||||
required: true,
|
||||
}),
|
||||
title: t.string(),
|
||||
content: t.string(),
|
||||
published: t.boolean(),
|
||||
authorId: t.id()
|
||||
})
|
||||
authorId: t.id(),
|
||||
}),
|
||||
});
|
||||
|
||||
builder.queryFields((t) => ({
|
||||
|
|
@ -49,19 +49,19 @@ builder.queryFields((t) => ({
|
|||
type: [Post],
|
||||
resolve: async () => {
|
||||
return await prisma.post.findMany();
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
builder.mutationFields((t) => ({
|
||||
createPost: t.field({
|
||||
type: Post,
|
||||
args: {
|
||||
input: t.arg({ required: true, type: CreatePost })
|
||||
input: t.arg({ required: true, type: CreatePost }),
|
||||
},
|
||||
resolve: async (parent, args) => {
|
||||
const author = await prisma.user.findUnique({
|
||||
where: { id: Number(args.input.authorId) }
|
||||
where: { id: Number(args.input.authorId) },
|
||||
});
|
||||
if (!author) {
|
||||
throw new Error('Author does not exist!');
|
||||
|
|
@ -73,23 +73,23 @@ builder.mutationFields((t) => ({
|
|||
published: args.input.published,
|
||||
author: {
|
||||
connect: {
|
||||
id: author.id
|
||||
}
|
||||
}
|
||||
}
|
||||
id: author.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return post;
|
||||
}
|
||||
},
|
||||
}),
|
||||
updatePost: t.field({
|
||||
type: Post,
|
||||
args: {
|
||||
input: t.arg({ required: true, type: UpdatePost })
|
||||
input: t.arg({ required: true, type: UpdatePost }),
|
||||
},
|
||||
resolve: async (parent, args) => {
|
||||
const post = await prisma.post.update({
|
||||
where: {
|
||||
id: Number(args.input.id)
|
||||
id: Number(args.input.id),
|
||||
},
|
||||
data: {
|
||||
title: args.input.title ?? undefined,
|
||||
|
|
@ -98,13 +98,13 @@ builder.mutationFields((t) => ({
|
|||
...(args.input.authorId && {
|
||||
author: {
|
||||
connect: {
|
||||
id: Number(args.input.authorId)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
id: Number(args.input.authorId),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
return post;
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
|
@ -8,33 +8,33 @@ export const User = builder.prismaObject('User', {
|
|||
name: t.exposeString('name'),
|
||||
posts: t.relation('posts'),
|
||||
createdAt: t.expose('createdAt', {
|
||||
type: 'Date'
|
||||
type: 'Date',
|
||||
}),
|
||||
updatedAt: t.expose('updatedAt', {
|
||||
type: 'Date'
|
||||
})
|
||||
})
|
||||
type: 'Date',
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
const CreateUser = builder.inputType('CreateUser', {
|
||||
fields: (t) => ({
|
||||
email: t.string({
|
||||
required: true
|
||||
required: true,
|
||||
}),
|
||||
name: t.string({
|
||||
required: true
|
||||
})
|
||||
})
|
||||
required: true,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
const UpdateUser = builder.inputType('UpdateUser', {
|
||||
fields: (t) => ({
|
||||
id: t.id({
|
||||
required: true
|
||||
required: true,
|
||||
}),
|
||||
email: t.string(),
|
||||
name: t.string()
|
||||
})
|
||||
name: t.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
builder.queryFields((t) => ({
|
||||
|
|
@ -42,42 +42,42 @@ builder.queryFields((t) => ({
|
|||
type: [User],
|
||||
resolve: async () => {
|
||||
return await prisma.user.findMany();
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
builder.mutationFields((t) => ({
|
||||
createUser: t.field({
|
||||
type: User,
|
||||
args: {
|
||||
input: t.arg({ required: true, type: CreateUser })
|
||||
input: t.arg({ required: true, type: CreateUser }),
|
||||
},
|
||||
resolve: async (parent, args) => {
|
||||
const post = await prisma.user.create({
|
||||
data: {
|
||||
email: args.input.email,
|
||||
name: args.input.name
|
||||
}
|
||||
name: args.input.name,
|
||||
},
|
||||
});
|
||||
return post;
|
||||
}
|
||||
},
|
||||
}),
|
||||
updateUser: t.field({
|
||||
type: User,
|
||||
args: {
|
||||
input: t.arg({ required: true, type: UpdateUser })
|
||||
input: t.arg({ required: true, type: UpdateUser }),
|
||||
},
|
||||
resolve: async (parent, args) => {
|
||||
const post = await prisma.user.update({
|
||||
where: {
|
||||
id: Number(args.input.id)
|
||||
id: Number(args.input.id),
|
||||
},
|
||||
data: {
|
||||
email: args.input.email,
|
||||
name: args.input.name ?? undefined
|
||||
}
|
||||
name: args.input.name ?? undefined,
|
||||
},
|
||||
});
|
||||
return post;
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
|
@ -3,5 +3,5 @@ import type { YogaInitialContext } from 'graphql-yoga';
|
|||
|
||||
export const Context = (initialContext: YogaInitialContext) => ({
|
||||
...initialContext,
|
||||
config: Config
|
||||
config: Config,
|
||||
});
|
||||
|
|
@ -10,5 +10,5 @@ export const Yoga = createYoga<RequestEvent>({
|
|||
graphqlEndpoint: '/api/graphql',
|
||||
// Let Yoga use sveltekit's Response object
|
||||
fetchAPI: { Response },
|
||||
logging: yogaLogger
|
||||
logging: yogaLogger,
|
||||
});
|
||||
12
src/lib/types/daisy-colors.ts
Normal file
12
src/lib/types/daisy-colors.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export type DaisyColor =
|
||||
| 'default'
|
||||
| 'neutral'
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'accent'
|
||||
| 'ghost'
|
||||
| 'link'
|
||||
| 'info'
|
||||
| 'success'
|
||||
| 'warning'
|
||||
| 'error';
|
||||
1
src/lib/types/daisy-sizes.ts
Normal file
1
src/lib/types/daisy-sizes.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export type DaisySize = 'xs' | 'sm' | 'lg';
|
||||
2
src/lib/types/index.ts
Normal file
2
src/lib/types/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from './daisy-colors';
|
||||
export * from './daisy-sizes';
|
||||
|
|
@ -9,6 +9,6 @@
|
|||
|
||||
<style>
|
||||
.layout {
|
||||
@apply h-screen w-screen animate-fade bg-slate-100;
|
||||
@apply h-screen w-screen bg-base-100;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
import { prisma } from '$lib/server/prisma';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
export async function load(event) {
|
||||
const userId = event.cookies.get('user');
|
||||
if (!userId) {
|
||||
return {
|
||||
authenticated: false
|
||||
};
|
||||
const sessionId = event.cookies.get('auth_session');
|
||||
if (!sessionId) {
|
||||
redirect(303, '/login');
|
||||
}
|
||||
const user = await prisma.user.findUnique({
|
||||
const user = await prisma.session.findUnique({
|
||||
where: {
|
||||
id: userId
|
||||
}
|
||||
id: sessionId,
|
||||
},
|
||||
});
|
||||
return {
|
||||
authenticated: !!user
|
||||
};
|
||||
if (!user) {
|
||||
redirect(401, '/login');
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import Loader from '$lib/components/Loader.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
import Loader from '$lib/components/common/Loader';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
$effect(() => {
|
||||
const id = setTimeout(() => (data.authenticated ? goto('/app') : goto('/login')), 1500);
|
||||
const id = setTimeout(() => goto('/app'), 1500);
|
||||
return () => {
|
||||
clearTimeout(id);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="site-loader">
|
||||
<h1>Hestia</h1>
|
||||
<div class="site-loader" transition:fade>
|
||||
<Loader />
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import Navbar from '$lib/components/Navbar.svelte';
|
||||
import { Navbar } from '$lib/components/Navigation';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ export const actions = {
|
|||
}
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
email: form.get('email') as string
|
||||
}
|
||||
email: form.get('email') as string,
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
logger.error('User not found! ${user}');
|
||||
|
|
@ -31,7 +31,7 @@ export const actions = {
|
|||
const sessionCookie = auth.createSessionCookie(session.id);
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: '/',
|
||||
maxAge: 120
|
||||
maxAge: 120,
|
||||
});
|
||||
redirect(302, '/');
|
||||
},
|
||||
|
|
@ -47,8 +47,8 @@ export const actions = {
|
|||
data: {
|
||||
email: form.get('email') as string,
|
||||
name: form.get('name') as string,
|
||||
password: hashedPassword
|
||||
}
|
||||
password: hashedPassword,
|
||||
},
|
||||
});
|
||||
const session = await auth.createSession(user.id.toString(), {});
|
||||
const sessionCookie = auth.createSessionCookie(session.id);
|
||||
|
|
@ -57,8 +57,8 @@ export const actions = {
|
|||
}
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: '/',
|
||||
maxAge: 120
|
||||
maxAge: 120,
|
||||
});
|
||||
redirect(302, '/');
|
||||
}
|
||||
},
|
||||
} satisfies Actions;
|
||||
|
|
|
|||
|
|
@ -1,38 +1,50 @@
|
|||
<script lang="ts">
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import Input from '$lib/components/Input.svelte';
|
||||
import { fade, scale } from 'svelte/transition';
|
||||
import Button from '$lib/components/common/Button';
|
||||
import TextInput from '$lib/components/common/TextInput';
|
||||
import Tabs from '$lib/components/Navigation/Tabs';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
let mode: 'register' | 'login' = $state('login');
|
||||
let action = $derived(mode === 'login' ? '?/login' : '?/register');
|
||||
|
||||
function onViewToggle() {
|
||||
mode = mode === 'login' ? 'register' : 'login';
|
||||
}
|
||||
let tab: 0 | 1 = $state(0);
|
||||
</script>
|
||||
|
||||
<div class="page">
|
||||
<h1 class="underline">Hestia</h1>
|
||||
<div class="login">
|
||||
<form method="POST" {action} transition:scale>
|
||||
<h2 transition:fade>{mode === 'login' ? 'Login' : 'Register'}</h2>
|
||||
{#if mode === 'register'}
|
||||
<div transition:fade>
|
||||
<Input label="Name" name="name" />
|
||||
</div>
|
||||
{#snippet userIcon()}
|
||||
<i class="fi fi-br-envelope"></i>
|
||||
{/snippet}
|
||||
|
||||
{#snippet passwordIcon()}
|
||||
<i class="fi fi-br-key"></i>
|
||||
{/snippet}
|
||||
|
||||
{#snippet nameIcon()}
|
||||
<i class="fi fi-rr-user"></i>
|
||||
{/snippet}
|
||||
|
||||
{#snippet form(variant: 'login' | 'register')}
|
||||
<form method="POST" action={`?/${variant}`}>
|
||||
<div class="card-body gap-4">
|
||||
<TextInput start={userIcon} placeholder="Email" name="email" type="email" />
|
||||
<TextInput
|
||||
start={passwordIcon}
|
||||
placeholder="Password"
|
||||
name="password"
|
||||
type="password"
|
||||
/>
|
||||
{#if variant === 'register'}
|
||||
<TextInput start={nameIcon} placeholder="Name" name="name" fade />
|
||||
{/if}
|
||||
<Input label="Email" name="email" type="email" />
|
||||
<Input label="Password" name="password" type="password" />
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
onClick={onViewToggle}
|
||||
label={mode === 'login' ? 'Register' : 'Login'}
|
||||
size="large"
|
||||
primary
|
||||
/>
|
||||
<Button type="submit" label="Submit" size="large" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-actions px-4">
|
||||
<Button block type="submit" label="Submit" outline />
|
||||
</div>
|
||||
</form>
|
||||
{/snippet}
|
||||
|
||||
<div class="page" transition:fade>
|
||||
<div class="card bg-base-200 py-4 shadow-xl">
|
||||
<div class="card-title">
|
||||
<Tabs variant="bordered" bind:selected={tab} tabs={['Login', 'Register']} />
|
||||
</div>
|
||||
{@render form(tab === 0 ? 'login' : 'register')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -40,10 +52,4 @@
|
|||
.page {
|
||||
@apply flex flex-col items-center justify-around gap-24 py-[10%];
|
||||
}
|
||||
.login {
|
||||
@apply w-fit max-w-lg animate-fade rounded-lg bg-white p-8;
|
||||
}
|
||||
.login > form {
|
||||
@apply flex w-full flex-col items-center gap-8 rounded-lg;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -11,8 +11,8 @@ const config = {
|
|||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
}
|
||||
adapter: adapter(),
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import typography from '@tailwindcss/typography';
|
||||
import daisyui from 'daisyui';
|
||||
import type { Config } from 'tailwindcss';
|
||||
|
||||
export default {
|
||||
|
|
@ -7,19 +8,22 @@ export default {
|
|||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
display: ['Baskervville SC']
|
||||
display: ['Baskervville SC'],
|
||||
},
|
||||
animation: {
|
||||
fade: 'fadeIn .5s ease-in-out'
|
||||
fade: 'fadeIn .5s ease-in-out',
|
||||
},
|
||||
keyframes: {
|
||||
fadeIn: {
|
||||
from: { opacity: '0' },
|
||||
to: { opacity: '1' }
|
||||
}
|
||||
}
|
||||
}
|
||||
to: { opacity: '1' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
plugins: [typography]
|
||||
plugins: [typography, daisyui],
|
||||
daisyui: {
|
||||
logs: false,
|
||||
},
|
||||
} satisfies Config;
|
||||
|
|
@ -5,6 +5,6 @@ export default defineConfig({
|
|||
plugins: [sveltekit()],
|
||||
|
||||
test: {
|
||||
include: ['src/**/*.{test,spec}.{js,ts}']
|
||||
}
|
||||
include: ['src/**/*.{test,spec}.{js,ts}'],
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue
🙈