mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Merge pull request #15 from BradNut/shadcn-svelte
Move to Shadcn Svelte and Drizzle
This commit is contained in:
commit
6503659352
329 changed files with 37002 additions and 3522 deletions
|
|
@ -1,31 +1,30 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: ['plugin:svelte/recommended'],
|
||||
plugins: ['@typescript-eslint'],
|
||||
ignorePatterns: ['*.cjs'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.svelte'],
|
||||
parser: 'svelte-eslint-parser',
|
||||
// Parse the `<script>` in `.svelte` as TypeScript by adding the following configuration.
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
}
|
||||
}
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:svelte/recommended',
|
||||
'prettier'
|
||||
],
|
||||
settings: {
|
||||
'svelte3/typescript': () => require('typescript')
|
||||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020,
|
||||
project: './tsconfig.json',
|
||||
extraFileExtensions: ['.svelte'] // This is a required setting in `@typescript-eslint/parser` v4.24.0.
|
||||
extraFileExtensions: ['.svelte']
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
}
|
||||
};
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.svelte'],
|
||||
parser: 'svelte-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -5,8 +5,12 @@ node_modules
|
|||
/package
|
||||
.env
|
||||
.env.*
|
||||
*.xdp*
|
||||
!.env.example
|
||||
.vercel
|
||||
.output
|
||||
.idea
|
||||
.fleet
|
||||
.fleet
|
||||
|
||||
# Sentry Config File
|
||||
.sentryclirc
|
||||
|
|
|
|||
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
v20
|
||||
46
.vscode/launch.json
vendored
46
.vscode/launch.json
vendored
|
|
@ -1,17 +1,31 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Vite DEV server",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "npx",
|
||||
"runtimeArgs": ["vite"],
|
||||
"type": "node",
|
||||
"serverReadyAction": {
|
||||
"action": "debugWithChrome",
|
||||
"pattern": "Local: http://localhost:([0-9]+)",
|
||||
"uriFormat": "http://localhost:%s"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch server",
|
||||
"request": "launch",
|
||||
"runtimeArgs": ["dev"],
|
||||
"runtimeExecutable": "pnpm",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"type": "node",
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch browser",
|
||||
"url": "http://127.0.0.1:5173",
|
||||
"runtimeExecutable": "/home/bshellnu/.local/share/flatpak/app/org.chromium.Chromium/current/active/files/chromium/chrome",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Both",
|
||||
"configurations": ["Launch server", "Launch browser"]
|
||||
}
|
||||
]
|
||||
}
|
||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"cSpell.words": ["kickstarter", "msrp"]
|
||||
"cSpell.words": ["iconify", "kickstarter", "lucide", "msrp", "pcss"]
|
||||
}
|
||||
|
|
|
|||
13
components.json
Normal file
13
components.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"$schema": "https://shadcn-svelte.com/schema.json",
|
||||
"style": "default",
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/app.postcss",
|
||||
"baseColor": "slate"
|
||||
},
|
||||
"aliases": {
|
||||
"components": "$lib/components",
|
||||
"utils": "$lib/utils"
|
||||
}
|
||||
}
|
||||
20
drizzle.config.ts
Normal file
20
drizzle.config.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import 'dotenv/config';
|
||||
import { defineConfig } from 'drizzle-kit';
|
||||
|
||||
export default defineConfig({
|
||||
schema: './src/schema.ts',
|
||||
out: './drizzle',
|
||||
driver: 'pg',
|
||||
dbCredentials: {
|
||||
host: process.env.DATABASE_HOST || 'localhost',
|
||||
port: Number(process.env.DATABASE_PORT) || 5432,
|
||||
user: process.env.DATABASE_USER,
|
||||
password: process.env.DATABASE_PASSWORD,
|
||||
database: process.env.DATABASE || 'boredgame',
|
||||
ssl: true
|
||||
},
|
||||
// Print all statements
|
||||
verbose: true,
|
||||
// Always as for confirmation
|
||||
strict: true
|
||||
});
|
||||
233
drizzle/0000_oval_wolverine.sql
Normal file
233
drizzle/0000_oval_wolverine.sql
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
CREATE TABLE IF NOT EXISTS "artists" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(255),
|
||||
"slug" varchar(255),
|
||||
"external_id" integer,
|
||||
"created_at" timestamp with time zone DEFAULT (now(6)),
|
||||
"updated_at" timestamp with time zone DEFAULT (now(6))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "artists_to_games" (
|
||||
"artist_id" varchar(255),
|
||||
"game_id" varchar(255)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "categories" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(255),
|
||||
"slug" varchar(255),
|
||||
"external_id" integer,
|
||||
"created_at" timestamp with time zone DEFAULT (now(6)),
|
||||
"updated_at" timestamp with time zone DEFAULT (now(6))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "categories_to_games" (
|
||||
"category_id" varchar(255),
|
||||
"game_id" varchar(255)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "collection_items" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"collection_id" varchar(255) NOT NULL,
|
||||
"game_id" varchar(255) NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT (now(6)),
|
||||
"updated_at" timestamp with time zone DEFAULT (now(6))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "collections" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"user_id" varchar(255) NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT (now(6)),
|
||||
"updated_at" timestamp with time zone DEFAULT (now(6))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "designers" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(255),
|
||||
"slug" varchar(255),
|
||||
"external_id" integer,
|
||||
"created_at" timestamp with time zone DEFAULT (now(6)),
|
||||
"updated_at" timestamp with time zone DEFAULT (now(6))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "designers_to_games" (
|
||||
"designer_id" varchar(255),
|
||||
"game_id" varchar(255)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "expansions" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"base_game_id" varchar(255) NOT NULL,
|
||||
"game_id" varchar(255) NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT (now(6)),
|
||||
"updated_at" timestamp with time zone DEFAULT (now(6))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "games" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(255),
|
||||
"slug" varchar(255),
|
||||
"description" text,
|
||||
"year_published" integer,
|
||||
"min_players" integer,
|
||||
"max_players" integer,
|
||||
"playtime" integer,
|
||||
"min_playtime" integer,
|
||||
"max_playtime" integer,
|
||||
"min_age" integer,
|
||||
"image_url" varchar(255),
|
||||
"thumb_url" varchar(255),
|
||||
"url" varchar(255),
|
||||
"external_id" integer,
|
||||
"last_sync_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT (now(6)),
|
||||
"updated_at" timestamp with time zone DEFAULT (now(6)),
|
||||
CONSTRAINT "games_external_id_unique" UNIQUE("external_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "mechanics" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(255),
|
||||
"slug" varchar(255),
|
||||
"external_id" integer,
|
||||
"created_at" timestamp with time zone DEFAULT (now(6)),
|
||||
"updated_at" timestamp with time zone DEFAULT (now(6))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "mechanics_to_games" (
|
||||
"mechanic_id" varchar(255),
|
||||
"game_id" varchar(255)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "publishers" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(255),
|
||||
"slug" varchar(255),
|
||||
"external_id" integer,
|
||||
"created_at" timestamp with time zone DEFAULT (now(6)),
|
||||
"updated_at" timestamp with time zone DEFAULT (now(6))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "publishers_to_games" (
|
||||
"publisher_id" varchar(255),
|
||||
"game_id" varchar(255)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "roles" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(255),
|
||||
CONSTRAINT "roles_name_unique" UNIQUE("name")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "sessions" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"user_id" varchar(255) NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL,
|
||||
"ip_country" varchar(255),
|
||||
"ip_address" varchar(255)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "user_roles" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"user_id" varchar(255) NOT NULL,
|
||||
"role_id" varchar(255) NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT (now(6)),
|
||||
"updated_at" timestamp with time zone DEFAULT (now(6))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"username" varchar(255),
|
||||
"hashed_password" varchar(255),
|
||||
"email" varchar(255),
|
||||
"first_name" varchar(255),
|
||||
"last_name" varchar(255),
|
||||
"verified" boolean DEFAULT false,
|
||||
"receive_email" boolean DEFAULT false,
|
||||
"theme" varchar(255) DEFAULT 'system',
|
||||
"created_at" timestamp DEFAULT (now(6)),
|
||||
"updated_at" timestamp DEFAULT (now(6)),
|
||||
CONSTRAINT "users_username_unique" UNIQUE("username"),
|
||||
CONSTRAINT "users_email_unique" UNIQUE("email")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "wishlist_items" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"wishlist_id" varchar(255) NOT NULL,
|
||||
"game_id" varchar(255) NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT (now(6)),
|
||||
"updated_at" timestamp with time zone DEFAULT (now(6))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "wishlists" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"user_id" varchar(255) NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT (now(6)),
|
||||
"updated_at" timestamp with time zone DEFAULT (now(6))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "collection_items" ADD CONSTRAINT "collection_items_collection_id_collections_id_fk" FOREIGN KEY ("collection_id") REFERENCES "collections"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "collection_items" ADD CONSTRAINT "collection_items_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "collections" ADD CONSTRAINT "collections_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "expansions" ADD CONSTRAINT "expansions_base_game_id_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "games"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "expansions" ADD CONSTRAINT "expansions_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "roles"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "wishlist_items" ADD CONSTRAINT "wishlist_items_wishlist_id_wishlists_id_fk" FOREIGN KEY ("wishlist_id") REFERENCES "wishlists"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "wishlist_items" ADD CONSTRAINT "wishlist_items_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "wishlists" ADD CONSTRAINT "wishlists_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
51
drizzle/0001_giant_tomorrow_man.sql
Normal file
51
drizzle/0001_giant_tomorrow_man.sql
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
ALTER TABLE "artists" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "artists" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "artists" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "artists" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "categories" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "categories" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "categories" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "categories" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "collection_items" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "collection_items" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "collection_items" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "collection_items" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "collections" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "collections" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "collections" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "collections" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "designers" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "designers" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "designers" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "designers" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "expansions" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "expansions" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "expansions" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "expansions" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "games" ALTER COLUMN "last_sync_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "games" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "games" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "games" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "games" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "mechanics" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "mechanics" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "mechanics" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "mechanics" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "publishers" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "publishers" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "publishers" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "publishers" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "user_roles" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "user_roles" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "user_roles" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "user_roles" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "users" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "users" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "wishlist_items" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "wishlist_items" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "wishlist_items" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "wishlist_items" ALTER COLUMN "updated_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "wishlists" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "wishlists" ALTER COLUMN "created_at" SET DEFAULT (now());--> statement-breakpoint
|
||||
ALTER TABLE "wishlists" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "wishlists" ALTER COLUMN "updated_at" SET DEFAULT (now());
|
||||
27
drizzle/0002_sour_silverclaw.sql
Normal file
27
drizzle/0002_sour_silverclaw.sql
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
ALTER TABLE "artists" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "artists" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "categories" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "categories" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "collection_items" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "collection_items" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "collections" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "collections" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "designers" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "designers" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "expansions" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "expansions" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "games" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "games" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "mechanics" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "mechanics" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "publishers" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "publishers" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "user_roles" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "user_roles" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "users" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "users" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "wishlist_items" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "wishlist_items" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "wishlists" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "wishlists" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "games" ADD COLUMN "text_searchable_index" "tsvector";
|
||||
1
drizzle/0003_thick_tinkerer.sql
Normal file
1
drizzle/0003_thick_tinkerer.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
CREATE INDEX IF NOT EXISTS "text_searchable_idx" ON "games" ("text_searchable_index");
|
||||
30
drizzle/0004_fancy_umar.sql
Normal file
30
drizzle/0004_fancy_umar.sql
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
DO $$ BEGIN
|
||||
CREATE TYPE "external_id_type" AS ENUM('game', 'category', 'mechanic', 'publisher', 'designer', 'artist');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "external_ids" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"type" varchar(255),
|
||||
"external_id" varchar(255)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "game_external_ids" (
|
||||
"game_id" varchar(255) NOT NULL,
|
||||
"external_id" varchar(255) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "games" DROP CONSTRAINT "games_external_id_unique";--> statement-breakpoint
|
||||
ALTER TABLE "games" DROP COLUMN IF EXISTS "external_id";--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "game_external_ids" ADD CONSTRAINT "game_external_ids_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "game_external_ids" ADD CONSTRAINT "game_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "external_ids"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
16
drizzle/0005_uneven_lifeguard.sql
Normal file
16
drizzle/0005_uneven_lifeguard.sql
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
ALTER TABLE "game_external_ids" RENAME TO "games_to_external_ids";--> statement-breakpoint
|
||||
ALTER TABLE "games_to_external_ids" DROP CONSTRAINT "game_external_ids_game_id_games_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "games_to_external_ids" DROP CONSTRAINT "game_external_ids_external_id_external_ids_id_fk";
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "games_to_external_ids" ADD CONSTRAINT "games_to_external_ids_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "games_to_external_ids" ADD CONSTRAINT "games_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "external_ids"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
2
drizzle/0006_light_corsair.sql
Normal file
2
drizzle/0006_light_corsair.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "external_ids" ALTER COLUMN "type" SET NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "external_ids" ALTER COLUMN "external_id" SET NOT NULL;
|
||||
71
drizzle/0007_same_valeria_richards.sql
Normal file
71
drizzle/0007_same_valeria_richards.sql
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
CREATE TABLE IF NOT EXISTS "categories_to_external_ids" (
|
||||
"category_id" varchar(255) NOT NULL,
|
||||
"external_id" varchar(255) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "expansions_to_external_ids" (
|
||||
"expansion_id" varchar(255) NOT NULL,
|
||||
"external_id" varchar(255) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "mechanics_to_external_ids" (
|
||||
"mechanic_id" varchar(255) NOT NULL,
|
||||
"external_id" varchar(255) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "publishers_to_external_ids" (
|
||||
"publisher_id" varchar(255) NOT NULL,
|
||||
"external_id" varchar(255) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DROP TABLE "artists";--> statement-breakpoint
|
||||
DROP TABLE "artists_to_games";--> statement-breakpoint
|
||||
DROP TABLE "designers";--> statement-breakpoint
|
||||
DROP TABLE "designers_to_games";--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "categories_to_external_ids" ADD CONSTRAINT "categories_to_external_ids_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "categories"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "categories_to_external_ids" ADD CONSTRAINT "categories_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "external_ids"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "expansions_to_external_ids" ADD CONSTRAINT "expansions_to_external_ids_expansion_id_expansions_id_fk" FOREIGN KEY ("expansion_id") REFERENCES "expansions"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "expansions_to_external_ids" ADD CONSTRAINT "expansions_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "external_ids"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "mechanics_to_external_ids" ADD CONSTRAINT "mechanics_to_external_ids_mechanic_id_mechanics_id_fk" FOREIGN KEY ("mechanic_id") REFERENCES "mechanics"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "mechanics_to_external_ids" ADD CONSTRAINT "mechanics_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "external_ids"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "publishers_to_external_ids" ADD CONSTRAINT "publishers_to_external_ids_publisher_id_publishers_id_fk" FOREIGN KEY ("publisher_id") REFERENCES "publishers"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "publishers_to_external_ids" ADD CONSTRAINT "publishers_to_external_ids_external_id_external_ids_id_fk" FOREIGN KEY ("external_id") REFERENCES "external_ids"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
7
drizzle/0008_complete_manta.sql
Normal file
7
drizzle/0008_complete_manta.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
DO $$ BEGIN
|
||||
CREATE TYPE "type" AS ENUM('game', 'category', 'mechanic', 'publisher', 'designer', 'artist');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "external_ids" ALTER COLUMN "type" SET DATA TYPE type;
|
||||
7
drizzle/0009_equal_christian_walker.sql
Normal file
7
drizzle/0009_equal_christian_walker.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
DO $$ BEGIN
|
||||
CREATE TYPE "external_id_type" AS ENUM('game', 'category', 'mechanic', 'publisher', 'designer', 'artist');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "external_ids" ALTER COLUMN "type" SET DATA TYPE external_id_type;
|
||||
1
drizzle/0010_flat_mister_sinister.sql
Normal file
1
drizzle/0010_flat_mister_sinister.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "collection_items" ADD COLUMN "times_played" integer DEFAULT 0;
|
||||
97
drizzle/0011_gigantic_mister_sinister.sql
Normal file
97
drizzle/0011_gigantic_mister_sinister.sql
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
ALTER TABLE "categories_to_external_ids" DROP CONSTRAINT "categories_to_external_ids_category_id_categories_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "expansions" DROP CONSTRAINT "expansions_base_game_id_games_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "expansions_to_external_ids" DROP CONSTRAINT "expansions_to_external_ids_expansion_id_expansions_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "games_to_external_ids" DROP CONSTRAINT "games_to_external_ids_game_id_games_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "mechanics_to_external_ids" DROP CONSTRAINT "mechanics_to_external_ids_mechanic_id_mechanics_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "publishers_to_external_ids" DROP CONSTRAINT "publishers_to_external_ids_publisher_id_publishers_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "categories_to_games" ALTER COLUMN "category_id" SET NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "categories_to_games" ALTER COLUMN "game_id" SET NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "mechanics_to_games" ALTER COLUMN "mechanic_id" SET NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "mechanics_to_games" ALTER COLUMN "game_id" SET NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "publishers_to_games" ALTER COLUMN "publisher_id" SET NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "publishers_to_games" ALTER COLUMN "game_id" SET NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "categories_to_external_ids" ADD CONSTRAINT "categories_to_external_ids_category_id_external_id_pk" PRIMARY KEY("category_id","external_id");--> statement-breakpoint
|
||||
ALTER TABLE "categories_to_games" ADD CONSTRAINT "categories_to_games_category_id_game_id_pk" PRIMARY KEY("category_id","game_id");--> statement-breakpoint
|
||||
ALTER TABLE "expansions_to_external_ids" ADD CONSTRAINT "expansions_to_external_ids_expansion_id_external_id_pk" PRIMARY KEY("expansion_id","external_id");--> statement-breakpoint
|
||||
ALTER TABLE "games_to_external_ids" ADD CONSTRAINT "games_to_external_ids_game_id_external_id_pk" PRIMARY KEY("game_id","external_id");--> statement-breakpoint
|
||||
ALTER TABLE "mechanics_to_external_ids" ADD CONSTRAINT "mechanics_to_external_ids_mechanic_id_external_id_pk" PRIMARY KEY("mechanic_id","external_id");--> statement-breakpoint
|
||||
ALTER TABLE "mechanics_to_games" ADD CONSTRAINT "mechanics_to_games_mechanic_id_game_id_pk" PRIMARY KEY("mechanic_id","game_id");--> statement-breakpoint
|
||||
ALTER TABLE "publishers_to_external_ids" ADD CONSTRAINT "publishers_to_external_ids_publisher_id_external_id_pk" PRIMARY KEY("publisher_id","external_id");--> statement-breakpoint
|
||||
ALTER TABLE "publishers_to_games" ADD CONSTRAINT "publishers_to_games_publisher_id_game_id_pk" PRIMARY KEY("publisher_id","game_id");--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "categories_to_external_ids" ADD CONSTRAINT "categories_to_external_ids_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "categories"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "categories_to_games" ADD CONSTRAINT "categories_to_games_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "categories"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "categories_to_games" ADD CONSTRAINT "categories_to_games_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "expansions" ADD CONSTRAINT "expansions_base_game_id_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "games"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "expansions_to_external_ids" ADD CONSTRAINT "expansions_to_external_ids_expansion_id_expansions_id_fk" FOREIGN KEY ("expansion_id") REFERENCES "expansions"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "games_to_external_ids" ADD CONSTRAINT "games_to_external_ids_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "mechanics_to_external_ids" ADD CONSTRAINT "mechanics_to_external_ids_mechanic_id_mechanics_id_fk" FOREIGN KEY ("mechanic_id") REFERENCES "mechanics"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "mechanics_to_games" ADD CONSTRAINT "mechanics_to_games_mechanic_id_mechanics_id_fk" FOREIGN KEY ("mechanic_id") REFERENCES "mechanics"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "mechanics_to_games" ADD CONSTRAINT "mechanics_to_games_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "publishers_to_external_ids" ADD CONSTRAINT "publishers_to_external_ids_publisher_id_publishers_id_fk" FOREIGN KEY ("publisher_id") REFERENCES "publishers"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "publishers_to_games" ADD CONSTRAINT "publishers_to_games_publisher_id_publishers_id_fk" FOREIGN KEY ("publisher_id") REFERENCES "publishers"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "publishers_to_games" ADD CONSTRAINT "publishers_to_games_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "games"("id") ON DELETE restrict ON UPDATE cascade;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
2
drizzle/0012_dizzy_lethal_legion.sql
Normal file
2
drizzle/0012_dizzy_lethal_legion.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "users" ALTER COLUMN "created_at" SET DATA TYPE timestamp (6) with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "users" ALTER COLUMN "updated_at" SET DATA TYPE timestamp (6) with time zone;
|
||||
3
drizzle/0013_clever_monster_badoon.sql
Normal file
3
drizzle/0013_clever_monster_badoon.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE "categories" DROP COLUMN IF EXISTS "external_id";--> statement-breakpoint
|
||||
ALTER TABLE "mechanics" DROP COLUMN IF EXISTS "external_id";--> statement-breakpoint
|
||||
ALTER TABLE "publishers" DROP COLUMN IF EXISTS "external_id";
|
||||
1
drizzle/0014_organic_morlocks.sql
Normal file
1
drizzle/0014_organic_morlocks.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE "expansions_to_external_ids";
|
||||
12
drizzle/0015_awesome_gabe_jones.sql
Normal file
12
drizzle/0015_awesome_gabe_jones.sql
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
CREATE TABLE IF NOT EXISTS "password_reset_tokens" (
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"user_id" varchar(255) NOT NULL,
|
||||
"expires_at" timestamp (6) with time zone,
|
||||
"created_at" timestamp (6) with time zone DEFAULT now()
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "password_reset_tokens" ADD CONSTRAINT "password_reset_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
1049
drizzle/meta/0000_snapshot.json
Normal file
1049
drizzle/meta/0000_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1049
drizzle/meta/0001_snapshot.json
Normal file
1049
drizzle/meta/0001_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1055
drizzle/meta/0002_snapshot.json
Normal file
1055
drizzle/meta/0002_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1063
drizzle/meta/0003_snapshot.json
Normal file
1063
drizzle/meta/0003_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1138
drizzle/meta/0004_snapshot.json
Normal file
1138
drizzle/meta/0004_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1138
drizzle/meta/0005_snapshot.json
Normal file
1138
drizzle/meta/0005_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1138
drizzle/meta/0006_snapshot.json
Normal file
1138
drizzle/meta/0006_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1194
drizzle/meta/0007_snapshot.json
Normal file
1194
drizzle/meta/0007_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1194
drizzle/meta/0008_snapshot.json
Normal file
1194
drizzle/meta/0008_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1194
drizzle/meta/0009_snapshot.json
Normal file
1194
drizzle/meta/0009_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1201
drizzle/meta/0010_snapshot.json
Normal file
1201
drizzle/meta/0010_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1346
drizzle/meta/0011_snapshot.json
Normal file
1346
drizzle/meta/0011_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1346
drizzle/meta/0012_snapshot.json
Normal file
1346
drizzle/meta/0012_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1328
drizzle/meta/0013_snapshot.json
Normal file
1328
drizzle/meta/0013_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1271
drizzle/meta/0014_snapshot.json
Normal file
1271
drizzle/meta/0014_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1320
drizzle/meta/0015_snapshot.json
Normal file
1320
drizzle/meta/0015_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
118
drizzle/meta/_journal.json
Normal file
118
drizzle/meta/_journal.json
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
{
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "5",
|
||||
"when": 1707437865821,
|
||||
"tag": "0000_oval_wolverine",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "5",
|
||||
"when": 1707438055782,
|
||||
"tag": "0001_giant_tomorrow_man",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "5",
|
||||
"when": 1707524139123,
|
||||
"tag": "0002_sour_silverclaw",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "5",
|
||||
"when": 1707526808124,
|
||||
"tag": "0003_thick_tinkerer",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "5",
|
||||
"when": 1707932397672,
|
||||
"tag": "0004_fancy_umar",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "5",
|
||||
"when": 1707932466413,
|
||||
"tag": "0005_uneven_lifeguard",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "5",
|
||||
"when": 1707932522909,
|
||||
"tag": "0006_light_corsair",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 7,
|
||||
"version": "5",
|
||||
"when": 1707951501716,
|
||||
"tag": "0007_same_valeria_richards",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 8,
|
||||
"version": "5",
|
||||
"when": 1708105454143,
|
||||
"tag": "0008_complete_manta",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "5",
|
||||
"when": 1708105890146,
|
||||
"tag": "0009_equal_christian_walker",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 10,
|
||||
"version": "5",
|
||||
"when": 1708243232524,
|
||||
"tag": "0010_flat_mister_sinister",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 11,
|
||||
"version": "5",
|
||||
"when": 1708330668971,
|
||||
"tag": "0011_gigantic_mister_sinister",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 12,
|
||||
"version": "5",
|
||||
"when": 1708330799655,
|
||||
"tag": "0012_dizzy_lethal_legion",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 13,
|
||||
"version": "5",
|
||||
"when": 1708453431550,
|
||||
"tag": "0013_clever_monster_badoon",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 14,
|
||||
"version": "5",
|
||||
"when": 1708479971410,
|
||||
"tag": "0014_organic_morlocks",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 15,
|
||||
"version": "5",
|
||||
"when": 1709344835732,
|
||||
"tag": "0015_awesome_gabe_jones",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
module.exports = {
|
||||
environmentVariables: {
|
||||
'--xsmall-viewport': '480px',
|
||||
'--small-viewport': '640px',
|
||||
'--medium-viewport': '768px',
|
||||
'--large-viewport': '1024px',
|
||||
'--xlarge-viewport': '1280px',
|
||||
'--xxlarge-viewport': '1536px',
|
||||
}
|
||||
}
|
||||
153
package.json
153
package.json
|
|
@ -1,65 +1,124 @@
|
|||
{
|
||||
"name": "boredgame",
|
||||
"version": "0.0.2",
|
||||
"private": "true",
|
||||
"scripts": {
|
||||
"dev": "NODE_OPTIONS=\"--inspect\" vite dev --host",
|
||||
"build": "vite build",
|
||||
"build": "prisma generate && vite build",
|
||||
"package": "svelte-kit package",
|
||||
"preview": "vite preview",
|
||||
"test": "playwright test",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --check --plugin-search-dir=. . && eslint .",
|
||||
"format": "prettier --write --plugin-search-dir=. ."
|
||||
"test:ui": "svelte-kit sync && playwright test --ui",
|
||||
"postinstall": "prisma generate",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"test:unit": "vitest",
|
||||
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||
"format": "prettier --plugin-search-dir . --write .",
|
||||
"site:update": "pnpm update -i -L",
|
||||
"generate": "drizzle-kit generate:pg",
|
||||
"migrate": "tsx ./src/migrate.ts",
|
||||
"seed": "tsx ./src/seed.ts",
|
||||
"push": "drizzle-kit push:pg"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "node --loader ts-node/esm prisma/seed.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.33.0",
|
||||
"@rgossiaux/svelte-headlessui": "1.0.2",
|
||||
"@rgossiaux/svelte-heroicons": "^0.1.2",
|
||||
"@sveltejs/adapter-auto": "^1.0.3",
|
||||
"@sveltejs/adapter-vercel": "^1.0.6",
|
||||
"@sveltejs/kit": "^1.16.2",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/node": "^18.16.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.2",
|
||||
"@typescript-eslint/parser": "^5.59.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^8.40.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-svelte": "^2.27.3",
|
||||
"@melt-ui/pp": "^0.3.0",
|
||||
"@melt-ui/svelte": "^0.75.2",
|
||||
"@playwright/test": "^1.42.0",
|
||||
"@resvg/resvg-js": "^2.6.0",
|
||||
"@sveltejs/adapter-auto": "^3.1.1",
|
||||
"@sveltejs/enhanced-img": "^0.1.8",
|
||||
"@sveltejs/kit": "^2.5.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||
"@types/cookie": "^0.6.0",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/pg": "^8.11.2",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"autoprefixer": "^10.4.18",
|
||||
"dotenv": "^16.4.5",
|
||||
"drizzle-kit": "^0.20.14",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"just-clone": "^6.2.0",
|
||||
"just-debounce-it": "^3.2.0",
|
||||
"postcss": "^8.4.23",
|
||||
"postcss-color-functional-notation": "^4.2.4",
|
||||
"postcss-custom-media": "^9.1.3",
|
||||
"postcss-env-function": "^4.0.6",
|
||||
"postcss-import": "^15.1.0",
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"postcss-media-minmax": "^5.0.0",
|
||||
"postcss-nested": "^6.0.1",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-svelte": "^2.10.0",
|
||||
"sass": "^1.62.1",
|
||||
"svelte": "^3.59.0",
|
||||
"svelte-check": "^2.10.3",
|
||||
"svelte-preprocess": "^4.10.7",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^4.3.5",
|
||||
"vitest": "^0.25.3"
|
||||
"postcss": "^8.4.35",
|
||||
"postcss-import": "^16.0.1",
|
||||
"postcss-load-config": "^5.0.3",
|
||||
"postcss-preset-env": "^9.4.0",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-svelte": "^3.2.2",
|
||||
"prisma": "^5.9.1",
|
||||
"sass": "^1.71.1",
|
||||
"satori": "^0.10.13",
|
||||
"satori-html": "^0.3.2",
|
||||
"svelte": "^4.2.12",
|
||||
"svelte-check": "^3.6.6",
|
||||
"svelte-meta-tags": "^3.1.1",
|
||||
"svelte-preprocess": "^5.1.3",
|
||||
"svelte-sequential-preprocessor": "^2.0.1",
|
||||
"sveltekit-flash-message": "^2.4.2",
|
||||
"sveltekit-rate-limiter": "^0.4.3",
|
||||
"sveltekit-superforms": "^2.7.0",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.6.1",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.1.5",
|
||||
"vitest": "^1.3.1",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0 || >=20.0.0 <21.0.0",
|
||||
"pnpm": ">=8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/fira-mono": "^4.5.10",
|
||||
"@leveluptuts/svelte-side-menu": "^1.0.5",
|
||||
"@leveluptuts/svelte-toy": "^2.0.3",
|
||||
"@fontsource/fira-mono": "^5.0.12",
|
||||
"@iconify-icons/line-md": "^1.2.26",
|
||||
"@iconify-icons/mdi": "^1.2.47",
|
||||
"@lucia-auth/adapter-drizzle": "^1.0.2",
|
||||
"@lucia-auth/adapter-prisma": "4.0.0",
|
||||
"@lukeed/uuid": "^2.0.1",
|
||||
"@types/feather-icons": "^4.29.1",
|
||||
"cookie": "^0.5.0",
|
||||
"feather-icons": "^4.29.0",
|
||||
"open-props": "^1.5.8",
|
||||
"@neondatabase/serverless": "^0.9.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@planetscale/database": "^1.16.0",
|
||||
"@prisma/client": "^5.9.1",
|
||||
"@sentry/sveltekit": "^7.100.1",
|
||||
"@sveltejs/adapter-vercel": "^5.1.0",
|
||||
"@types/feather-icons": "^4.29.4",
|
||||
"@vercel/og": "^0.5.20",
|
||||
"bits-ui": "^0.19.3",
|
||||
"boardgamegeekclient": "^1.9.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"cookie": "^0.6.0",
|
||||
"drizzle-orm": "^0.29.4",
|
||||
"feather-icons": "^4.29.1",
|
||||
"formsnap": "^0.5.1",
|
||||
"html-entities": "^2.5.2",
|
||||
"iconify-icon": "^2.0.0",
|
||||
"just-kebab-case": "^4.2.0",
|
||||
"loader": "^2.1.1",
|
||||
"lucia": "3.0.1",
|
||||
"lucide-svelte": "^0.344.0",
|
||||
"mysql2": "^3.9.2",
|
||||
"nanoid": "^5.0.6",
|
||||
"open-props": "^1.6.20",
|
||||
"oslo": "^1.1.3",
|
||||
"pg": "^8.11.3",
|
||||
"postgres": "^3.4.3",
|
||||
"radix-svelte": "^0.9.0",
|
||||
"svelte-french-toast": "^1.2.0",
|
||||
"svelte-lazy-loader": "^1.0.0",
|
||||
"zod": "^3.21.4",
|
||||
"zod-to-json-schema": "^3.21.0"
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwind-variants": "^0.2.0",
|
||||
"tailwindcss-animate": "^1.0.6",
|
||||
"zod-to-json-schema": "^3.22.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,9 @@ const config: PlaywrightTestConfig = {
|
|||
webServer: {
|
||||
command: 'npm run build && npm run preview',
|
||||
port: 4173
|
||||
}
|
||||
},
|
||||
testDir: 'tests',
|
||||
testMatch: /(.+\.)?(test|spec)\.[jt]s/
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
6911
pnpm-lock.yaml
6911
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -1,19 +1,23 @@
|
|||
const tailwindcss = require("tailwindcss");
|
||||
const tailwindNesting = require('tailwindcss/nesting');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const postcssMediaMinmax = require('postcss-media-minmax');
|
||||
const customMedia = require('postcss-custom-media');
|
||||
const postcssPresetEnv = require('postcss-preset-env');
|
||||
const atImport = require('postcss-import');
|
||||
const postcssNested = require('postcss-nested');
|
||||
const postcssEnvFunction = require('postcss-env-function');
|
||||
|
||||
const config = {
|
||||
plugins: [
|
||||
autoprefixer(),
|
||||
postcssMediaMinmax,
|
||||
customMedia,
|
||||
atImport(),
|
||||
postcssNested,
|
||||
postcssEnvFunction(),
|
||||
]
|
||||
plugins: [
|
||||
atImport(),
|
||||
tailwindNesting(),
|
||||
tailwindcss(),
|
||||
postcssPresetEnv({
|
||||
stage: 3,
|
||||
features: {
|
||||
'nesting-rules': false,
|
||||
'custom-media-queries': true,
|
||||
'media-query-ranges': true
|
||||
}
|
||||
}),
|
||||
] //Some plugins, like tailwindcss/nesting, need to run before Tailwind, tailwindcss(), //But others, like autoprefixer, need to run after, autoprefixer]
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
|
|
|||
256
prisma/categories.json
Normal file
256
prisma/categories.json
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
{
|
||||
"categories": [
|
||||
{
|
||||
"name": "Abstract Strategy"
|
||||
},
|
||||
{
|
||||
"name": "Action / Dexterity"
|
||||
},
|
||||
{
|
||||
"name": "Adventure"
|
||||
},
|
||||
{
|
||||
"name": "Age of Reason"
|
||||
},
|
||||
{
|
||||
"name": "American Civil War"
|
||||
},
|
||||
{
|
||||
"name": "American Indian Wars"
|
||||
},
|
||||
{
|
||||
"name": "American Revolutionary War"
|
||||
},
|
||||
{
|
||||
"name": "American West"
|
||||
},
|
||||
{
|
||||
"name": "Ancient"
|
||||
},
|
||||
{
|
||||
"name": "Animals"
|
||||
},
|
||||
{
|
||||
"name": "Arabian"
|
||||
},
|
||||
{
|
||||
"name": "Aviation / Flight"
|
||||
},
|
||||
{
|
||||
"name": "Bluffing"
|
||||
},
|
||||
{
|
||||
"name": "Book"
|
||||
},
|
||||
{
|
||||
"name": "Card Game"
|
||||
},
|
||||
{
|
||||
"name": "Children's Game"
|
||||
},
|
||||
{
|
||||
"name": "City Building"
|
||||
},
|
||||
{
|
||||
"name": "Civil War"
|
||||
},
|
||||
{
|
||||
"name": "Civilization"
|
||||
},
|
||||
{
|
||||
"name": "Collectible Components"
|
||||
},
|
||||
{
|
||||
"name": "Comic Book / Strip"
|
||||
},
|
||||
{
|
||||
"name": "Deduction"
|
||||
},
|
||||
{
|
||||
"name": "Dice"
|
||||
},
|
||||
{
|
||||
"name": "Economic"
|
||||
},
|
||||
{
|
||||
"name": "Educational"
|
||||
},
|
||||
{
|
||||
"name": "Electronic"
|
||||
},
|
||||
{
|
||||
"name": "Environmental"
|
||||
},
|
||||
{
|
||||
"name": "Expansion for Base-game"
|
||||
},
|
||||
{
|
||||
"name": "Exploration"
|
||||
},
|
||||
{
|
||||
"name": "Fan Expansion"
|
||||
},
|
||||
{
|
||||
"name": "Fantasy"
|
||||
},
|
||||
{
|
||||
"name": "Farming"
|
||||
},
|
||||
{
|
||||
"name": "Fighting"
|
||||
},
|
||||
{
|
||||
"name": "Game System"
|
||||
},
|
||||
{
|
||||
"name": "Horror"
|
||||
},
|
||||
{
|
||||
"name": "Humor"
|
||||
},
|
||||
{
|
||||
"name": "Industry / Manufacturing"
|
||||
},
|
||||
{
|
||||
"name": "Korean War"
|
||||
},
|
||||
{
|
||||
"name": "Mafia"
|
||||
},
|
||||
{
|
||||
"name": "Math"
|
||||
},
|
||||
{
|
||||
"name": "Mature / Adult"
|
||||
},
|
||||
{
|
||||
"name": "Maze"
|
||||
},
|
||||
{
|
||||
"name": "Medical"
|
||||
},
|
||||
{
|
||||
"name": "Medieval"
|
||||
},
|
||||
{
|
||||
"name": "Memory"
|
||||
},
|
||||
{
|
||||
"name": "Miniatures"
|
||||
},
|
||||
{
|
||||
"name": "Modern Warefare"
|
||||
},
|
||||
{
|
||||
"name": "Movies / TV / Radio Theme"
|
||||
},
|
||||
{
|
||||
"name": "Murder/Mystery"
|
||||
},
|
||||
{
|
||||
"name": "Music"
|
||||
},
|
||||
{
|
||||
"name": "Mythology"
|
||||
},
|
||||
{
|
||||
"name": "Napoleonic"
|
||||
},
|
||||
{
|
||||
"name": "Nautical"
|
||||
},
|
||||
{
|
||||
"name": "Negotiation"
|
||||
},
|
||||
{
|
||||
"name": "Novel-based"
|
||||
},
|
||||
{
|
||||
"name": "Number"
|
||||
},
|
||||
{
|
||||
"name": "Party Game"
|
||||
},
|
||||
{
|
||||
"name": "Pike and Shot"
|
||||
},
|
||||
{
|
||||
"name": "Pirates"
|
||||
},
|
||||
{
|
||||
"name": "Political"
|
||||
},
|
||||
{
|
||||
"name": "Post-Napoleonic"
|
||||
},
|
||||
{
|
||||
"name": "Prehistoric"
|
||||
},
|
||||
{
|
||||
"name": "Print & Play"
|
||||
},
|
||||
{
|
||||
"name": "Puzzle"
|
||||
},
|
||||
{
|
||||
"name": "Racing"
|
||||
},
|
||||
{
|
||||
"name": "Real-time"
|
||||
},
|
||||
{
|
||||
"name": "Religious"
|
||||
},
|
||||
{
|
||||
"name": "Renaissance"
|
||||
},
|
||||
{
|
||||
"name": "Science Fiction"
|
||||
},
|
||||
{
|
||||
"name": "Space Exploration"
|
||||
},
|
||||
{
|
||||
"name": "Spies/Secret Agents"
|
||||
},
|
||||
{
|
||||
"name": "Sports"
|
||||
},
|
||||
{
|
||||
"name": "Territory Building"
|
||||
},
|
||||
{
|
||||
"name": "Trains"
|
||||
},
|
||||
{
|
||||
"name": "Transportation"
|
||||
},
|
||||
{
|
||||
"name": "Travel"
|
||||
},
|
||||
{
|
||||
"name": "Trivia"
|
||||
},
|
||||
{
|
||||
"name": "Video Game Theme"
|
||||
},
|
||||
{
|
||||
"name": "Vietnam War"
|
||||
},
|
||||
{
|
||||
"name": "Wargame"
|
||||
},
|
||||
{
|
||||
"name": "Word Game"
|
||||
},
|
||||
{
|
||||
"name": "World War I"
|
||||
},
|
||||
{
|
||||
"name": "World War II"
|
||||
},
|
||||
{
|
||||
"name": "Zombies"
|
||||
}
|
||||
]
|
||||
}
|
||||
577
prisma/mechanics.json
Normal file
577
prisma/mechanics.json
Normal file
|
|
@ -0,0 +1,577 @@
|
|||
{
|
||||
"mechanics": [
|
||||
{
|
||||
"name": "Acting"
|
||||
},
|
||||
{
|
||||
"name": "Action Drafting"
|
||||
},
|
||||
{
|
||||
"name": "Action Points"
|
||||
},
|
||||
{
|
||||
"name": "Action Queue"
|
||||
},
|
||||
{
|
||||
"name": "Action Retrieval"
|
||||
},
|
||||
{
|
||||
"name": "Action Timer"
|
||||
},
|
||||
{
|
||||
"name": "Action/Event"
|
||||
},
|
||||
{
|
||||
"name": "Advantage Token"
|
||||
},
|
||||
{
|
||||
"name": "Alliances"
|
||||
},
|
||||
{
|
||||
"name": "Area Majority / Influence"
|
||||
},
|
||||
{
|
||||
"name": "Area Movement"
|
||||
},
|
||||
{
|
||||
"name": "Area-Impulse"
|
||||
},
|
||||
{
|
||||
"name": "Auction Compensation"
|
||||
},
|
||||
{
|
||||
"name": "Auction: Dexterity"
|
||||
},
|
||||
{
|
||||
"name": "Auction: Dutch"
|
||||
},
|
||||
{
|
||||
"name": "Auction: Dutch Priority"
|
||||
},
|
||||
{
|
||||
"name": "Auction: English"
|
||||
},
|
||||
{
|
||||
"name": "Auction: Fixed Placement"
|
||||
},
|
||||
{
|
||||
"name": "Auction: Multiple Lot"
|
||||
},
|
||||
{
|
||||
"name": "Auction: Once Around"
|
||||
},
|
||||
{
|
||||
"name": "Auction: Sealed Bid"
|
||||
},
|
||||
{
|
||||
"name": "Auction: Turn Order Until Pass"
|
||||
},
|
||||
{
|
||||
"name": "Auction/Bidding"
|
||||
},
|
||||
{
|
||||
"name": "Automatic Resource Growth"
|
||||
},
|
||||
{
|
||||
"name": "Betting and Bluffing"
|
||||
},
|
||||
{
|
||||
"name": "Bias"
|
||||
},
|
||||
{
|
||||
"name": "Bids As Wagers"
|
||||
},
|
||||
{
|
||||
"name": "Bingo"
|
||||
},
|
||||
{
|
||||
"name": "Bribery"
|
||||
},
|
||||
{
|
||||
"name": "Campaign / Battle Card Driven"
|
||||
},
|
||||
{
|
||||
"name": "Card Play Conflict Resolution"
|
||||
},
|
||||
{
|
||||
"name": "Catch the Leader"
|
||||
},
|
||||
{
|
||||
"name": "Chaining"
|
||||
},
|
||||
{
|
||||
"name": "Chit-Pull System"
|
||||
},
|
||||
{
|
||||
"name": "Closed Drafting"
|
||||
},
|
||||
{
|
||||
"name": "Closed Economy Auction"
|
||||
},
|
||||
{
|
||||
"name": "Command Cards"
|
||||
},
|
||||
{
|
||||
"name": "Commodity Speculation"
|
||||
},
|
||||
{
|
||||
"name": "Communication Limits"
|
||||
},
|
||||
{
|
||||
"name": "Connections"
|
||||
},
|
||||
{
|
||||
"name": "Constrained Bidding"
|
||||
},
|
||||
{
|
||||
"name": "Contracts"
|
||||
},
|
||||
{
|
||||
"name": "Cooperative Game"
|
||||
},
|
||||
{
|
||||
"name": "Crayon Rail System"
|
||||
},
|
||||
{
|
||||
"name": "Critical Hits and Failures"
|
||||
},
|
||||
{
|
||||
"name": "Cube Tower"
|
||||
},
|
||||
{
|
||||
"name": "Deck Construction"
|
||||
},
|
||||
{
|
||||
"name": "Deck, Bag, and Pool Building"
|
||||
},
|
||||
{
|
||||
"name": "Deduction"
|
||||
},
|
||||
{
|
||||
"name": "Delayed Purchase"
|
||||
},
|
||||
{
|
||||
"name": "Dice Rolling"
|
||||
},
|
||||
{
|
||||
"name": "Die Icon Resolution"
|
||||
},
|
||||
{
|
||||
"name": "Different Dice Movement"
|
||||
},
|
||||
{
|
||||
"name": "Drawing"
|
||||
},
|
||||
{
|
||||
"name": "Elapsed Real Time Ending"
|
||||
},
|
||||
{
|
||||
"name": "Enclosure"
|
||||
},
|
||||
{
|
||||
"name": "End Game Bonuses"
|
||||
},
|
||||
{
|
||||
"name": "Events"
|
||||
},
|
||||
{
|
||||
"name": "Finale Ending"
|
||||
},
|
||||
{
|
||||
"name": "Flicking"
|
||||
},
|
||||
{
|
||||
"name": "Follow"
|
||||
},
|
||||
{
|
||||
"name": "Force Commitment"
|
||||
},
|
||||
{
|
||||
"name": "Grid Coverage"
|
||||
},
|
||||
{
|
||||
"name": "Grid Movement"
|
||||
},
|
||||
{
|
||||
"name": "Hand Management"
|
||||
},
|
||||
{
|
||||
"name": "Hexagon Grid"
|
||||
},
|
||||
{
|
||||
"name": "Hidden Movement"
|
||||
},
|
||||
{
|
||||
"name": "Hidden Roles"
|
||||
},
|
||||
{
|
||||
"name": "Hidden Victory Points"
|
||||
},
|
||||
{
|
||||
"name": "Highest-Lowest Scoring"
|
||||
},
|
||||
{
|
||||
"name": "Hot Potato"
|
||||
},
|
||||
{
|
||||
"name": "I Cut, You Choose"
|
||||
},
|
||||
{
|
||||
"name": "Impulse Movement"
|
||||
},
|
||||
{
|
||||
"name": "Income"
|
||||
},
|
||||
{
|
||||
"name": "Increase Value of Unchosen Resources"
|
||||
},
|
||||
{
|
||||
"name": "Induction"
|
||||
},
|
||||
{
|
||||
"name": "Interrupts"
|
||||
},
|
||||
{
|
||||
"name": "Investment"
|
||||
},
|
||||
{
|
||||
"name": "Kill Steal"
|
||||
},
|
||||
{
|
||||
"name": "King of the Hill"
|
||||
},
|
||||
{
|
||||
"name": "Ladder Climbing"
|
||||
},
|
||||
{
|
||||
"name": "Layering"
|
||||
},
|
||||
{
|
||||
"name": "Legacy Game"
|
||||
},
|
||||
{
|
||||
"name": "Line Drawing"
|
||||
},
|
||||
{
|
||||
"name": "Line of Sight"
|
||||
},
|
||||
{
|
||||
"name": "Loans"
|
||||
},
|
||||
{
|
||||
"name": "Lose a Turn"
|
||||
},
|
||||
{
|
||||
"name": "Mancala"
|
||||
},
|
||||
{
|
||||
"name": "Map Addition"
|
||||
},
|
||||
{
|
||||
"name": "Map Deformation"
|
||||
},
|
||||
{
|
||||
"name": "Map Reduction"
|
||||
},
|
||||
{
|
||||
"name": "Market"
|
||||
},
|
||||
{
|
||||
"name": "Matching"
|
||||
},
|
||||
{
|
||||
"name": "Measurement Movement"
|
||||
},
|
||||
{
|
||||
"name": "Melding and Splaying"
|
||||
},
|
||||
{
|
||||
"name": "Memory"
|
||||
},
|
||||
{
|
||||
"name": "Minimap Resolution"
|
||||
},
|
||||
{
|
||||
"name": "Modular Board"
|
||||
},
|
||||
{
|
||||
"name": "Move Through Deck"
|
||||
},
|
||||
{
|
||||
"name": "Movement Points"
|
||||
},
|
||||
{
|
||||
"name": "Movement Template"
|
||||
},
|
||||
{
|
||||
"name": "Moving Multiple Units"
|
||||
},
|
||||
{
|
||||
"name": "Multi-Use Cards"
|
||||
},
|
||||
{
|
||||
"name": "Multiple Maps"
|
||||
},
|
||||
{
|
||||
"name": "Narrative Choice / Paragraph"
|
||||
},
|
||||
{
|
||||
"name": "Negotiation"
|
||||
},
|
||||
{
|
||||
"name": "Neighbor Scope"
|
||||
},
|
||||
{
|
||||
"name": "Network and Route Building"
|
||||
},
|
||||
{
|
||||
"name": "Once-Per-Game Abilities"
|
||||
},
|
||||
{
|
||||
"name": "Open Drafting"
|
||||
},
|
||||
{
|
||||
"name": "Order Counters"
|
||||
},
|
||||
{
|
||||
"name": "Ordering"
|
||||
},
|
||||
{
|
||||
"name": "Ownership"
|
||||
},
|
||||
{
|
||||
"name": "Paper-and-Pencil"
|
||||
},
|
||||
{
|
||||
"name": "Passed Action Token"
|
||||
},
|
||||
{
|
||||
"name": "Pattern Building"
|
||||
},
|
||||
{
|
||||
"name": "Pattern Movement"
|
||||
},
|
||||
{
|
||||
"name": "Pattern Recognition"
|
||||
},
|
||||
{
|
||||
"name": "Physical Removal"
|
||||
},
|
||||
{
|
||||
"name": "Pick-up and Deliver"
|
||||
},
|
||||
{
|
||||
"name": "Pieces as Map"
|
||||
},
|
||||
{
|
||||
"name": "Player Elimination"
|
||||
},
|
||||
{
|
||||
"name": "Player Judge"
|
||||
},
|
||||
{
|
||||
"name": "Point to Point Movement"
|
||||
},
|
||||
{
|
||||
"name": "Predictive Bid"
|
||||
},
|
||||
{
|
||||
"name": "Prisoner's Dilemma"
|
||||
},
|
||||
{
|
||||
"name": "Programmed Movement"
|
||||
},
|
||||
{
|
||||
"name": "Push Your Luck"
|
||||
},
|
||||
{
|
||||
"name": "Questions and Answers"
|
||||
},
|
||||
{
|
||||
"name": "Race"
|
||||
},
|
||||
{
|
||||
"name": "Random Production"
|
||||
},
|
||||
{
|
||||
"name": "Ratio / Combat Results Table"
|
||||
},
|
||||
{
|
||||
"name": "Re-rolling and Locking"
|
||||
},
|
||||
{
|
||||
"name": "Real-Time"
|
||||
},
|
||||
{
|
||||
"name": "Relative Movement"
|
||||
},
|
||||
{
|
||||
"name": "Resource Queue"
|
||||
},
|
||||
{
|
||||
"name": "Resource to Move"
|
||||
},
|
||||
{
|
||||
"name": "Rock-Paper-Scissors"
|
||||
},
|
||||
{
|
||||
"name": "Role Playing"
|
||||
},
|
||||
{
|
||||
"name": "Roles with Asymmetric Information"
|
||||
},
|
||||
{
|
||||
"name": "Roll / Spin and Move"
|
||||
},
|
||||
{
|
||||
"name": "Rondel"
|
||||
},
|
||||
{
|
||||
"name": "Scenario / Mission / Campaign Game"
|
||||
},
|
||||
{
|
||||
"name": "Score-and-Reset Game"
|
||||
},
|
||||
{
|
||||
"name": "Secret Unit Deployment"
|
||||
},
|
||||
{
|
||||
"name": "Selection Order Bid"
|
||||
},
|
||||
{
|
||||
"name": "Semi-Cooperative Game"
|
||||
},
|
||||
{
|
||||
"name": "Set Collection"
|
||||
},
|
||||
{
|
||||
"name": "Simulation"
|
||||
},
|
||||
{
|
||||
"name": "Simultaneous Action Selection"
|
||||
},
|
||||
{
|
||||
"name": "Singing"
|
||||
},
|
||||
{
|
||||
"name": "Single Loser Game"
|
||||
},
|
||||
{
|
||||
"name": "Slide/Push"
|
||||
},
|
||||
{
|
||||
"name": "Solo / Solitaire Game"
|
||||
},
|
||||
{
|
||||
"name": "Speed Matching"
|
||||
},
|
||||
{
|
||||
"name": "Square Grid"
|
||||
},
|
||||
{
|
||||
"name": "Stacking and Balancing"
|
||||
},
|
||||
{
|
||||
"name": "Stat Check Resolution"
|
||||
},
|
||||
{
|
||||
"name": "Static Capture"
|
||||
},
|
||||
{
|
||||
"name": "Stock Holding"
|
||||
},
|
||||
{
|
||||
"name": "Storytelling"
|
||||
},
|
||||
{
|
||||
"name": "Sudden Death Ending"
|
||||
},
|
||||
{
|
||||
"name": "Tags"
|
||||
},
|
||||
{
|
||||
"name": "Take That"
|
||||
},
|
||||
{
|
||||
"name": "Targeted Clues"
|
||||
},
|
||||
{
|
||||
"name": "Team-Based Game"
|
||||
},
|
||||
{
|
||||
"name": "Tech Trees / Tech Tracks"
|
||||
},
|
||||
{
|
||||
"name": "Three Dimensional Movement"
|
||||
},
|
||||
{
|
||||
"name": "Tile Placement"
|
||||
},
|
||||
{
|
||||
"name": "Track Movement"
|
||||
},
|
||||
{
|
||||
"name": "Trading"
|
||||
},
|
||||
{
|
||||
"name": "Traitor Game"
|
||||
},
|
||||
{
|
||||
"name": "Trick-taking"
|
||||
},
|
||||
{
|
||||
"name": "Tug of War"
|
||||
},
|
||||
{
|
||||
"name": "Turn Order: Auction"
|
||||
},
|
||||
{
|
||||
"name": "Turn Order: Claim Action"
|
||||
},
|
||||
{
|
||||
"name": "Turn Order: Pass Order"
|
||||
},
|
||||
{
|
||||
"name": "Turn Order: Progressive"
|
||||
},
|
||||
{
|
||||
"name": "Turn Order: Random"
|
||||
},
|
||||
{
|
||||
"name": "Turn Order: Role Order"
|
||||
},
|
||||
{
|
||||
"name": "Turn Order: Stat-Based"
|
||||
},
|
||||
{
|
||||
"name": "Turn Order: Time Track"
|
||||
},
|
||||
{
|
||||
"name": "Variable Phase Order"
|
||||
},
|
||||
{
|
||||
"name": "Variable Player Powers"
|
||||
},
|
||||
{
|
||||
"name": "Variable Set-up"
|
||||
},
|
||||
{
|
||||
"name": "Victory Points as a Resource"
|
||||
},
|
||||
{
|
||||
"name": "Voting"
|
||||
},
|
||||
{
|
||||
"name": "Worker Placement"
|
||||
},
|
||||
{
|
||||
"name": "Worker Placement with Dice Workers"
|
||||
},
|
||||
{
|
||||
"name": "Worker Placement, Different Worker Types"
|
||||
},
|
||||
{
|
||||
"name": "Zone of Control"
|
||||
}
|
||||
]
|
||||
}
|
||||
272
prisma/schema.prisma
Normal file
272
prisma/schema.prisma
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
previewFeatures = ["fullTextSearch", "fullTextIndex", "relationJoins"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "mysql"
|
||||
url = env("DATABASE_URL")
|
||||
relationMode = "prisma"
|
||||
}
|
||||
|
||||
model Role {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
userRoles UserRole[]
|
||||
|
||||
@@map("roles")
|
||||
}
|
||||
|
||||
model UserRole {
|
||||
id String @id @default(cuid())
|
||||
user User @relation(fields: [user_id], references: [id])
|
||||
user_id String
|
||||
role Role @relation(fields: [role_id], references: [id])
|
||||
role_id String
|
||||
created_at DateTime @default(now()) @db.Timestamp(6)
|
||||
updated_at DateTime @updatedAt @db.Timestamp(6)
|
||||
|
||||
@@unique([user_id, role_id])
|
||||
@@index([user_id])
|
||||
@@index([role_id])
|
||||
@@map("user_roles")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
username String @unique
|
||||
hashed_password String?
|
||||
email String? @unique
|
||||
firstName String?
|
||||
lastName String?
|
||||
roles UserRole[]
|
||||
verified Boolean @default(false)
|
||||
receiveEmail Boolean @default(false)
|
||||
collection Collection?
|
||||
wishlist Wishlist?
|
||||
list List[]
|
||||
theme String @default("system")
|
||||
created_at DateTime @default(now()) @db.Timestamp(6)
|
||||
updated_at DateTime @updatedAt @db.Timestamp(6)
|
||||
sessions Session[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @unique
|
||||
userId String
|
||||
ip_country String
|
||||
ip_address String
|
||||
expiresAt DateTime
|
||||
user User @relation(references: [id], fields: [userId], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
@@map("sessions")
|
||||
}
|
||||
|
||||
model Collection {
|
||||
id String @id @default(cuid())
|
||||
user_id String @unique
|
||||
user User @relation(references: [id], fields: [user_id])
|
||||
items CollectionItem[]
|
||||
|
||||
@@index([user_id])
|
||||
@@map("collections")
|
||||
}
|
||||
|
||||
model CollectionItem {
|
||||
id String @id @default(cuid())
|
||||
collection_id String
|
||||
collection Collection @relation(references: [id], fields: [collection_id], onDelete: Cascade)
|
||||
game_id String @unique
|
||||
game Game @relation(references: [id], fields: [game_id])
|
||||
times_played Int
|
||||
|
||||
@@index([game_id, collection_id])
|
||||
@@index([game_id])
|
||||
@@index([collection_id])
|
||||
@@map("collection_items")
|
||||
}
|
||||
|
||||
model Wishlist {
|
||||
id String @id @default(cuid())
|
||||
user_id String @unique
|
||||
user User @relation(references: [id], fields: [user_id])
|
||||
items WishlistItem[]
|
||||
|
||||
@@index([user_id])
|
||||
@@map("wishlists")
|
||||
}
|
||||
|
||||
model WishlistItem {
|
||||
id String @id @default(cuid())
|
||||
wishlist_id String
|
||||
wishlist Wishlist @relation(references: [id], fields: [wishlist_id], onDelete: Cascade)
|
||||
game_id String @unique
|
||||
game Game @relation(references: [id], fields: [game_id])
|
||||
created_at DateTime @default(now()) @db.Timestamp(6)
|
||||
updated_at DateTime @updatedAt @db.Timestamp(6)
|
||||
|
||||
@@index([game_id, wishlist_id])
|
||||
@@index([game_id])
|
||||
@@index([wishlist_id])
|
||||
@@map("wishlist_items")
|
||||
}
|
||||
|
||||
model List {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
user_id String @unique
|
||||
user User @relation(references: [id], fields: [user_id])
|
||||
items ListItem[]
|
||||
|
||||
@@index([user_id])
|
||||
@@map("lists")
|
||||
}
|
||||
|
||||
model ListItem {
|
||||
id String @id @default(cuid())
|
||||
list_id String
|
||||
list List @relation(references: [id], fields: [list_id], onDelete: Cascade)
|
||||
game_id String @unique
|
||||
game Game @relation(references: [id], fields: [game_id])
|
||||
created_at DateTime @default(now()) @db.Timestamp(6)
|
||||
updated_at DateTime @updatedAt @db.Timestamp(6)
|
||||
|
||||
@@index([game_id, list_id])
|
||||
@@index([game_id])
|
||||
@@index([list_id])
|
||||
@@map("list_items")
|
||||
}
|
||||
|
||||
model Game {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
slug String
|
||||
description String? @db.LongText
|
||||
year_published Int? @db.Year
|
||||
min_players Int?
|
||||
max_players Int?
|
||||
playtime Int?
|
||||
min_playtime Int?
|
||||
max_playtime Int?
|
||||
min_age Int?
|
||||
image_url String?
|
||||
thumb_url String?
|
||||
url String?
|
||||
categories Category[]
|
||||
mechanics Mechanic[]
|
||||
designers Designer[]
|
||||
publishers Publisher[]
|
||||
artists Artist[]
|
||||
names GameName[]
|
||||
expansions Expansion[] @relation("BaseToExpansion")
|
||||
expansion_of Expansion[] @relation("ExpansionToBase")
|
||||
collection_items CollectionItem[]
|
||||
wishlist_items WishlistItem[]
|
||||
list_items ListItem[]
|
||||
external_id Int @unique
|
||||
last_sync_at DateTime? @db.Timestamp(6)
|
||||
created_at DateTime @default(now()) @db.Timestamp(6)
|
||||
updated_at DateTime @updatedAt @db.Timestamp(6)
|
||||
|
||||
@@fulltext([name])
|
||||
@@fulltext([slug])
|
||||
@@map("games")
|
||||
}
|
||||
|
||||
model GameName {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
slug String
|
||||
game_id String
|
||||
game Game @relation(references: [id], fields: [game_id])
|
||||
created_at DateTime @default(now()) @db.Timestamp(6)
|
||||
updated_at DateTime @updatedAt @db.Timestamp(6)
|
||||
|
||||
@@index([game_id])
|
||||
@@map("game_names")
|
||||
}
|
||||
|
||||
model Publisher {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
slug String
|
||||
external_id Int @unique
|
||||
games Game[]
|
||||
created_at DateTime @default(now()) @db.Timestamp(6)
|
||||
updated_at DateTime @updatedAt @db.Timestamp(6)
|
||||
|
||||
@@fulltext([name])
|
||||
@@map("publishers")
|
||||
}
|
||||
|
||||
model Category {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
slug String
|
||||
games Game[]
|
||||
external_id Int @unique
|
||||
created_at DateTime @default(now()) @db.Timestamp(6)
|
||||
updated_at DateTime @updatedAt @db.Timestamp(6)
|
||||
|
||||
@@fulltext([name])
|
||||
@@map("categories")
|
||||
}
|
||||
|
||||
model Mechanic {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
slug String
|
||||
games Game[]
|
||||
external_id Int @unique
|
||||
created_at DateTime @default(now()) @db.Timestamp(6)
|
||||
updated_at DateTime @updatedAt @db.Timestamp(6)
|
||||
|
||||
@@fulltext([name])
|
||||
@@map("mechanics")
|
||||
}
|
||||
|
||||
model Designer {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
slug String
|
||||
external_id Int @unique
|
||||
games Game[]
|
||||
created_at DateTime @default(now()) @db.Timestamp(6)
|
||||
updated_at DateTime @updatedAt @db.Timestamp(6)
|
||||
|
||||
@@fulltext([name])
|
||||
@@map("designers")
|
||||
}
|
||||
|
||||
model Artist {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
slug String @unique
|
||||
external_id Int @unique
|
||||
games Game[]
|
||||
created_at DateTime @default(now()) @db.Timestamp(6)
|
||||
updated_at DateTime @updatedAt @db.Timestamp(6)
|
||||
|
||||
@@fulltext([name])
|
||||
@@map("artists")
|
||||
}
|
||||
|
||||
model Expansion {
|
||||
id String @id @default(cuid())
|
||||
base_game Game @relation(name: "BaseToExpansion", fields: [base_game_id], references: [id])
|
||||
base_game_id String
|
||||
game Game @relation(name: "ExpansionToBase", fields: [game_id], references: [id])
|
||||
game_id String
|
||||
created_at DateTime @default(now()) @db.Timestamp(6)
|
||||
updated_at DateTime @updatedAt @db.Timestamp(6)
|
||||
|
||||
@@index([base_game_id])
|
||||
@@index([game_id])
|
||||
@@map("expansions")
|
||||
}
|
||||
84
prisma/seed.ts
Normal file
84
prisma/seed.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log(`Start seeding ...`);
|
||||
|
||||
console.log('Creating roles ...');
|
||||
const existingRoles = await prisma.role.findMany();
|
||||
if (existingRoles.length === 0) {
|
||||
await prisma.role.createMany({
|
||||
data: [{ name: 'admin' }, { name: 'user' }]
|
||||
});
|
||||
console.log('Roles created.');
|
||||
} else {
|
||||
console.log('Roles already exist. No action taken.');
|
||||
}
|
||||
|
||||
if (!await prisma.publisher.findFirst({
|
||||
where: {
|
||||
external_id: 9999
|
||||
}
|
||||
})) {
|
||||
console.log('Publisher does not exist. Creating...');
|
||||
await prisma.publisher.create({
|
||||
data: {
|
||||
name: 'Unknown',
|
||||
slug: 'unknown',
|
||||
external_id: 9999
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!await prisma.designer.findFirst({
|
||||
where: {
|
||||
external_id: 9999
|
||||
}
|
||||
})) {
|
||||
console.log('Designer does not exist. Creating...');
|
||||
await prisma.designer.create({
|
||||
data: {
|
||||
name: 'Unknown',
|
||||
slug: 'unknown',
|
||||
external_id: 9999
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!await prisma.artist.findFirst({
|
||||
where: {
|
||||
external_id: 9999
|
||||
}
|
||||
})) {
|
||||
console.log('Artist does not exist. Creating...');
|
||||
await prisma.artist.create({
|
||||
data: {
|
||||
name: 'Unknown',
|
||||
slug: 'unknown',
|
||||
external_id: 9999
|
||||
}
|
||||
});
|
||||
}
|
||||
// for (const p of userData) {
|
||||
// const user = await prisma.user.create({
|
||||
// data: {
|
||||
// firstName: p.user.firstName,
|
||||
// lastName: p.user.lastName,
|
||||
// email: p.user.email,
|
||||
// username: p.user.username
|
||||
// }
|
||||
// });
|
||||
// console.log(`Created user with id: ${user.id}`);
|
||||
// }
|
||||
console.log(`Seeding finished.`);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch(async (e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
109
src/app.css
109
src/app.css
|
|
@ -1,109 +0,0 @@
|
|||
@import '@fontsource/fira-mono';
|
||||
|
||||
:root {
|
||||
font-family: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
--font-mono: 'Fira Mono', monospace;
|
||||
--pure-white: #ffffff;
|
||||
--primary-color: #b9c6d2;
|
||||
--secondary-color: #d0dde9;
|
||||
--tertiary-color: #edf0f8;
|
||||
--accent-color: #ff3e00;
|
||||
--heading-color: rgba(0, 0, 0, 0.7);
|
||||
--text-color: #444444;
|
||||
--background-without-opacity: rgba(255, 255, 255, 0.7);
|
||||
--column-width: 42rem;
|
||||
--column-margin-top: 4rem;
|
||||
--z-highest: 100;
|
||||
--cardBorderRadius: 12px;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
background-color: var(--primary-color);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
var(--primary-color) 0%,
|
||||
var(--secondary-color) 10.45%,
|
||||
var(--tertiary-color) 41.35%
|
||||
);
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: '';
|
||||
width: 80vw;
|
||||
height: 100vh;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 10vw;
|
||||
z-index: -1;
|
||||
background: radial-gradient(
|
||||
50% 50% at 50% 50%,
|
||||
var(--pure-white) 0%,
|
||||
rgba(255, 255, 255, 0) 100%
|
||||
);
|
||||
opacity: 0.05;
|
||||
}
|
||||
|
||||
#svelte {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
font-weight: 400;
|
||||
color: var(--heading-color);
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 16px;
|
||||
font-family: var(--font-mono);
|
||||
background-color: rgba(255, 255, 255, 0.45);
|
||||
border-radius: 3px;
|
||||
box-shadow: 2px 2px 6px rgb(255 255 255 / 25%);
|
||||
padding: 0.5em;
|
||||
overflow-x: auto;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@media (min-width: 720px) {
|
||||
h1 {
|
||||
font-size: 2.4rem;
|
||||
}
|
||||
}
|
||||
57
src/app.d.ts
vendored
57
src/app.d.ts
vendored
|
|
@ -1,14 +1,55 @@
|
|||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
declare namespace App {
|
||||
interface Locals {
|
||||
userid: string;
|
||||
|
||||
import type { PrismaClient, User } from '@prisma/client';
|
||||
|
||||
type User = Omit<User, 'created_at' | 'updated_at'>;
|
||||
|
||||
// src/app.d.ts
|
||||
declare global {
|
||||
namespace App {
|
||||
interface PageData {
|
||||
flash?: { type: 'success' | 'error' | 'info'; message: string };
|
||||
}
|
||||
interface Locals {
|
||||
auth: import('lucia').AuthRequest;
|
||||
user: import('lucia').User | null;
|
||||
session: import('lucia').Session | null;
|
||||
prisma: PrismaClient;
|
||||
startTimer: number;
|
||||
ip: string;
|
||||
country: string;
|
||||
error: string;
|
||||
errorId: string;
|
||||
errorStackTrace: string;
|
||||
message: unknown;
|
||||
track: unknown;
|
||||
}
|
||||
interface Error {
|
||||
code?: string;
|
||||
errorId?: string;
|
||||
}
|
||||
}
|
||||
|
||||
// interface PageData {}
|
||||
|
||||
// interface Error {}
|
||||
|
||||
// interface Platform {}
|
||||
interface Document {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
startViewTransition: (callback: any) => void; // Add your custom property/method here
|
||||
}
|
||||
}
|
||||
|
||||
// interface PageData {}
|
||||
// interface Error {}
|
||||
// interface Platform {}
|
||||
|
||||
// /// <reference types="lucia" />
|
||||
// declare global {
|
||||
// namespace Lucia {
|
||||
// type Auth = import('$lib/server/lucia').Auth;
|
||||
// type DatabaseUserAttributes = User;
|
||||
// type DatabaseSessionAttributes = {};
|
||||
// }
|
||||
// }
|
||||
|
||||
// THIS IS IMPORTANT!!!
|
||||
export {};
|
||||
|
|
|
|||
49
src/app.html
49
src/app.html
|
|
@ -1,39 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<meta charset="utf-8" />
|
||||
<meta name="description" content="Bored? Find a game! Bored Game!" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon-bored.png" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon-bored-game.svg" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<script>
|
||||
const htmlElement = document.documentElement;
|
||||
const userTheme = localStorage.theme;
|
||||
const userFont = localStorage.font;
|
||||
// const htmlElement = document.documentElement;
|
||||
// const userTheme = localStorage.theme;
|
||||
// const userFont = localStorage.font;
|
||||
|
||||
const prefersDarkMode = window.matchMedia('prefers-color-scheme: dark').matches;
|
||||
const prefersLightMode = window.matchMedia('prefers-color-scheme: light').matches;
|
||||
// const prefersDarkMode = window.matchMedia('prefers-color-scheme: dark').matches;
|
||||
// const prefersLightMode = window.matchMedia('prefers-color-scheme: light').matches;
|
||||
|
||||
// check if the user set a theme
|
||||
if (userTheme) {
|
||||
htmlElement.dataset.theme = userTheme;
|
||||
}
|
||||
// // check if the user set a theme
|
||||
// if (userTheme) {
|
||||
// htmlElement.dataset.theme = userTheme;
|
||||
// }
|
||||
|
||||
// otherwise check for user preference
|
||||
if (!userTheme && prefersDarkMode) {
|
||||
htmlElement.dataset.theme = '🌛 Night';
|
||||
localStorage.theme = '🌛 Night';
|
||||
}
|
||||
// // otherwise check for user preference
|
||||
// if (!userTheme && prefersDarkMode) {
|
||||
// htmlElement.dataset.theme = '🌛 Night';
|
||||
// localStorage.theme = '🌛 Night';
|
||||
// }
|
||||
|
||||
if (!userTheme && prefersLightMode) {
|
||||
htmlElement.dataset.theme = '☀️ Daylight';
|
||||
localStorage.theme = '☀️ Daylight';
|
||||
}
|
||||
// if (!userTheme && prefersLightMode) {
|
||||
// htmlElement.dataset.theme = '☀️ Daylight';
|
||||
// localStorage.theme = '☀️ Daylight';
|
||||
// }
|
||||
|
||||
// if nothing is set default to dark mode
|
||||
if (!userTheme && !prefersDarkMode && !prefersLightMode) {
|
||||
htmlElement.dataset.theme = '🌛 Night';
|
||||
localStorage.theme = '🌛 Night';
|
||||
}
|
||||
// // if nothing is set default to dark mode
|
||||
// if (!userTheme && !prefersDarkMode && !prefersLightMode) {
|
||||
// htmlElement.dataset.theme = '🌛 Night';
|
||||
// localStorage.theme = '🌛 Night';
|
||||
// }
|
||||
</script>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
|
|
|||
24
src/hooks.client.ts
Normal file
24
src/hooks.client.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// import { dev } from '$app/environment';
|
||||
// import { handleErrorWithSentry, Replay } from '@sentry/sveltekit';
|
||||
// import * as Sentry from '@sentry/sveltekit';
|
||||
|
||||
// TODO: Fix Sentry
|
||||
// Sentry.init({
|
||||
// dsn: 'https://742e43279df93a3c4a4a78c12eb1f879@o4506057768632320.ingest.sentry.io/4506057770401792',
|
||||
// tracesSampleRate: 1.0,
|
||||
|
||||
// // This sets the sample rate to be 10%. You may want this to be 100% while
|
||||
// // in development and sample at a lower rate in production
|
||||
// replaysSessionSampleRate: 0.1,
|
||||
|
||||
// // If the entire session is not sampled, use the below sample rate to sample
|
||||
// // sessions when an error occurs.
|
||||
// replaysOnErrorSampleRate: 1.0,
|
||||
|
||||
// // If you don't want to use Session Replay, just remove the line below:
|
||||
// integrations: [new Replay()],
|
||||
// environment: dev ? 'development' : 'production'
|
||||
// });
|
||||
|
||||
// // If you have a custom error handler, pass it to `handleErrorWithSentry`
|
||||
// export const handleError = handleErrorWithSentry();
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import type { Handle } from '@sveltejs/kit';
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
let userid = event.cookies.get('userid');
|
||||
|
||||
if (!userid) {
|
||||
// if this is the first time the user has visited this app,
|
||||
// set a cookie so that we recognise them when they return
|
||||
userid = crypto.randomUUID();
|
||||
event.cookies.set('userid', userid, { path: '/' });
|
||||
}
|
||||
|
||||
event.locals.userid = userid;
|
||||
|
||||
return resolve(event);
|
||||
};
|
||||
60
src/hooks.server.ts
Normal file
60
src/hooks.server.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
// import * as Sentry from '@sentry/sveltekit';
|
||||
import { sequence } from '@sveltejs/kit/hooks';
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
import { dev } from '$app/environment';
|
||||
import { lucia } from '$lib/server/auth';
|
||||
|
||||
// TODO: Fix Sentry as it is not working on SvelteKit v2
|
||||
// Sentry.init({
|
||||
// dsn: 'https://742e43279df93a3c4a4a78c12eb1f879@o4506057768632320.ingest.sentry.io/4506057770401792',
|
||||
// tracesSampleRate: 1,
|
||||
// environment: dev ? 'development' : 'production',
|
||||
// enabled: !dev
|
||||
// });
|
||||
|
||||
export const authentication: Handle = async function ({ event, resolve }) {
|
||||
const startTimer = Date.now();
|
||||
event.locals.startTimer = startTimer;
|
||||
|
||||
const ip = event.request.headers.get('x-forwarded-for') as string;
|
||||
const country = event.request.headers.get('x-vercel-ip-country') as string;
|
||||
event.locals.ip = dev ? '127.0.0.1' : ip; // || event.getClientAddress();
|
||||
event.locals.country = dev ? 'us' : country;
|
||||
|
||||
const sessionId = event.cookies.get(lucia.sessionCookieName);
|
||||
if (!sessionId) {
|
||||
event.locals.user = null;
|
||||
event.locals.session = null;
|
||||
return resolve(event);
|
||||
}
|
||||
|
||||
const { session, user } = await lucia.validateSession(sessionId);
|
||||
if (session && session.fresh) {
|
||||
const sessionCookie = lucia.createSessionCookie(session.id);
|
||||
console.log('sessionCookie', JSON.stringify(sessionCookie, null, 2));
|
||||
// sveltekit types deviates from the de-facto standard
|
||||
// you can use 'as any' too
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: '.',
|
||||
...sessionCookie.attributes
|
||||
});
|
||||
}
|
||||
if (!session) {
|
||||
const sessionCookie = lucia.createBlankSessionCookie();
|
||||
console.log('blank sessionCookie', JSON.stringify(sessionCookie, null, 2));
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||
path: ".",
|
||||
...sessionCookie.attributes
|
||||
});
|
||||
}
|
||||
event.locals.user = user;
|
||||
event.locals.session = session;
|
||||
|
||||
return resolve(event);
|
||||
};
|
||||
|
||||
export const handle: Handle = sequence(
|
||||
// Sentry.sentryHandle(),
|
||||
authentication
|
||||
);
|
||||
// export const handleError = Sentry.handleErrorWithSentry();
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { spring } from 'svelte/motion';
|
||||
|
||||
let count = 0;
|
||||
|
||||
const displayed_count = spring();
|
||||
$: displayed_count.set(count);
|
||||
$: offset = modulo($displayed_count, 1);
|
||||
|
||||
function modulo(n: number, m: number) {
|
||||
// handle negative numbers
|
||||
return ((n % m) + m) % m;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="counter">
|
||||
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
|
||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||
<path d="M0,0.5 L1,0.5" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="counter-viewport">
|
||||
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
|
||||
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
|
||||
<strong>{Math.floor($displayed_count)}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
|
||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.counter {
|
||||
display: flex;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.counter button {
|
||||
width: 2em;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
touch-action: manipulation;
|
||||
color: var(--text-color);
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.counter button:hover {
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 25%;
|
||||
height: 25%;
|
||||
}
|
||||
|
||||
path {
|
||||
vector-effect: non-scaling-stroke;
|
||||
stroke-width: 2px;
|
||||
stroke: var(--text-color);
|
||||
}
|
||||
|
||||
.counter-viewport {
|
||||
width: 8em;
|
||||
height: 4em;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.counter-viewport strong {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-weight: 400;
|
||||
color: var(--accent-color);
|
||||
font-size: 4rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.counter-digits {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
top: -100%;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
1
src/lib/assets/bored-game.svg
Normal file
1
src/lib/assets/bored-game.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 35 KiB |
112
src/lib/components/AddToList.svelte
Normal file
112
src/lib/components/AddToList.svelte
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
<script lang="ts">
|
||||
import { enhance } from "$app/forms";
|
||||
import { fly } from "svelte/transition";
|
||||
import { createSelect, melt } from "@melt-ui/svelte";
|
||||
import { Check, ChevronDown, MinusCircle, PlusCircle } from "lucide-svelte";
|
||||
import { Button } from '$components/ui/button';
|
||||
import type { Collection, Wishlist } from "@prisma/client";
|
||||
|
||||
export let game_id: string;
|
||||
export let collection: Collection;
|
||||
export let wishlist: Wishlist;
|
||||
export let in_wishlist = false;
|
||||
export let in_collection = false;
|
||||
export let lists = [];
|
||||
|
||||
// const handleChange = ({ curr, next }) => {
|
||||
// console.log({ curr, next });
|
||||
// return next;
|
||||
// }
|
||||
|
||||
// const {
|
||||
// elements: { trigger, menu, option, label, group, groupLabel },
|
||||
// states: { valueLabel, open },
|
||||
// helpers: { isSelected },
|
||||
// } = createSelect({
|
||||
// forceVisible: true,
|
||||
// onValueChange: handleChange
|
||||
// });
|
||||
|
||||
// console.log({ in_collection, in_wishlist });
|
||||
|
||||
// let options: Record<string, string> = {};
|
||||
// let list_of_lists = [];
|
||||
// if (!in_collection) {
|
||||
// options[collection.id] = 'Add to collection';
|
||||
// }
|
||||
// if (!in_wishlist) {
|
||||
// options[wishlist.id] = 'Add to wishlist';
|
||||
// }
|
||||
// lists.forEach((list) => {
|
||||
// if (!list?.in_list) {
|
||||
// options[list.id] = list.name;
|
||||
// }
|
||||
// });
|
||||
</script>
|
||||
|
||||
<div class="flex gap-1">
|
||||
{#if in_wishlist}
|
||||
<form method="POST" action={`/wishlist?/remove`} use:enhance>
|
||||
<input type="hidden" name="id" value={game_id} />
|
||||
<Button class="flex gap-1" variant="destructive" type="submit">
|
||||
<MinusCircle class="square-5" /> Remove from wishlist
|
||||
</Button>
|
||||
</form>
|
||||
{:else}
|
||||
<form method="POST" action='/wishlist?/add' use:enhance>
|
||||
<input type="hidden" name="id" value={game_id} />
|
||||
<Button class="flex gap-1" type="submit">
|
||||
<PlusCircle class="square-5" /> Add to wishlist
|
||||
</Button>
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
{#if in_collection}
|
||||
<form method="POST" action='/collection?/remove' use:enhance>
|
||||
<input type="hidden" name="id" value={game_id} />
|
||||
<Button class="flex gap-1" type="submit" variant="destructive">
|
||||
<MinusCircle class="square-5" /> Remove from collection
|
||||
</Button>
|
||||
</form>
|
||||
{:else}
|
||||
<form method="POST" action='/collection?/add' use:enhance>
|
||||
<input type="hidden" name="id" value={game_id} />
|
||||
<Button class="flex gap-1" type="submit">
|
||||
<PlusCircle class="square-5" /> Add to collection
|
||||
</Button>
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- <div class="flex flex-col gap-1"> -->
|
||||
<!-- svelte-ignore a11y-label-has-associated-control - $label contains the 'for' attribute -->
|
||||
<!-- <button
|
||||
class="flex h-10 min-w-[220px] items-center justify-between rounded-md bg-white px-3 py-2 text-black-500 transition-opacity hover:opacity-90"
|
||||
use:melt={$trigger}
|
||||
aria-label="Wishlist"
|
||||
>
|
||||
{$valueLabel || 'Add to...'}
|
||||
<ChevronDown class="square-5" />
|
||||
</button>
|
||||
{#if $open}
|
||||
<div
|
||||
class="z-10 flex max-h-[360px] flex-col overflow-y-auto rounded-md bg-white p-1 focus:!ring-0"
|
||||
use:melt={$menu}
|
||||
transition:fly={{ duration: 150, y: -5 }}
|
||||
>
|
||||
{#each Object.entries(options) as [key, value]}
|
||||
<div
|
||||
class="flex relative cursor-pointer rounded-md py-1 pl-8 pr-4 text-neutral-800 focus:z-10 focus:text-purple-700 data-[highlighted]:bg-purple-50 data-[selected]:bg-purple-100 data-[highlighted]:text-purple-900 data-[selected]:text-purple-900"
|
||||
use:melt={$option({ value: key, label: value })}
|
||||
>
|
||||
{value}
|
||||
{#if $isSelected(key)}
|
||||
<div class="check">
|
||||
<Check class="square-4" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div> -->
|
||||
27
src/lib/components/Footer.svelte
Normal file
27
src/lib/components/Footer.svelte
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<script>
|
||||
import { PUBLIC_SITE_URL } from "$env/static/public";
|
||||
</script>
|
||||
|
||||
<footer>
|
||||
<p>Bored Game © {new Date().getFullYear()} | Built by <a target="__blank" href="https://bradleyshellnut.com">Bradley Shellnut</a> | {PUBLIC_SITE_URL}</p>
|
||||
</footer>
|
||||
|
||||
<style lang="postcss">
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
footer a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
footer {
|
||||
padding: 40px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
64
src/lib/components/Game.svelte
Normal file
64
src/lib/components/Game.svelte
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<script lang="ts">
|
||||
import type { GameType, SavedGameType } from '$lib/types';
|
||||
import * as Card from "$lib/components/ui/card";
|
||||
import type { CollectionItem } from '@prisma/client';
|
||||
|
||||
export let game: GameType | CollectionItem;
|
||||
export let detailed: boolean = false;
|
||||
export let variant: 'default' | 'compact' = 'default';
|
||||
|
||||
// Naive and assumes description is only on our GameType at the moment
|
||||
function isGameType(game: GameType | SavedGameType): game is GameType {
|
||||
return (game as GameType).description !== undefined;
|
||||
}
|
||||
</script>
|
||||
|
||||
<article class="grid grid-template-cols-2 gap-4">
|
||||
<Card.Root class={variant === 'compact' ? 'game-card-compact' : ''}>
|
||||
<Card.Header>
|
||||
<Card.Title class="game-card-header">
|
||||
<span style:--transition-name="game-name-{game.slug}">
|
||||
{game.name}
|
||||
{#if game?.year_published}
|
||||
({game?.year_published})
|
||||
{/if}
|
||||
</span>
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content class={variant === 'compact' ? 'pt-6' : ''}>
|
||||
<a
|
||||
class="thumbnail"
|
||||
href={`/game/${game.id}`}
|
||||
title={`View ${game.name}`}
|
||||
data-sveltekit-preload-data
|
||||
>
|
||||
<img src={game.thumb_url} alt={`Image of ${game.name}`} loading="lazy" decoding="async" />
|
||||
<div class="game-details">
|
||||
{#if game?.players}
|
||||
<p>Players: {game.players}</p>
|
||||
<p>Time: {game.playtime} minutes</p>
|
||||
{#if isGameType(game) && game?.min_age}
|
||||
<p>Min Age: {game.min_age}</p>
|
||||
{/if}
|
||||
{#if detailed && isGameType(game) && game?.description}
|
||||
<div class="description">{@html game.description}</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</article>
|
||||
|
||||
<style lang="postcss">
|
||||
:global(.game-card-compact) {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
|
||||
.game-card-header {
|
||||
span {
|
||||
view-transition-name: var(--transition-name);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
158
src/lib/components/Header.svelte
Normal file
158
src/lib/components/Header.svelte
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
<script lang="ts">
|
||||
import { applyAction, enhance } from '$app/forms';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { ListChecks, ListTodo, LogOut, User } from 'lucide-svelte';
|
||||
import * as DropdownMenu from "$components/ui/dropdown-menu";
|
||||
import * as Avatar from "$components/ui/avatar";
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import Logo from '$components/logo.svelte';
|
||||
|
||||
export let user: User | null;
|
||||
|
||||
let avatar = user?.username.slice(0, 1).toUpperCase() || '?';
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<div class="corner">
|
||||
<a href="/" title="Home">
|
||||
<div class="logo-image">
|
||||
<Logo />
|
||||
</div>
|
||||
Bored Game
|
||||
</a>
|
||||
</div>
|
||||
<!-- <TextSearch /> -->
|
||||
<nav>
|
||||
{#if user}
|
||||
<a href="/collection" title="Go to your collection" data-sveltekit-preload-data>Collection</a>
|
||||
<a href="/wishlist" title="Go to your wishlist" data-sveltekit-preload-data>Wishlist</a>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<Avatar.Root asChild>
|
||||
<Avatar.Fallback class="text-3xl font-medium text-magnum-700 h-16 w-16 bg-neutral-100">
|
||||
{avatar}
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content>
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>My Account</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<a href="/profile">
|
||||
<DropdownMenu.Item>
|
||||
<User class="mr-2 h-4 w-4" />
|
||||
<span>Profile</span>
|
||||
</DropdownMenu.Item>
|
||||
</a>
|
||||
<a href="/collection">
|
||||
<DropdownMenu.Item>
|
||||
<ListChecks class="mr-2 h-4 w-4" />
|
||||
<span>Collection</span>
|
||||
</DropdownMenu.Item>
|
||||
</a>
|
||||
<a href="/wishlist">
|
||||
<DropdownMenu.Item>
|
||||
<ListTodo class="mr-2 h-4 w-4" />
|
||||
<span>Wishlist</span>
|
||||
</DropdownMenu.Item>
|
||||
</a>
|
||||
<form
|
||||
use:enhance={() => {
|
||||
return async ({ result }) => {
|
||||
console.log(result);
|
||||
if (result.type === 'success' || result.type === 'redirect') {
|
||||
toast.success('Logged Out');
|
||||
} else if (result.type === 'error') {
|
||||
console.log(result);
|
||||
toast.error(`Error: ${result.error.message}`);
|
||||
} else {
|
||||
toast.error(`Something went wrong.`);
|
||||
console.log(result);
|
||||
}
|
||||
await invalidateAll();
|
||||
await applyAction(result);
|
||||
};
|
||||
}}
|
||||
action="/logout"
|
||||
method="POST"
|
||||
>
|
||||
<button type="submit" class="">
|
||||
<DropdownMenu.Item>
|
||||
<div class="flex items-center gap-1">
|
||||
<LogOut class="mr-2 h-4 w-4"/>
|
||||
<span>Sign out</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
</form>
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
{/if}
|
||||
{#if !user}
|
||||
<a href="/login">
|
||||
<span class="flex-auto">Login</span></a
|
||||
>
|
||||
<a href="/sign-up">
|
||||
<span class="flex-auto">Sign Up</span></a
|
||||
>
|
||||
{/if}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<style lang="postcss">
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--containerPadding);
|
||||
font-size: 1.6rem;
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
padding-top: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.corner {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.corner a {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.logo-image {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 1rem;
|
||||
--background: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
nav a {
|
||||
color: var(--heading-color);
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s linear;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -19,5 +19,6 @@
|
|||
grid-template-columns: repeat(2, auto);
|
||||
place-items: center;
|
||||
gap: 0.25rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
9
src/lib/components/WishlistButton.svelte
Normal file
9
src/lib/components/WishlistButton.svelte
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { Button } from '$components/ui/button';
|
||||
</script>
|
||||
|
||||
<Button type="submit">Add to wishlist</Button>
|
||||
|
||||
<style lang="postcss">
|
||||
|
||||
</style>
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
<script lang="ts">
|
||||
export let kind = 'primary';
|
||||
export let size;
|
||||
export let icon = false;
|
||||
export let disabled = false;
|
||||
</script>
|
||||
|
|
@ -9,29 +8,22 @@
|
|||
<slot />
|
||||
</button>
|
||||
|
||||
<style>
|
||||
<style lang="postcss">
|
||||
button {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
border-radius: 10px;
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
min-width: 20rem;
|
||||
max-width: 30rem;
|
||||
min-height: 6.2rem;
|
||||
text-align: start;
|
||||
background-color: var(--color-btn-primary-active);
|
||||
}
|
||||
|
||||
.danger {
|
||||
background-color: var(--warning);
|
||||
}
|
||||
|
||||
.danger:hover {
|
||||
background-color: var(--warning-hover);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, auto);
|
||||
gap: 1rem;
|
||||
@media (min-width: 1000px) {
|
||||
min-width: 23.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition';
|
||||
import {
|
||||
Dialog,
|
||||
DialogDescription,
|
||||
DialogOverlay,
|
||||
DialogTitle
|
||||
} from '@rgossiaux/svelte-headlessui';
|
||||
import { boredState } from '$root/lib/stores/boredState';
|
||||
import { collectionStore } from '$root/lib/stores/collectionStore';
|
||||
// import {
|
||||
// Dialog,
|
||||
// DialogDescription,
|
||||
// DialogOverlay,
|
||||
// DialogTitle
|
||||
// } from '@rgossiaux/svelte-headlessui';
|
||||
import { boredState } from '$lib/stores/boredState';
|
||||
import { collectionStore } from '$lib/stores/collectionStore';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
function clearCollection() {
|
||||
|
|
@ -21,18 +21,18 @@
|
|||
$: isOpen = $boredState?.dialog?.isOpen;
|
||||
</script>
|
||||
|
||||
<Dialog
|
||||
<!-- <Dialog
|
||||
open={isOpen}
|
||||
on:close={() => {
|
||||
boredState.update((n) => ({ ...n, dialog: { isOpen: false } }));
|
||||
}}
|
||||
static
|
||||
>
|
||||
<div transition:fade>
|
||||
<DialogOverlay class="dialog-overlay" />
|
||||
> -->
|
||||
<div transition:fade|global>
|
||||
<!-- <DialogOverlay class="dialog-overlay" /> -->
|
||||
<div class="dialog">
|
||||
<DialogTitle>Clear collection</DialogTitle>
|
||||
<DialogDescription>Are you sure you want to clear your collection?</DialogDescription>
|
||||
<!-- <DialogTitle>Clear collection</DialogTitle> -->
|
||||
<!-- <DialogDescription>Are you sure you want to clear your collection?</DialogDescription> -->
|
||||
|
||||
<div class="dialog-footer">
|
||||
<button class="remove" on:click={clearCollection}>Clear</button>
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
<!-- </Dialog> -->
|
||||
|
||||
<style lang="scss">
|
||||
.dialog {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { browser } from '$app/environment';
|
||||
import { boredState } from '$root/lib/stores/boredState';
|
||||
import { wishlistStore } from '$root/lib/stores/wishlistStore';
|
||||
import { boredState } from '$lib/stores/boredState';
|
||||
import { wishlistStore } from '$lib/stores/wishlistStore';
|
||||
import DefaultDialog from './DefaultDialog.svelte';
|
||||
|
||||
function clearWishlist() {
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
gap: 2rem;
|
||||
margin: 1rem 0;
|
||||
|
||||
button {
|
||||
& button {
|
||||
display: flex;
|
||||
place-content: center;
|
||||
gap: 1rem;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { type SvelteComponent, createEventDispatcher } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import {
|
||||
Dialog,
|
||||
DialogDescription,
|
||||
DialogOverlay,
|
||||
DialogTitle
|
||||
} from '@rgossiaux/svelte-headlessui';
|
||||
import { boredState } from '$root/lib/stores/boredState';
|
||||
// import {
|
||||
// Dialog,
|
||||
// DialogDescription,
|
||||
// DialogOverlay,
|
||||
// DialogTitle
|
||||
// } from '@rgossiaux/svelte-headlessui';
|
||||
import { boredState } from '$lib/stores/boredState';
|
||||
|
||||
export let title: string;
|
||||
export let description: string;
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
export let passive = false;
|
||||
export let primaryButtonText = '';
|
||||
export let primaryButtonDisabled = false;
|
||||
export let primaryButtonIcon: typeof SvelteComponent = undefined;
|
||||
export let primaryButtonIcon: typeof SvelteComponent<any> = undefined;
|
||||
export let primaryButtonIconDescription = '';
|
||||
export let secondaryButtonText = '';
|
||||
|
||||
|
|
@ -25,19 +25,19 @@
|
|||
$: isOpen = $boredState?.dialog?.isOpen;
|
||||
</script>
|
||||
|
||||
<Dialog
|
||||
<!-- <Dialog
|
||||
open={isOpen}
|
||||
on:close={() => {
|
||||
dispatch('close');
|
||||
}}
|
||||
static
|
||||
>
|
||||
<div transition:fade>
|
||||
<DialogOverlay class="dialog-overlay" />
|
||||
> -->
|
||||
<div transition:fade|global>
|
||||
<!-- <DialogOverlay class="dialog-overlay" /> -->
|
||||
<div class="dialog">
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<!-- <DialogTitle>{title}</DialogTitle> -->
|
||||
{#if description}
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
<!-- <DialogDescription>{description}</DialogDescription> -->
|
||||
{/if}
|
||||
|
||||
<div class="dialog-footer">
|
||||
|
|
@ -72,7 +72,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
<!-- </Dialog> -->
|
||||
|
||||
<style lang="scss">
|
||||
.dialog {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition';
|
||||
// import { Button, buttonVariants } from '$components/ui/button';
|
||||
import { Button, buttonVariants } from '$components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogDescription,
|
||||
DialogOverlay,
|
||||
DialogTitle
|
||||
} from '@rgossiaux/svelte-headlessui';
|
||||
import { boredState } from '$root/lib/stores/boredState';
|
||||
import { collectionStore } from '$root/lib/stores/collectionStore';
|
||||
import { removeFromCollection } from '$root/lib/util/manipulateCollection';
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger
|
||||
} from '$components/ui/dialog';
|
||||
import { boredState } from '$lib/stores/boredState';
|
||||
import { collectionStore } from '$lib/stores/collectionStore';
|
||||
import { removeFromCollection } from '$lib/utils/manipulateCollection';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
function removeGame() {
|
||||
|
|
@ -24,14 +29,42 @@
|
|||
$: isOpen = $boredState?.dialog?.isOpen;
|
||||
</script>
|
||||
|
||||
<Dialog
|
||||
<Dialog modal={true}>
|
||||
<DialogTrigger class={buttonVariants({ variant: "outline" })}>
|
||||
Remove from collection
|
||||
</DialogTrigger>
|
||||
<DialogContent class="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Remove from collection</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to remove from your collection?
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div class="grid gap-4 py-4">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">Name</Label>
|
||||
<Input id="name" value="Pedro Duarte" class="col-span-3" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">Username</Label>
|
||||
<Input id="username" value="@peduarte" class="col-span-3" />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit">Remove</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
|
||||
<!-- <Dialog
|
||||
open={isOpen}
|
||||
on:close={() => {
|
||||
boredState.update((n) => ({ ...n, dialog: { isOpen: false } }));
|
||||
}}
|
||||
static
|
||||
>
|
||||
<div transition:fade>
|
||||
<div transition:fade|global>
|
||||
<DialogOverlay class="dialog-overlay" />
|
||||
<div class="dialog">
|
||||
<DialogTitle>Remove from collection</DialogTitle>
|
||||
|
|
@ -47,7 +80,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Dialog> -->
|
||||
|
||||
<style lang="scss">
|
||||
.dialog {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition';
|
||||
import {
|
||||
Dialog,
|
||||
DialogDescription,
|
||||
DialogOverlay,
|
||||
DialogTitle
|
||||
} from '@rgossiaux/svelte-headlessui';
|
||||
import { boredState } from '$root/lib/stores/boredState';
|
||||
import { wishlistStore } from '$root/lib/stores/wishlistStore';
|
||||
import { removeFromWishlist } from '$root/lib/util/manipulateWishlist';
|
||||
// import {
|
||||
// Dialog,
|
||||
// DialogDescription,
|
||||
// DialogOverlay,
|
||||
// DialogTitle
|
||||
// } from '@rgossiaux/svelte-headlessui';
|
||||
import { boredState } from '$lib/stores/boredState';
|
||||
import { wishlistStore } from '$lib/stores/wishlistStore';
|
||||
import { removeFromWishlist } from '$lib/utils/manipulateWishlist';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
function removeGame() {
|
||||
|
|
@ -24,18 +24,18 @@
|
|||
$: isOpen = $boredState?.dialog?.isOpen;
|
||||
</script>
|
||||
|
||||
<Dialog
|
||||
<!-- <Dialog
|
||||
open={isOpen}
|
||||
on:close={() => {
|
||||
boredState.update((n) => ({ ...n, dialog: { isOpen: false } }));
|
||||
}}
|
||||
static
|
||||
>
|
||||
<div transition:fade>
|
||||
<DialogOverlay class="dialog-overlay" />
|
||||
> -->
|
||||
<div transition:fade|global>
|
||||
<!-- <DialogOverlay class="dialog-overlay" /> -->
|
||||
<div class="dialog">
|
||||
<DialogTitle>Remove from wishlist</DialogTitle>
|
||||
<DialogDescription>Are you sure you want to remove from your wishlist?</DialogDescription>
|
||||
<!-- <DialogTitle>Remove from wishlist</DialogTitle> -->
|
||||
<!-- <DialogDescription>Are you sure you want to remove from your wishlist?</DialogDescription> -->
|
||||
|
||||
<div class="dialog-footer">
|
||||
<button class="remove" on:click={removeGame}>Remove</button>
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
<!-- </Dialog> -->
|
||||
|
||||
<style lang="scss">
|
||||
.dialog {
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
<footer>
|
||||
<p>Built by <a target="__blank" href="https://bradleyshellnut.com">Bradley Shellnut</a></p>
|
||||
<p>
|
||||
<a
|
||||
target="__blank"
|
||||
href="https://www.flaticon.com/free-icons/board-game"
|
||||
title="board game icons">Board game icons created by Freepik - Flaticon</a
|
||||
>
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<style lang="postcss">
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
footer a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
footer {
|
||||
padding: 40px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,187 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { Image } from 'svelte-lazy-loader';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { MinusCircleIcon, PlusCircleIcon } from '@rgossiaux/svelte-heroicons/outline';
|
||||
import type { GameType, SavedGameType } from '$lib/types';
|
||||
import { collectionStore } from '$lib/stores/collectionStore';
|
||||
import { wishlistStore } from '$root/lib/stores/wishlistStore';
|
||||
import { addToCollection, removeFromCollection } from '$lib/util/manipulateCollection';
|
||||
import { addToWishlist } from '$lib/util/manipulateWishlist';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export let game: GameType | SavedGameType;
|
||||
export let detailed: boolean = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function removeGameFromWishlist() {
|
||||
dispatch('handleRemoveWishlist', game);
|
||||
}
|
||||
|
||||
function removeGameFromCollection() {
|
||||
dispatch('handleRemoveCollection', game);
|
||||
}
|
||||
|
||||
// Naive and assumes description is only on our GameType at the moment
|
||||
function isGameType(game: GameType | SavedGameType): game is GameType {
|
||||
return (game as GameType).description !== undefined;
|
||||
}
|
||||
|
||||
// function lazy(img: HTMLImageElement) {
|
||||
// function loaded() {
|
||||
// img.classList.add('loaded');
|
||||
// img.classList.remove('loading');
|
||||
// }
|
||||
// if (img.complete) {
|
||||
// loaded();
|
||||
// } else {
|
||||
// img.classList.add('loading');
|
||||
// img.onload = () => loaded();
|
||||
// }
|
||||
// }
|
||||
|
||||
$: existsInCollection = $collectionStore.find((item: SavedGameType) => item.id === game.id);
|
||||
$: existsInWishlist = $wishlistStore.find((item: SavedGameType) => item.id === game.id);
|
||||
</script>
|
||||
|
||||
<article class="game-container" transition:fade>
|
||||
<h2>{game.name}</h2>
|
||||
<a
|
||||
class="thumbnail"
|
||||
href={`/game/${game.id}`}
|
||||
title={`View ${game.name}`}
|
||||
data-sveltekit-preload-data
|
||||
>
|
||||
<!-- <Image src={game.thumb_url} alt={`Image of ${game.name}`} /> -->
|
||||
<img src={game.thumb_url} alt={`Image of ${game.name}`} loading="lazy" decoding="async" />
|
||||
<!-- loading="lazy" decoding="async" -->
|
||||
</a>
|
||||
|
||||
<div class="game-details">
|
||||
<p>Players: {game.players}</p>
|
||||
<p>Time: {game.playtime} minutes</p>
|
||||
{#if isGameType(game) && game?.min_age}
|
||||
<p>Min Age: {game.min_age}</p>
|
||||
{/if}
|
||||
{#if detailed && isGameType(game) && game?.description}
|
||||
<div class="description">{@html game.description}</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="game-buttons">
|
||||
{#if existsInCollection}
|
||||
<button
|
||||
aria-label="Remove from collection"
|
||||
class="btn remove"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
removeGameFromCollection();
|
||||
}}><span>Remove from Collection</span> <MinusCircleIcon width="24" height="24" /></button
|
||||
>
|
||||
{:else}
|
||||
<button
|
||||
aria-label="Add to collection"
|
||||
class="btn"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
addToCollection(game);
|
||||
if (browser) {
|
||||
localStorage.collection = JSON.stringify($collectionStore);
|
||||
}
|
||||
}}><span>Add to collection</span> <PlusCircleIcon width="24" height="24" /></button
|
||||
>
|
||||
{/if}
|
||||
{#if existsInWishlist}
|
||||
<button
|
||||
aria-label="Remove from wishlist"
|
||||
class="btn remove"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
removeGameFromWishlist();
|
||||
}}><span>Remove from Wishlist</span> <MinusCircleIcon width="24" height="24" /></button
|
||||
>
|
||||
{:else}
|
||||
<button
|
||||
aria-label="Add to wishlist"
|
||||
class="btn"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
addToWishlist(game);
|
||||
if (browser) {
|
||||
localStorage.wishlist = JSON.stringify($wishlistStore);
|
||||
}
|
||||
}}><span>Add to wishlist</span> <PlusCircleIcon width="24" height="24" /></button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<style lang="scss">
|
||||
img {
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
padding: 1rem;
|
||||
background-color: var(--color-btn-primary-active);
|
||||
}
|
||||
|
||||
.game-container {
|
||||
display: grid;
|
||||
grid-template-rows: repeat(auto-fill, 1fr);
|
||||
place-items: center;
|
||||
text-align: center;
|
||||
|
||||
@media (max-width: 650px) {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
gap: var(--spacing-16);
|
||||
padding: var(--spacing-16) var(--spacing-16);
|
||||
transition: all 0.3s;
|
||||
border-radius: 8px;
|
||||
background-color: var(--primary);
|
||||
&:hover {
|
||||
background-color: hsla(222, 9%, 65%, 1);
|
||||
}
|
||||
|
||||
/* .game-info {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
gap: 0.75rem;
|
||||
margin: 0.2rem;
|
||||
} */
|
||||
|
||||
.game-details {
|
||||
p,
|
||||
a {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.game-buttons {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
|
||||
.btn {
|
||||
max-height: 100px;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.remove {
|
||||
background-color: var(--warning);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--warning-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
<script lang="ts">
|
||||
import Profile from '../preferences/profile.svelte';
|
||||
import logo from './bored-game.png';
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<div class="corner">
|
||||
<a href="/" title="Home">
|
||||
<img src={logo} alt="Bored Game Home" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<a href="/collection" title="Go to your collection" data-sveltekit-preload-data>Collection</a>
|
||||
<a href="/wishlist" title="Go to your wishlist" data-sveltekit-preload-data>Wishlist</a>
|
||||
<Profile />
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<style lang="scss">
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--containerPadding);
|
||||
font-size: 1.6rem;
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
padding-top: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.corner {
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
}
|
||||
|
||||
.corner a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.corner img {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 1rem;
|
||||
--background: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
padding: 0 1em;
|
||||
color: var(--heading-color);
|
||||
font-weight: 700;
|
||||
/* font-size: 0.8rem; */
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s linear;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.1566,22.8189c-10.4-14.8851-30.94-19.2971-45.7914-9.8348L22.2825,29.6078A29.9234,29.9234,0,0,0,8.7639,49.6506a31.5136,31.5136,0,0,0,3.1076,20.2318A30.0061,30.0061,0,0,0,7.3953,81.0653a31.8886,31.8886,0,0,0,5.4473,24.1157c10.4022,14.8865,30.9423,19.2966,45.7914,9.8348L84.7167,98.3921A29.9177,29.9177,0,0,0,98.2353,78.3493,31.5263,31.5263,0,0,0,95.13,58.117a30,30,0,0,0,4.4743-11.1824,31.88,31.88,0,0,0-5.4473-24.1157" style="fill:#ff3e00"/><path d="M45.8171,106.5815A20.7182,20.7182,0,0,1,23.58,98.3389a19.1739,19.1739,0,0,1-3.2766-14.5025,18.1886,18.1886,0,0,1,.6233-2.4357l.4912-1.4978,1.3363.9815a33.6443,33.6443,0,0,0,10.203,5.0978l.9694.2941-.0893.9675a5.8474,5.8474,0,0,0,1.052,3.8781,6.2389,6.2389,0,0,0,6.6952,2.485,5.7449,5.7449,0,0,0,1.6021-.7041L69.27,76.281a5.4306,5.4306,0,0,0,2.4506-3.631,5.7948,5.7948,0,0,0-.9875-4.3712,6.2436,6.2436,0,0,0-6.6978-2.4864,5.7427,5.7427,0,0,0-1.6.7036l-9.9532,6.3449a19.0329,19.0329,0,0,1-5.2965,2.3259,20.7181,20.7181,0,0,1-22.2368-8.2427,19.1725,19.1725,0,0,1-3.2766-14.5024,17.9885,17.9885,0,0,1,8.13-12.0513L55.8833,23.7472a19.0038,19.0038,0,0,1,5.3-2.3287A20.7182,20.7182,0,0,1,83.42,29.6611a19.1739,19.1739,0,0,1,3.2766,14.5025,18.4,18.4,0,0,1-.6233,2.4357l-.4912,1.4978-1.3356-.98a33.6175,33.6175,0,0,0-10.2037-5.1l-.9694-.2942.0893-.9675a5.8588,5.8588,0,0,0-1.052-3.878,6.2389,6.2389,0,0,0-6.6952-2.485,5.7449,5.7449,0,0,0-1.6021.7041L37.73,51.719a5.4218,5.4218,0,0,0-2.4487,3.63,5.7862,5.7862,0,0,0,.9856,4.3717,6.2437,6.2437,0,0,0,6.6978,2.4864,5.7652,5.7652,0,0,0,1.602-.7041l9.9519-6.3425a18.978,18.978,0,0,1,5.2959-2.3278,20.7181,20.7181,0,0,1,22.2368,8.2427,19.1725,19.1725,0,0,1,3.2766,14.5024,17.9977,17.9977,0,0,1-8.13,12.0532L51.1167,104.2528a19.0038,19.0038,0,0,1-5.3,2.3287" style="fill:#fff"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
|
|
@ -1,11 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition';
|
||||
import {
|
||||
Listbox,
|
||||
ListboxButton,
|
||||
ListboxOptions,
|
||||
ListboxOption
|
||||
} from '@rgossiaux/svelte-headlessui';
|
||||
// import {
|
||||
// Listbox,
|
||||
// ListboxButton,
|
||||
// ListboxOptions,
|
||||
// ListboxOption
|
||||
// } from '@rgossiaux/svelte-headlessui';
|
||||
|
||||
const shows = [
|
||||
{ id: 1, name: 'Cowboy Bebop', completed: false },
|
||||
|
|
@ -22,8 +22,8 @@
|
|||
<h4>Listbox</h4>
|
||||
|
||||
<div class="listbox">
|
||||
<Listbox value={selected} on:change={(event) => (selected = event.detail)} let:open>
|
||||
<ListboxButton class="button">
|
||||
<!-- <Listbox value={selected} on:change={(event) => (selected = event.detail)} let:open> -->
|
||||
<!-- <ListboxButton class="button"> -->
|
||||
<span>{selected.name}</span>
|
||||
<svg
|
||||
width="20"
|
||||
|
|
@ -40,25 +40,25 @@
|
|||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</ListboxButton>
|
||||
<!-- </ListboxButton> -->
|
||||
{#if open}
|
||||
<div transition:fade={{ duration: 200 }}>
|
||||
<ListboxOptions class="options">
|
||||
<div transition:fade|global={{ duration: 200 }}>
|
||||
<!-- <ListboxOptions class="options"> -->
|
||||
{#each shows as anime (anime.id)}
|
||||
<ListboxOption
|
||||
<!-- <ListboxOption
|
||||
class="option"
|
||||
value={anime}
|
||||
disabled={anime.completed}
|
||||
let:active
|
||||
let:selected
|
||||
>
|
||||
> -->
|
||||
<span class:active class:selected>{anime.name}</span>
|
||||
</ListboxOption>
|
||||
<!-- </ListboxOption> -->
|
||||
{/each}
|
||||
</ListboxOptions>
|
||||
<!-- </ListboxOptions> -->
|
||||
</div>
|
||||
{/if}
|
||||
</Listbox>
|
||||
<!-- </Listbox> -->
|
||||
</div>
|
||||
|
||||
<!-- ... -->
|
||||
|
|
|
|||
14
src/lib/components/logo.svelte
Normal file
14
src/lib/components/logo.svelte
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-go-game" viewBox="0 0 24 24"
|
||||
stroke-width="1" stroke="var(--fg)" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M6 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||
<path d="M12 12m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||
<path d="M6 18m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||
<path d="M18 18m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||
<path d="M3 12h7m4 0h7" />
|
||||
<path d="M3 6h1m4 0h13" />
|
||||
<path d="M3 18h1m4 0h8m4 0h1" />
|
||||
<path d="M6 3v1m0 4v8m0 4v1" />
|
||||
<path d="M12 3v7m0 4v7" />
|
||||
<path d="M18 3v13m0 4v1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 671 B |
|
|
@ -2,17 +2,17 @@
|
|||
// Based on https://carbon-components-svelte.onrender.com/components/Pagination
|
||||
import { afterUpdate, createEventDispatcher } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import {
|
||||
Listbox,
|
||||
ListboxButton,
|
||||
ListboxOption,
|
||||
ListboxOptions
|
||||
} from '@rgossiaux/svelte-headlessui';
|
||||
import {
|
||||
CheckIcon,
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon
|
||||
} from '@rgossiaux/svelte-heroicons/outline';
|
||||
// import {
|
||||
// Listbox,
|
||||
// ListboxButton,
|
||||
// ListboxOption,
|
||||
// ListboxOptions
|
||||
// } from '@rgossiaux/svelte-headlessui';
|
||||
// import {
|
||||
// CheckIcon,
|
||||
// ChevronLeftIcon,
|
||||
// ChevronRightIcon
|
||||
// } from '@rgossiaux/svelte-heroicons/outline';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
|
|
@ -41,36 +41,36 @@
|
|||
<div class="container">
|
||||
<div class="listbox">
|
||||
<p>Per-page:</p>
|
||||
<Listbox
|
||||
<!-- <Listbox
|
||||
class="list-box"
|
||||
value={pageSize || 10}
|
||||
on:change={(e) => {
|
||||
dispatch('perPageEvent', { pageSize: e.detail, page });
|
||||
}}
|
||||
let:open
|
||||
>
|
||||
<ListboxButton>{pageSize || 10}</ListboxButton>
|
||||
{#if open}
|
||||
<div transition:fade={{ duration: 100 }}>
|
||||
<ListboxOptions static class="options">
|
||||
> -->
|
||||
<!-- <ListboxButton>{pageSize || 10}</ListboxButton> -->
|
||||
<!-- {#if open} -->
|
||||
<div transition:fade|global={{ duration: 100 }}>
|
||||
<!-- <ListboxOptions static class="options"> -->
|
||||
{#each pageSizes as size (size)}
|
||||
<ListboxOption
|
||||
<!-- <ListboxOption
|
||||
value={`${size}`}
|
||||
disabled={pageSizeInputDisabled}
|
||||
class={({ active }) => (active ? 'active option' : 'option')}
|
||||
style="display: flex; gap: 1rem; padding: 1rem;"
|
||||
let:selected
|
||||
>
|
||||
{#if selected}
|
||||
<CheckIcon height="24" width="24" />
|
||||
{/if}
|
||||
<span class="size-option" class:selected>{size.toString()}</span>
|
||||
</ListboxOption>
|
||||
> -->
|
||||
<!-- {#if selected} -->
|
||||
<!-- <CheckIcon height="24" width="24" /> -->
|
||||
<!-- {/if} -->
|
||||
<!-- <span class="size-option" class:selected>{size.toString()}</span> -->
|
||||
<!-- </ListboxOption> -->
|
||||
{/each}
|
||||
</ListboxOptions>
|
||||
<!-- </ListboxOptions> -->
|
||||
</div>
|
||||
{/if}
|
||||
</Listbox>
|
||||
<!-- {/if} -->
|
||||
<!-- </Listbox> -->
|
||||
</div>
|
||||
<p>
|
||||
Page {page || 1} of {totalPages || 1}
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
dispatch('previousPageEvent', { page });
|
||||
}}
|
||||
>
|
||||
<ChevronLeftIcon width="24" height="24" />
|
||||
<!-- <ChevronLeftIcon width="24" height="24" /> -->
|
||||
<p class="word">{backwardText || 'Prev'}</p>
|
||||
</button>
|
||||
<button
|
||||
|
|
@ -103,7 +103,7 @@
|
|||
}}
|
||||
>
|
||||
<p class="word">{forwardText || 'Next'}</p>
|
||||
<ChevronRightIcon width="24" height="24" />
|
||||
<!-- <ChevronRightIcon width="24" height="24" /> -->
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { browser } from '$app/environment';
|
||||
import { boredState } from '$root/lib/stores/boredState';
|
||||
import { collectionStore } from '$root/lib/stores/collectionStore';
|
||||
import { ToastType } from '$root/lib/types';
|
||||
import { SaveIcon, ShareIcon, TrashIcon } from '@rgossiaux/svelte-heroicons/outline';
|
||||
import { boredState } from '$lib/stores/boredState';
|
||||
import { collectionStore } from '$lib/stores/collectionStore';
|
||||
import { ToastType } from '$lib/types';
|
||||
// import { SaveIcon, ShareIcon, TrashIcon } from '@rgossiaux/svelte-heroicons/outline';
|
||||
import ClearCollectionDialog from '../dialog/ClearCollectionDialog.svelte';
|
||||
import { toast } from '../toast/toast';
|
||||
|
||||
|
|
@ -48,13 +48,15 @@
|
|||
</div>
|
||||
<div class="collection-buttons">
|
||||
<button type="button" aria-label="Export Collection" on:click={() => exportCollection()}
|
||||
><ShareIcon width="24" height="24" />Export</button
|
||||
>
|
||||
<!-- <ShareIcon width="24" height="24" /> -->
|
||||
Export</button
|
||||
>
|
||||
<!-- <button type="button" aria-label="Save Collection" on:click={() => saveCollection()}
|
||||
><SaveIcon width="24" height="24" />Save</button
|
||||
> -->
|
||||
<button type="button" aria-label="Clear saved collection" on:click={() => clearCollection()}>
|
||||
<TrashIcon width="24" height="24" />Clear
|
||||
<!-- <TrashIcon width="24" height="24" />Clear -->
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { browser } from '$app/environment';
|
||||
import { boredState } from '$root/lib/stores/boredState';
|
||||
import { wishlistStore } from '$root/lib/stores/wishlistStore';
|
||||
import { ToastType } from '$root/lib/types';
|
||||
import { SaveIcon, ShareIcon, TrashIcon } from '@rgossiaux/svelte-heroicons/outline';
|
||||
import { boredState } from '$lib/stores/boredState';
|
||||
import { wishlistStore } from '$lib/stores/wishlistStore';
|
||||
import { ToastType } from '$lib/types';
|
||||
// import { SaveIcon, ShareIcon, TrashIcon } from '@rgossiaux/svelte-heroicons/outline';
|
||||
import ClearWishlistDialog from '../dialog/ClearWishlistDialog.svelte';
|
||||
import { toast } from '../toast/toast';
|
||||
|
||||
|
|
@ -48,13 +48,15 @@
|
|||
</div>
|
||||
<div class="wishlist-buttons">
|
||||
<button type="button" aria-label="Export Wishlist" on:click={() => exportWishlist()}
|
||||
><ShareIcon width="24" height="24" />Export</button
|
||||
>
|
||||
<!-- <ShareIcon width="24" height="24" /> -->
|
||||
Export</button
|
||||
>
|
||||
<!-- <button type="button" aria-label="Save Wishlist" on:click={() => saveWishlist()}
|
||||
><SaveIcon width="24" height="24" />Save</button
|
||||
> -->
|
||||
<button type="button" aria-label="Clear saved wishlist" on:click={() => clearWishlist()}>
|
||||
<TrashIcon width="24" height="24" />Clear
|
||||
<!-- <TrashIcon width="24" height="24" />Clear -->
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,22 +1,35 @@
|
|||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition';
|
||||
import { Popover, PopoverButton, PopoverPanel } from '@rgossiaux/svelte-headlessui';
|
||||
import { CogIcon } from '@rgossiaux/svelte-heroicons/outline';
|
||||
import { collectionStore } from '$root/lib/stores/collectionStore';
|
||||
import cogOutline from '@iconify-icons/mdi/cog-outline';
|
||||
import Themes from './themes.svelte';
|
||||
import GameCollection from './gameCollection.svelte';
|
||||
import GameWishlist from './gameWishlist.svelte';
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<Popover let:open class="popover">
|
||||
<PopoverButton aria-label="Preferences">
|
||||
<CogIcon width="24" height="24" />
|
||||
</PopoverButton>
|
||||
<!-- <Popover let:open class="popover">
|
||||
<PopoverButton aria-label="Preferences"> -->
|
||||
<!-- <CogIcon width="24" height="24" /> -->
|
||||
<!-- <iconify-icon icon="mdi:cog-outline"
|
||||
width="24" height="24"
|
||||
style={open ?
|
||||
'transform: rotate(90deg); transition: transform 0.5s ease;'
|
||||
: 'transform: rotate(0deg); transition: transform 0.5s ease;'
|
||||
}
|
||||
></iconify-icon> -->
|
||||
<iconify-icon
|
||||
icon={cogOutline}
|
||||
width="24" height="24"
|
||||
style={open ?
|
||||
'transform: rotate(90deg); transition: transform 0.5s ease;'
|
||||
: 'transform: rotate(0deg); transition: transform 0.5s ease;'
|
||||
}
|
||||
/>
|
||||
<!-- </PopoverButton> -->
|
||||
|
||||
{#if open}
|
||||
<div transition:fade={{ duration: 100 }}>
|
||||
<PopoverPanel class="popover-panel" static>
|
||||
<div transition:fade|global={{ duration: 100 }}>
|
||||
<!-- <PopoverPanel class="popover-panel" static> -->
|
||||
<div class="preferences">
|
||||
<svg
|
||||
width="24"
|
||||
|
|
@ -46,10 +59,10 @@
|
|||
<GameWishlist />
|
||||
</div>
|
||||
</div>
|
||||
</PopoverPanel>
|
||||
<!-- </PopoverPanel> -->
|
||||
</div>
|
||||
{/if}
|
||||
</Popover>
|
||||
<!-- </Popover> -->
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition';
|
||||
import { browser } from '$app/environment';
|
||||
import {
|
||||
Listbox,
|
||||
ListboxButton,
|
||||
ListboxOption,
|
||||
ListboxOptions
|
||||
} from '@rgossiaux/svelte-headlessui';
|
||||
// import {
|
||||
// Listbox,
|
||||
// ListboxButton,
|
||||
// ListboxOption,
|
||||
// ListboxOptions
|
||||
// } from '@rgossiaux/svelte-headlessui';
|
||||
|
||||
type Themes = Record<string, { name: string }>;
|
||||
|
||||
|
|
@ -61,8 +61,8 @@
|
|||
|
||||
<div class="theme">
|
||||
<div class="listbox">
|
||||
<Listbox value={selectedTheme} on:change={handleChange} let:open>
|
||||
<ListboxButton class="button">
|
||||
<!-- <Listbox value={selectedTheme} on:change={handleChange} let:open> -->
|
||||
<!-- <ListboxButton class="button"> -->
|
||||
<span>{selectedTheme.name}</span>
|
||||
|
||||
<span>
|
||||
|
|
@ -82,22 +82,22 @@
|
|||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</ListboxButton>
|
||||
<!-- </ListboxButton> -->
|
||||
|
||||
{#if open}
|
||||
<div transition:fade={{ duration: 100 }}>
|
||||
<ListboxOptions class="options" static>
|
||||
<div transition:fade|global={{ duration: 100 }}>
|
||||
<!-- <ListboxOptions class="options" static> -->
|
||||
{#each Object.entries(themes) as [key, theme] (key)}
|
||||
<ListboxOption value={theme} let:active let:selected>
|
||||
<!-- <ListboxOption value={theme} let:active let:selected> -->
|
||||
<span class="option" class:active class:selected>
|
||||
{theme.name}
|
||||
</span>
|
||||
</ListboxOption>
|
||||
<!-- </ListboxOption> -->
|
||||
{/each}
|
||||
</ListboxOptions>
|
||||
<!-- </ListboxOptions> -->
|
||||
</div>
|
||||
{/if}
|
||||
</Listbox>
|
||||
<!-- </Listbox> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import { collectionStore } from '$lib/stores/collectionStore';
|
||||
import { toast } from '$lib/components/toast/toast';
|
||||
import { ToastType, type SavedGameType } from '$lib/types';
|
||||
import { mapSavedGameToGame } from '$root/lib/util/gameMapper';
|
||||
import { mapSavedGameToGame } from '$lib/utils/gameMapper';
|
||||
|
||||
async function getRandomCollectionGame() {
|
||||
if ($collectionStore.length > 0) {
|
||||
|
|
|
|||
55
src/lib/components/search/GameSearchForm.svelte
Normal file
55
src/lib/components/search/GameSearchForm.svelte
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<script lang="ts">
|
||||
import { superForm, type Infer, type SuperValidated } from 'sveltekit-superforms';
|
||||
import { search_schema, type SearchSchema } from '$lib/zodValidation';
|
||||
import * as Form from "$lib/components/ui/form";
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import Input from '$components/ui/input/input.svelte';
|
||||
import Checkbox from '$components/ui/checkbox/checkbox.svelte';
|
||||
|
||||
export let data: SuperValidated<Infer<SearchSchema>>;
|
||||
|
||||
const form = superForm(data, {
|
||||
validators: zodClient(search_schema),
|
||||
});
|
||||
|
||||
const { form: formData } = form;
|
||||
</script>
|
||||
|
||||
<search>
|
||||
<form id="search-form" action="/search" method="GET" data-sveltekit-reload>
|
||||
<fieldset>
|
||||
<Form.Field {form} name="q">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label>Search</Form.Label>
|
||||
<Input {...attrs} bind:value={$formData.q} />
|
||||
</Form.Control>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
<Form.Field {form} name="skip">
|
||||
<Form.Control let:attrs>
|
||||
<Input type="hidden" />
|
||||
</Form.Control>
|
||||
</Form.Field>
|
||||
<Form.Field {form} name="limit">
|
||||
<Form.Control let:attrs>
|
||||
<Input type="hidden" />
|
||||
</Form.Control>
|
||||
</Form.Field>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Form.Field {form} name="exact">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label>Exact Search</Form.Label>
|
||||
<Checkbox {...attrs} class="mt-0" bind:checked={$formData.exact} />
|
||||
<input name={attrs.name} value={$formData.exact} hidden />
|
||||
</Form.Control>
|
||||
</Form.Field>
|
||||
</div>
|
||||
</fieldset>
|
||||
<Form.Button>Submit</Form.Button>
|
||||
</form>
|
||||
</search>
|
||||
|
||||
<style lang="postcss">
|
||||
</style>
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
<script lang="ts">
|
||||
import type { ActionData } from './$types';
|
||||
import { boredState } from '$lib/stores/boredState';
|
||||
import type { PageData } from '.svelte-kit/types/src/routes/$types';
|
||||
|
||||
export let data: PageData;
|
||||
console.log('advanced search data', data);
|
||||
export let form;
|
||||
export let errors;
|
||||
export let constraints;
|
||||
console.log('advanced search data', $form);
|
||||
|
||||
let submitting = $boredState?.loading;
|
||||
let minAge = +data?.minAge || 1;
|
||||
let minPlayers = +data?.minPlayers || 1;
|
||||
let maxPlayers = +data?.maxPlayers || 1;
|
||||
let exactMinPlayers = Boolean(data?.exactMinPlayers) || false;
|
||||
let exactMaxPlayers = Boolean(data?.exactMaxPlayers) || false;
|
||||
let minAge = +$form?.minAge || 1;
|
||||
let minPlayers = +$form?.minPlayers || 1;
|
||||
let maxPlayers = +$form?.maxPlayers || 1;
|
||||
let exactMinPlayers = Boolean($form?.exactMinPlayers) || false;
|
||||
let exactMaxPlayers = Boolean($form?.exactMaxPlayers) || false;
|
||||
</script>
|
||||
|
||||
<fieldset class="advanced-search" aria-busy={submitting} disabled={submitting}>
|
||||
|
|
@ -20,10 +20,10 @@
|
|||
Min Age
|
||||
<input id="minAge" name="minAge" bind:value={minAge} type="number" min={1} max={120} />
|
||||
</label>
|
||||
{#if data?.errors?.minAge}
|
||||
{#if $errors?.minAge}
|
||||
<div id="minPlayers-error" class="error">
|
||||
<p aria-label={`Error: ${data?.errors?.minAge}`} class="center">
|
||||
{data?.errors?.minAge}
|
||||
<p aria-label={`Error: ${$errors?.minAge}`} class="center">
|
||||
{$errors?.minAge}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -50,10 +50,10 @@
|
|||
bind:value={exactMinPlayers}
|
||||
/>
|
||||
</label>
|
||||
{#if data?.errors?.minPlayers}
|
||||
{#if $errors?.minPlayers}
|
||||
<div id="minPlayers-error" class="error">
|
||||
<p aria-label={`Error: ${data?.errors?.minPlayers}`} class="center">
|
||||
{data?.errors?.minPlayers}
|
||||
<p aria-label={`Error: ${$errors?.minPlayers}`} class="center">
|
||||
{$errors?.minPlayers}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -80,10 +80,10 @@
|
|||
bind:value={exactMaxPlayers}
|
||||
/>
|
||||
</label>
|
||||
{#if data?.error?.id === 'maxPlayers'}
|
||||
{#if $errors?.id === 'maxPlayers'}
|
||||
<div id="maxPlayers-error" class="error">
|
||||
<p aria-label={`Error: ${data.error.message}`} class="center">
|
||||
Error: {data.error.message}
|
||||
<p aria-label={`Error: ${$errors.message}`} class="center">
|
||||
Error: {$errors.message}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,47 @@
|
|||
<script lang="ts">
|
||||
import { applyAction, enhance } from '$app/forms';
|
||||
import type { SuperValidated } from 'sveltekit-superforms/index';
|
||||
import type { SearchSchema } from '$lib/zodValidation';
|
||||
import { boredState } from '$lib/stores/boredState';
|
||||
import { gameStore } from '$lib/stores/gameSearchStore';
|
||||
import { ToastType } from '$root/lib/types';
|
||||
import { toast } from '../../toast/toast';
|
||||
import { superForm } from 'sveltekit-superforms/client';
|
||||
import { Button } from '$components/ui/button';
|
||||
|
||||
export let data: SuperValidated<SearchSchema>;
|
||||
const { enhance } = superForm(data, {
|
||||
onSubmit: () => {
|
||||
gameStore.removeAll();
|
||||
boredState.update((n) => ({ ...n, loading: true }));
|
||||
},
|
||||
onResult: ({ result, formEl, cancel }) => {
|
||||
boredState.update((n) => ({ ...n, loading: false }));
|
||||
if (result.type === 'success') {
|
||||
gameStore.addAll(result?.data?.searchData?.games);
|
||||
} else {
|
||||
cancel();
|
||||
}
|
||||
},
|
||||
// onUpdated: ({ form }) => {
|
||||
// if ($gameStore.length <= 0) {
|
||||
// toast.send('No results found 😿', {
|
||||
// duration: 3000,
|
||||
// type: ToastType.ERROR,
|
||||
// dismissible: true
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
});
|
||||
|
||||
let submitting = $boredState?.loading;
|
||||
let checked = true;
|
||||
</script>
|
||||
|
||||
<form
|
||||
action="/search?/random"
|
||||
method="POST"
|
||||
use:enhance={() => {
|
||||
gameStore.removeAll();
|
||||
boredState.update((n) => ({ ...n, loading: true }));
|
||||
return async ({ result }) => {
|
||||
console.log('result', result);
|
||||
boredState.update((n) => ({ ...n, loading: false }));
|
||||
// `result` is an `ActionResult` object
|
||||
if (result.type === 'success') {
|
||||
// console.log('In success');
|
||||
const resultGames = result?.data?.games;
|
||||
if (resultGames?.length <= 0) {
|
||||
toast.send('No results found 😿', {
|
||||
duration: 3000,
|
||||
type: ToastType.ERROR,
|
||||
dismissible: true
|
||||
});
|
||||
}
|
||||
gameStore.addAll(resultGames);
|
||||
// console.log(`Frontend result random: ${JSON.stringify(result)}`);
|
||||
await applyAction(result);
|
||||
} else {
|
||||
// console.log('Invalid');
|
||||
await applyAction(result);
|
||||
}
|
||||
};
|
||||
}}
|
||||
use:enhance
|
||||
>
|
||||
<fieldset aria-busy={submitting} disabled={submitting}>
|
||||
<!-- <input type="checkbox" id="random" name="random" hidden {checked} /> -->
|
||||
<button class="btn" type="submit" disabled={submitting}>Random Game 🎲</button>
|
||||
<Button type="submit" disabled={submitting}>Random Game 🎲</Button>
|
||||
<!-- <button class="btn" type="submit" disabled={submitting}>Random Game 🎲</button> -->
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,296 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { tick } from 'svelte';
|
||||
import { applyAction, enhance, type SubmitFunction } from '$app/forms';
|
||||
import type { ActionData, PageData } from './$types';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@rgossiaux/svelte-headlessui';
|
||||
import { ChevronRightIcon } from '@rgossiaux/svelte-heroicons/solid';
|
||||
import { boredState } from '$lib/stores/boredState';
|
||||
import AdvancedSearch from '$lib/components/search/advancedSearch/index.svelte';
|
||||
import { xl, md, sm } from '$lib/stores/mediaQueryStore';
|
||||
import { gameStore } from '$root/lib/stores/gameSearchStore';
|
||||
import { toast } from '../../toast/toast';
|
||||
import Pagination from '$lib/components/pagination/index.svelte';
|
||||
import Game from '$lib/components/game/index.svelte';
|
||||
import { ToastType, type GameType, type SavedGameType } from '$root/lib/types';
|
||||
import SkeletonPlaceholder from '../../SkeletonPlaceholder.svelte';
|
||||
import RemoveCollectionDialog from '../../dialog/RemoveCollectionDialog.svelte';
|
||||
import RemoveWishlistDialog from '../../dialog/RemoveWishlistDialog.svelte';
|
||||
|
||||
interface RemoveGameEvent extends Event {
|
||||
detail: GameType | SavedGameType;
|
||||
}
|
||||
|
||||
export let data: PageData;
|
||||
// console.log('search page data', data);
|
||||
export let form: ActionData;
|
||||
// console.log('search page form', form);
|
||||
const errors = data?.errors;
|
||||
|
||||
export let showButton: boolean = false;
|
||||
export let advancedSearch: boolean = false;
|
||||
|
||||
let gameToRemove: GameType | SavedGameType;
|
||||
let numberOfGameSkeleton = 1;
|
||||
let submitButton: HTMLElement;
|
||||
let pageSize = +data?.limit || 10;
|
||||
let totalItems = +data?.totalCount || 0;
|
||||
let offset = +data?.skip || 0;
|
||||
let page = Math.floor(offset / pageSize) + 1 || 1;
|
||||
let submitting = $boredState?.loading;
|
||||
let name = data?.name || '';
|
||||
let disclosureOpen = errors || false;
|
||||
|
||||
$: skip = (page - 1) * pageSize;
|
||||
$: showPagination = $gameStore?.length > 1;
|
||||
|
||||
if ($xl) {
|
||||
numberOfGameSkeleton = 8;
|
||||
} else if ($md) {
|
||||
numberOfGameSkeleton = 3;
|
||||
} else if ($sm) {
|
||||
numberOfGameSkeleton = 2;
|
||||
} else {
|
||||
numberOfGameSkeleton = 1;
|
||||
}
|
||||
|
||||
let placeholderList = [...Array(numberOfGameSkeleton).keys()];
|
||||
|
||||
if (form?.error) {
|
||||
disclosureOpen = true;
|
||||
}
|
||||
|
||||
async function handleNextPageEvent(event: CustomEvent) {
|
||||
if (+event?.detail?.page === page + 1) {
|
||||
page += 1;
|
||||
}
|
||||
await tick();
|
||||
submitButton.click();
|
||||
}
|
||||
|
||||
async function handlePreviousPageEvent(event: CustomEvent) {
|
||||
if (+event?.detail?.page === page - 1) {
|
||||
page -= 1;
|
||||
}
|
||||
await tick();
|
||||
submitButton.click();
|
||||
}
|
||||
|
||||
async function handlePerPageEvent(event: CustomEvent) {
|
||||
page = 1;
|
||||
pageSize = event.detail.pageSize;
|
||||
await tick();
|
||||
submitButton.click();
|
||||
}
|
||||
|
||||
function handleRemoveCollection(event: RemoveGameEvent) {
|
||||
gameToRemove = event?.detail;
|
||||
boredState.update((n) => ({
|
||||
...n,
|
||||
dialog: { isOpen: true, content: RemoveCollectionDialog, additionalData: gameToRemove }
|
||||
}));
|
||||
}
|
||||
|
||||
function handleRemoveWishlist(event: RemoveGameEvent) {
|
||||
gameToRemove = event?.detail;
|
||||
boredState.update((n) => ({
|
||||
...n,
|
||||
dialog: { isOpen: true, content: RemoveWishlistDialog, additionalData: gameToRemove }
|
||||
}));
|
||||
}
|
||||
|
||||
const submitSearch: SubmitFunction = ({ form, data, action, cancel }) => {
|
||||
const { name } = Object.fromEntries(data);
|
||||
if (!disclosureOpen && name?.length === 0) {
|
||||
toast.send('Please enter a search term', {
|
||||
duration: 3000,
|
||||
type: ToastType.ERROR,
|
||||
dismissible: true
|
||||
});
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
gameStore.removeAll();
|
||||
boredState.update((n) => ({ ...n, loading: true }));
|
||||
return async ({ result }) => {
|
||||
boredState.update((n) => ({ ...n, loading: false }));
|
||||
// `result` is an `ActionResult` object
|
||||
if (result.type === 'error') {
|
||||
toast.send('Error!', { duration: 3000, type: ToastType.ERROR, dismissible: true });
|
||||
await applyAction(result);
|
||||
} else if (result.type === 'success') {
|
||||
gameStore.removeAll();
|
||||
gameStore.addAll(result?.data?.games);
|
||||
totalItems = result?.data?.totalCount;
|
||||
// toast.send('Success!', { duration: 3000, type: ToastType.INFO, dismissible: true });
|
||||
await applyAction(result);
|
||||
} else {
|
||||
await applyAction(result);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// TODO: Keep all Pagination Values on back and forth browser
|
||||
// TODO: Add cache for certain number of pages so back and forth doesn't request data again
|
||||
</script>
|
||||
|
||||
<form id="search-form" action="/search" method="get" on:submit={() => {
|
||||
skip = 0;
|
||||
}}>
|
||||
<div class="search">
|
||||
<fieldset class="text-search" aria-busy={submitting} disabled={submitting}>
|
||||
<label for="q">
|
||||
Search
|
||||
<input
|
||||
id="q"
|
||||
name="q"
|
||||
bind:value={name}
|
||||
type="text"
|
||||
aria-label="Search boardgame"
|
||||
placeholder="Search boardgame"
|
||||
/>
|
||||
</label>
|
||||
<input id="skip" type="hidden" name="skip" bind:value={skip} />
|
||||
<input id="limit" type="hidden" name="limit" bind:value={pageSize} />
|
||||
</fieldset>
|
||||
{#if advancedSearch}
|
||||
<Disclosure>
|
||||
<DisclosureButton
|
||||
class="disclosure-button"
|
||||
on:click={() => (disclosureOpen = !disclosureOpen)}
|
||||
>
|
||||
<span>Advanced Search?</span>
|
||||
<ChevronRightIcon
|
||||
class="icon disclosure-icon"
|
||||
style={disclosureOpen
|
||||
? 'transform: rotate(90deg); transition: transform 0.5s ease;'
|
||||
: 'transform: rotate(0deg); transition: transform 0.5s ease;'}
|
||||
/>
|
||||
</DisclosureButton>
|
||||
|
||||
{#if disclosureOpen}
|
||||
<div transition:fade>
|
||||
<!-- Using `static`, `DisclosurePanel` is always rendered,
|
||||
and ignores the `open` state -->
|
||||
<DisclosurePanel static>
|
||||
<AdvancedSearch {data} />
|
||||
</DisclosurePanel>
|
||||
</div>
|
||||
{/if}
|
||||
</Disclosure>
|
||||
{/if}
|
||||
</div>
|
||||
{#if showButton}
|
||||
<button
|
||||
id="search-submit"
|
||||
class="btn"
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
bind:this={submitButton}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
{/if}
|
||||
</form>
|
||||
|
||||
{#if $boredState.loading}
|
||||
<div class="games">
|
||||
<h1>Games Found:</h1>
|
||||
<div class="games-list">
|
||||
{#each placeholderList as game, i}
|
||||
<SkeletonPlaceholder
|
||||
style="width: 100%; height: 500px; border-radius: var(--borderRadius);"
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="games">
|
||||
<h1>Games Found:</h1>
|
||||
<div class="games-list">
|
||||
{#if $gameStore?.length > 0}
|
||||
{#each $gameStore as game (game.id)}
|
||||
<Game
|
||||
on:handleRemoveWishlist={handleRemoveWishlist}
|
||||
on:handleRemoveCollection={handleRemoveCollection}
|
||||
{game}
|
||||
/>
|
||||
{/each}
|
||||
{:else}
|
||||
<h2>Sorry no games found!</h2>
|
||||
{/if}
|
||||
</div>
|
||||
{#if showPagination && $gameStore?.length > 0}
|
||||
<Pagination
|
||||
{pageSize}
|
||||
{page}
|
||||
{totalItems}
|
||||
forwardText="Next"
|
||||
backwardText="Prev"
|
||||
pageSizes={[10, 25, 50, 100]}
|
||||
on:nextPageEvent={handleNextPageEvent}
|
||||
on:previousPageEvent={handlePreviousPageEvent}
|
||||
on:perPageEvent={handlePerPageEvent}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="postcss">
|
||||
.search {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
:global(.disclosure-button) {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 1rem;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
gap: 1rem;
|
||||
place-content: start;
|
||||
place-items: center;
|
||||
|
||||
@media (max-width: 850px) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.games {
|
||||
margin: 2rem 0rem;
|
||||
|
||||
h1 {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.games-list {
|
||||
display: grid;
|
||||
--listColumns: 4;
|
||||
grid-template-columns: repeat(var(--listColumns), minmax(200px, 1fr));
|
||||
gap: 2rem;
|
||||
|
||||
@media screen and (800px < width <= 1200px) {
|
||||
--listColumns: 3;
|
||||
}
|
||||
|
||||
@media screen and (650px < width <= 800px) {
|
||||
--listColumns: 2;
|
||||
}
|
||||
|
||||
@media screen and (width <= 650px) {
|
||||
--listColumns: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
84
src/lib/components/signin.svelte
Normal file
84
src/lib/components/signin.svelte
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
<script lang="ts">
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import { ConicGradient } from '@skeletonlabs/skeleton';
|
||||
import type { ConicStop } from '@skeletonlabs/skeleton';
|
||||
import { i } from "@inlang/sdk-js";
|
||||
import { superForm } from 'sveltekit-superforms/client';
|
||||
//import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
|
||||
import { userSchema } from '$lib/validations/zod-schemas';
|
||||
import { AlertTriangle } from 'lucide-svelte';
|
||||
import { signInSchema } from '$lib/validations/auth';
|
||||
export let data;
|
||||
|
||||
const { form, errors, enhance, delayed } = superForm(data.form, {
|
||||
taintedMessage: null,
|
||||
validators: zodClient(signInSchema),
|
||||
delayMs: 0
|
||||
});
|
||||
const conicStops: ConicStop[] = [
|
||||
{ color: 'transparent', start: 0, end: 25 },
|
||||
{ color: 'rgb(var(--color-primary-900))', start: 75, end: 100 }
|
||||
];
|
||||
</script>
|
||||
|
||||
<form method="POST" action="/sign-in" use:enhance>
|
||||
<!--<SuperDebug data={$form} />-->
|
||||
{#if $errors._errors}
|
||||
<aside class="alert variant-filled-error mt-6">
|
||||
<!-- Icon -->
|
||||
<div><AlertTriangle size="42" /></div>
|
||||
<!-- Message -->
|
||||
<div class="alert-message">
|
||||
<h3 class="h3">{i("signinProblem")}</h3>
|
||||
<p>{$errors._errors}</p>
|
||||
</div>
|
||||
</aside>
|
||||
{/if}
|
||||
<div class="mt-6">
|
||||
<label class="label">
|
||||
<span class="sr-only">{i("email")}</span>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="{i("email")}"
|
||||
autocomplete="email"
|
||||
data-invalid={$errors.email}
|
||||
bind:value={$form.email}
|
||||
class="input"
|
||||
class:input-error={$errors.email}
|
||||
/>
|
||||
{#if $errors.email}
|
||||
<small>{$errors.email}</small>
|
||||
{/if}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<label class="label">
|
||||
<span class="sr-only">{i("password")}</span>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="{i("password")}"
|
||||
data-invalid={$errors.password}
|
||||
bind:value={$form.password}
|
||||
class="input"
|
||||
class:input-error={$errors.password}
|
||||
/>
|
||||
{#if $errors.password}
|
||||
<small>{$errors.password}</small>
|
||||
{/if}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<button type="submit" class="btn variant-filled-primary w-full"
|
||||
>{#if $delayed}<ConicGradient stops={conicStops} spin width="w-6" />{:else}{i("signin")}{/if}</button
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-row justify-center items-center mt-10">
|
||||
<a href="/password/reset" class="font-semibold">{i("forgotPassword")}</a>
|
||||
</div>
|
||||
</form>
|
||||
106
src/lib/components/signup.svelte
Normal file
106
src/lib/components/signup.svelte
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
<script lang="ts">
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import { superForm } from 'sveltekit-superforms/client';
|
||||
import { signUpSchema } from '$lib/validations/auth';
|
||||
|
||||
export let data;
|
||||
|
||||
const { form, errors, enhance } = superForm(data.form, {
|
||||
taintedMessage: null,
|
||||
validators: zodClient(signUpSchema),
|
||||
delayMs: 0
|
||||
});
|
||||
// $: termsValue = $form.terms as Writable<boolean>;
|
||||
</script>
|
||||
|
||||
<form method="POST" action="/sign-up" use:enhance>
|
||||
<h1>Signup user</h1>
|
||||
<label class="label">
|
||||
<span class="sr-only">First Name</span>
|
||||
<input
|
||||
id="firstName"
|
||||
name="firstName"
|
||||
type="text"
|
||||
placeholder="First Name"
|
||||
autocomplete="given-name"
|
||||
data-invalid={$errors.firstName}
|
||||
bind:value={$form.firstName}
|
||||
class="input"
|
||||
class:input-error={$errors.firstName}
|
||||
/>
|
||||
{#if $errors.firstName}
|
||||
<small>{$errors.firstName}</small>
|
||||
{/if}
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="sr-only">Last Name</span>
|
||||
<input
|
||||
id="lastName"
|
||||
name="lastName"
|
||||
type="text"
|
||||
placeholder="Last Name"
|
||||
autocomplete="family-name"
|
||||
data-invalid={$errors.lastName}
|
||||
bind:value={$form.lastName}
|
||||
class="input"
|
||||
class:input-error={$errors.lastName}
|
||||
/>
|
||||
{#if $errors.lastName}
|
||||
<small>{$errors.lastName}</small>
|
||||
{/if}
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="sr-only">Email</span>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
autocomplete="email"
|
||||
data-invalid={$errors.email}
|
||||
bind:value={$form.email}
|
||||
class="input"
|
||||
class:input-error={$errors.email}
|
||||
/>
|
||||
{#if $errors.email}
|
||||
<small>{$errors.email}</small>
|
||||
{/if}
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="sr-only">Username</span>
|
||||
<input
|
||||
id="username"
|
||||
name="username"
|
||||
type="username"
|
||||
placeholder="Username"
|
||||
autocomplete="uername"
|
||||
data-invalid={$errors.username}
|
||||
bind:value={$form.username}
|
||||
class="input"
|
||||
class:input-error={$errors.username}
|
||||
/>
|
||||
{#if $errors.username}
|
||||
<small>{$errors.username}</small>
|
||||
{/if}
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="sr-only">password</span>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="password"
|
||||
data-invalid={$errors.password}
|
||||
bind:value={$form.password}
|
||||
class="input"
|
||||
class:input-error={$errors.password}
|
||||
/>
|
||||
{#if $errors.password}
|
||||
<small>{$errors.password}</small>
|
||||
{/if}
|
||||
</label>
|
||||
|
||||
<button type="submit">Signup</button>
|
||||
|
||||
<a class="back" href="/"> or Cancel </a>
|
||||
</form>
|
||||
98
src/lib/components/socialImageCard.svelte
Normal file
98
src/lib/components/socialImageCard.svelte
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<script lang="ts">
|
||||
export let header: string;
|
||||
export let page: string;
|
||||
export let image: string;
|
||||
export let content: string;
|
||||
export let width = 1200;
|
||||
export let height = 630;
|
||||
export let url: string;
|
||||
</script>
|
||||
|
||||
<div class="social-card" style={`width: ${width}px; height: ${height}px;`}>
|
||||
<div class="social-card-header">
|
||||
<div class="social-card-title">
|
||||
<div class="social-card-image">
|
||||
<img src={image || '/images/bored-game.png'} alt="Bored Game" />
|
||||
</div>
|
||||
<h1>{header}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="social-card-content">
|
||||
<h2 class="page">{page}</h2>
|
||||
<p class="content">{content}</p>
|
||||
</div>
|
||||
<div class="social-card-footer">
|
||||
<footer>
|
||||
<p>Bored Game © {new Date().getFullYear()} | Built by Bradley Shellnut | {url || 'https://boredgame.vercel.app'}</p>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Fira Sans';
|
||||
src: url('/src/lib/FiraSans-Bold.ttf');
|
||||
}
|
||||
|
||||
.social-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
background-color: #DFDBE5;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='64' height='64' viewBox='0 0 64 64' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 16c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8 3.582 8 8 8zm0-2c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6zm33.414-6l5.95-5.95L45.95.636 40 6.586 34.05.636 32.636 2.05 38.586 8l-5.95 5.95 1.414 1.414L40 9.414l5.95 5.95 1.414-1.414L41.414 8zM40 48c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8 3.582 8 8 8zm0-2c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6zM9.414 40l5.95-5.95-1.414-1.414L8 38.586l-5.95-5.95L.636 34.05 6.586 40l-5.95 5.95 1.414 1.414L8 41.414l5.95 5.95 1.414-1.414L9.414 40z' fill='%239C92AC' fill-opacity='0.17' fill-rule='evenodd'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.social-card-header {
|
||||
display: flex;
|
||||
padding: 1.5rem;
|
||||
margin-top: 0.375rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.social-card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
font-size: 1.875rem; /* 30px */
|
||||
line-height: 2.25rem; /* 36px */
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
.social-card-image {
|
||||
display: flex;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.social-card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
padding: 1.5rem;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
font-size: 3.75rem; /* 60px */
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 1.25rem; /* 20px */
|
||||
line-height: 1.75rem; /* 28px */
|
||||
}
|
||||
|
||||
.social-card-footer {
|
||||
display: flex;
|
||||
padding: 1.5rem;
|
||||
padding-top: 0;
|
||||
align-items: center;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { fly, fade } from 'svelte/transition';
|
||||
import { flip } from 'svelte/animate';
|
||||
import Portal from '../../Portal.svelte';
|
||||
import Portal from '$lib/Portal.svelte';
|
||||
import ToastMessage from './ToastMessage.svelte';
|
||||
import { toast } from './toast';
|
||||
</script>
|
||||
|
|
@ -10,11 +10,13 @@
|
|||
<div class="toast-wrapper">
|
||||
{#each $toast as toastData (toastData.id)}
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label={toastData.dismissible ? 'Click to dismiss' : `${toastData.message}`}
|
||||
on:click={() => toastData.dismissible && toast.remove(toastData.id)}
|
||||
on:keydown={() => toastData.dismissible && toast.remove(toastData.id)}
|
||||
in:fly={{ opacity: 0, x: 100 }}
|
||||
out:fade
|
||||
in:fly|global={{ opacity: 0, x: 100 }}
|
||||
out:fade|global
|
||||
animate:flip
|
||||
class={`toast ${toastData.type.toLowerCase()}`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -4,40 +4,40 @@ import { ToastType } from '$lib/types';
|
|||
|
||||
// Custom store
|
||||
const newToast = () => {
|
||||
const { subscribe, update } = writable<ToastData[]>([]);
|
||||
const { subscribe, update } = writable<ToastData[]>([]);
|
||||
|
||||
function send(
|
||||
message: string,
|
||||
{
|
||||
duration = 2000,
|
||||
type = ToastType.INFO,
|
||||
autoDismiss = true,
|
||||
dismissible = false,
|
||||
showButton = false
|
||||
} = {}
|
||||
) {
|
||||
const id = Math.floor(Math.random() * 1000);
|
||||
function send(
|
||||
message: string,
|
||||
{
|
||||
duration = 2000,
|
||||
type = ToastType.INFO,
|
||||
autoDismiss = true,
|
||||
dismissible = false,
|
||||
showButton = false
|
||||
} = {}
|
||||
) {
|
||||
const id = Math.floor(Math.random() * 1000);
|
||||
|
||||
const newMessage: ToastData = {
|
||||
id,
|
||||
duration,
|
||||
autoDismiss,
|
||||
dismissible,
|
||||
showButton,
|
||||
type,
|
||||
message
|
||||
};
|
||||
update((store) => [...store, newMessage]);
|
||||
}
|
||||
const newMessage: ToastData = {
|
||||
id,
|
||||
duration,
|
||||
autoDismiss,
|
||||
dismissible,
|
||||
showButton,
|
||||
type,
|
||||
message
|
||||
};
|
||||
update((store) => [...store, newMessage]);
|
||||
}
|
||||
|
||||
function remove(id: number) {
|
||||
update((store) => {
|
||||
const newStore = store.filter((item: ToastData) => item.id !== id);
|
||||
return [...newStore];
|
||||
});
|
||||
}
|
||||
function remove(id: number) {
|
||||
update((store) => {
|
||||
const newStore = store.filter((item: ToastData) => item.id !== id);
|
||||
return [...newStore];
|
||||
});
|
||||
}
|
||||
|
||||
return { subscribe, send, remove };
|
||||
return { subscribe, send, remove };
|
||||
};
|
||||
|
||||
export const toast = newToast();
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
<script>
|
||||
import { Switch } from '@rgossiaux/svelte-headlessui';
|
||||
// import { Switch } from '@rgossiaux/svelte-headlessui';
|
||||
let enabled = false;
|
||||
</script>
|
||||
|
||||
<Switch
|
||||
<!-- <Switch
|
||||
checked={enabled}
|
||||
on:change={(e) => (enabled = e.detail)}
|
||||
class={enabled ? 'switch switch-enabled' : 'switch switch-disabled'}
|
||||
>
|
||||
<span class="sr-only">Dark Mode</span>
|
||||
<span class="toggle" class:toggle-on={enabled} class:toggle-off={!enabled} />
|
||||
</Switch>
|
||||
</Switch> -->
|
||||
|
||||
<style>
|
||||
:global(.switch) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
interface Transition {
|
||||
type: 'fade' | 'stagger' | 'page';
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
{#if transition.type === 'page' && url}
|
||||
<div class="transition" style="display: grid;">
|
||||
{#key url}
|
||||
<div style="grid-row: 1 / -1; grid-column: 1 / -1;" in:fade={{ duration: 400, delay: 400 }} out:fade={{ duration: 400}}>
|
||||
<div style="grid-row: 1 / -1; grid-column: 1 / -1;" in:fade|global={{ duration: 400, delay: 400 }} out:fade|global={{ duration: 400}}>
|
||||
<slot />
|
||||
</div>
|
||||
{/key}
|
||||
13
src/lib/components/ui/alert/alert-description.svelte
Normal file
13
src/lib/components/ui/alert/alert-description.svelte
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div class={cn("text-sm [&_p]:leading-relaxed", className)} {...$$restProps}>
|
||||
<slot />
|
||||
</div>
|
||||
21
src/lib/components/ui/alert/alert-title.svelte
Normal file
21
src/lib/components/ui/alert/alert-title.svelte
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import type { HeadingLevel } from ".";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLHeadingElement> & {
|
||||
level?: HeadingLevel;
|
||||
};
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let level: $$Props["level"] = "h5";
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<svelte:element
|
||||
this={level}
|
||||
class={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</svelte:element>
|
||||
21
src/lib/components/ui/alert/alert.svelte
Normal file
21
src/lib/components/ui/alert/alert.svelte
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { alertVariants, type Variant } from ".";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement> & {
|
||||
variant?: Variant;
|
||||
};
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let variant: $$Props["variant"] = "default";
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={cn(alertVariants({ variant }), className)}
|
||||
{...$$restProps}
|
||||
role="alert"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
33
src/lib/components/ui/alert/index.ts
Normal file
33
src/lib/components/ui/alert/index.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { tv, type VariantProps } from "tailwind-variants";
|
||||
|
||||
import Root from "./alert.svelte";
|
||||
import Description from "./alert-description.svelte";
|
||||
import Title from "./alert-title.svelte";
|
||||
|
||||
export const alertVariants = tv({
|
||||
base: "relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:text-foreground [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] [&:has(svg)]:pl-11",
|
||||
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-background text-foreground",
|
||||
destructive:
|
||||
"text-destructive border-destructive/50 dark:border-destructive [&>svg]:text-destructive text-destructive"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default"
|
||||
}
|
||||
});
|
||||
|
||||
export type Variant = VariantProps<typeof alertVariants>["variant"];
|
||||
export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Description,
|
||||
Title,
|
||||
//
|
||||
Root as Alert,
|
||||
Description as AlertDescription,
|
||||
Title as AlertTitle
|
||||
};
|
||||
16
src/lib/components/ui/avatar/avatar-fallback.svelte
Normal file
16
src/lib/components/ui/avatar/avatar-fallback.svelte
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = AvatarPrimitive.FallbackProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Fallback
|
||||
class={cn("flex h-full w-full items-center justify-center rounded-full bg-muted", className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</AvatarPrimitive.Fallback>
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue