Daisy UI (#14)
* add dependency * rename Input to TextInput and use daisy * base styling * storybook setup with tailwind and theme changer * daisy buttons * add flaticons * text input to daisy * Navbar to daisy * login using daisy * autodocs is... auto * refactor Tabs to separate components * move TextInput * move button * move navbar * remove index * move container * move loader * move tabs to navigation * organize storybook hierarchy * use card * remove storybook dark mode * README * ignore db file * ignore db * prisma scripts * format * blyat * fix redirect
This commit is contained in:
parent
992eb07f5c
commit
6ddaa69f69
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;
|
||||
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;
|
||||
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;
|
||||
@apply text-base-content;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply font-display text-4xl;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply font-display text-3xl;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply font-display text-2xl;
|
||||
}
|
||||
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;
|
||||
}
|
||||
// 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,10 +3,9 @@
|
|||
import Navbar from './Navbar.svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Navbar',
|
||||
title: 'Navigation/Navbar',
|
||||
component: Navbar,
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default" args={{ title: 'Storybook' }} />
|
||||
<Story name="Default" args={{ title: 'Storybook' }} />
|
||||
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>
|
||||
</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,10 +3,9 @@
|
|||
import Loader from './Loader.svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Loader',
|
||||
title: 'Feedback/Loader',
|
||||
component: Loader,
|
||||
tags: ['autodocs']
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default" args={{}} />
|
||||
<Story name="Default" args={{}} />
|
||||
|
|
@ -48,4 +48,4 @@
|
|||
transform: rotate(-360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
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';
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
// place files you want to import through the `$lib` alias in this folder.
|
||||
export * from './components';
|
||||
export * from './components';
|
||||
|
|
|
|||
|
|
@ -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,8 +17,8 @@ export const LoadConfig = (): Configuration => {
|
|||
}
|
||||
|
||||
return {
|
||||
app_version: data!.VITE_APP_VERSION
|
||||
app_version: data!.VITE_APP_VERSION,
|
||||
};
|
||||
};
|
||||
|
||||
export const Config = LoadConfig();
|
||||
export const Config = LoadConfig();
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export * from './schema';
|
||||
export * from './schema';
|
||||
|
|
|
|||
|
|
@ -10,5 +10,5 @@ export const DateScalar = builder.scalarType('Date', {
|
|||
throw new Error('Cyka blyat');
|
||||
}
|
||||
return new Date(date);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ export type Scalars = {
|
|||
Input: Date;
|
||||
Output: 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,
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -15,4 +15,4 @@ import './Scalars';
|
|||
import './posts';
|
||||
import './users';
|
||||
|
||||
export const Schema = builder.toSchema();
|
||||
export const Schema = builder.toSchema();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
})
|
||||
}));
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
export const prisma = new PrismaClient();
|
||||
export const prisma = new PrismaClient();
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@ import type { YogaInitialContext } from 'graphql-yoga';
|
|||
|
||||
export const Context = (initialContext: YogaInitialContext) => ({
|
||||
...initialContext,
|
||||
config: Config
|
||||
});
|
||||
config: Config,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
export * from './context';
|
||||
export * from './server';
|
||||
export * from './server';
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</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,3 +1,3 @@
|
|||
import { Yoga } from '$lib/server/yoga';
|
||||
|
||||
export { Yoga as GET, Yoga as POST };
|
||||
export { Yoga as GET, Yoga as POST };
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import Navbar from '$lib/components/Navbar.svelte';
|
||||
import { Navbar } from '$lib/components/Navigation';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<Navbar title="Svelte" />
|
||||
{@render children()}
|
||||
{@render children()}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</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]
|
||||
} satisfies Config;
|
||||
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