Comment out collection until I can fix it. Rewrite wishlist, create users with the default role, add seed roles, update the user with the default role if they don't have it. And other things.

This commit is contained in:
Bradley Shellnut 2023-06-15 23:28:49 -07:00
parent 734cee7a29
commit 48c16d7892
28 changed files with 978 additions and 612 deletions

View file

@ -21,36 +21,36 @@
"seed": "ts-node --esm prisma/seed.ts"
},
"devDependencies": {
"@playwright/test": "^1.34.3",
"@playwright/test": "^1.35.1",
"@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.19.0",
"@sveltejs/kit": "^1.20.2",
"@types/cookie": "^0.5.1",
"@types/node": "^18.16.16",
"@typescript-eslint/eslint-plugin": "^5.59.7",
"@typescript-eslint/parser": "^5.59.7",
"@types/node": "^18.16.18",
"@typescript-eslint/eslint-plugin": "^5.59.11",
"@typescript-eslint/parser": "^5.59.11",
"autoprefixer": "^10.4.14",
"eslint": "^8.41.0",
"eslint": "^8.42.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-svelte": "^2.29.0",
"eslint-plugin-svelte": "^2.30.0",
"just-clone": "^6.2.0",
"just-debounce-it": "^3.2.0",
"postcss": "^8.4.23",
"postcss": "^8.4.24",
"postcss-import": "^15.1.0",
"postcss-load-config": "^4.0.1",
"postcss-preset-env": "^8.4.2",
"postcss-preset-env": "^8.5.0",
"prettier": "^2.8.8",
"prettier-plugin-svelte": "^2.10.0",
"prisma": "^4.14.1",
"sass": "^1.62.1",
"prettier-plugin-svelte": "^2.10.1",
"prisma": "^4.15.0",
"sass": "^1.63.4",
"svelte": "^3.59.1",
"svelte-check": "^2.10.3",
"svelte-preprocess": "^5.0.3",
"sveltekit-superforms": "^0.8.7",
"svelte-preprocess": "^5.0.4",
"sveltekit-superforms": "^1.0.0",
"ts-node": "^10.9.1",
"tslib": "^2.5.2",
"tslib": "^2.5.3",
"typescript": "^4.9.5",
"vite": "^4.3.9",
"vitest": "^0.25.3",
@ -64,21 +64,21 @@
"dependencies": {
"@axiomhq/axiom-node": "^0.12.0",
"@fontsource/fira-mono": "^4.5.10",
"@iconify-icons/line-md": "^1.2.22",
"@iconify-icons/line-md": "^1.2.23",
"@iconify-icons/mdi": "^1.2.46",
"@leveluptuts/svelte-side-menu": "^1.0.5",
"@leveluptuts/svelte-toy": "^2.0.3",
"@lucia-auth/adapter-mysql": "^1.1.1",
"@lucia-auth/adapter-prisma": "^2.0.0",
"@lukeed/uuid": "^2.0.1",
"@prisma/client": "4.14.1",
"@prisma/client": "4.15.0",
"@types/feather-icons": "^4.29.1",
"cookie": "^0.5.0",
"feather-icons": "^4.29.0",
"iconify-icon": "^1.0.7",
"loader": "^2.1.1",
"lucia-auth": "^1.7.0",
"open-props": "^1.5.8",
"lucia-auth": "^1.8.0",
"open-props": "^1.5.9",
"svelte-lazy": "^1.2.1",
"svelte-lazy-loader": "^1.0.0",
"svelte-legos": "^0.2.1",

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@ const config = {
plugins: [
atImport(),
postcssPresetEnv({
stage: 3,
stage: 2,
features: {
'nesting-rules': true,
'custom-media-queries': true,

View file

@ -11,20 +11,39 @@ datasource db {
relationMode = "prisma"
}
model Role {
id String @id @default(cuid())
name String @unique
userRoles UserRole[]
}
model UserRole {
id String @id @default(cuid())
user AuthUser @relation(fields: [userId], references: [id])
userId String
role Role @relation(fields: [roleId], references: [id])
roleId String
@@unique([userId, roleId])
@@index([userId])
@@index([roleId])
}
model AuthUser {
id String @id @default(cuid())
username String @unique
email String? @unique
email String? @unique
firstName String?
lastName String?
role Role @default(USER)
roles UserRole[]
verified Boolean @default(false)
receiveEmail Boolean @default(false)
token String? @unique
collection Collection?
wishlist Wishlist[]
createdAt DateTime @default(now()) @db.Timestamp(6)
updatedAt DateTime @updatedAt @db.Timestamp(6)
theme String @default("system")
createdAt DateTime @default(now()) @db.Timestamp(6)
updatedAt DateTime @updatedAt @db.Timestamp(6)
auth_session AuthSession[]
auth_key AuthKey[]
@ -54,11 +73,6 @@ model AuthKey {
@@map("auth_key")
}
enum Role {
USER
ADMIN
}
model Collection {
id String @id @default(cuid())
user_id String @unique

View file

@ -1,31 +1,40 @@
import { PrismaClient } from '@prisma/client';
import userData from '../src/lib/data.json' assert { type: 'json' };
// import userData from '../src/lib/data.json' assert { type: 'json' };
const prisma = new PrismaClient();
async function main() {
console.log(`Start seeding ...`);
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
}
const existingRoles = await prisma.role.findMany();
if (existingRoles.length === 0) {
await prisma.role.createMany({
data: [{ name: 'admin' }, { name: 'user' }]
});
console.log(`Created user with id: ${user.id}`);
console.log('Roles created.');
} else {
console.log('Roles already exist. No action taken.');
}
// 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()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});

1
src/app.d.ts vendored
View file

@ -9,6 +9,7 @@ declare global {
}
interface Locals {
auth: import('lucia-auth').AuthRequest;
prisma: PrismaClient;
user: Lucia.UserAttributes;
startTimer: number;
error: string;

32
src/db/roles.ts Normal file
View file

@ -0,0 +1,32 @@
import prisma from '$lib/prisma';
export async function add_user_to_role(userId: string, roleName: string) {
// Find the role by its name
const role = await prisma.role.findUnique({
where: {
name: roleName
}
});
if (!role) {
throw new Error(`Role with name ${roleName} not found`);
}
// Create a UserRole entry linking the user and the role
const userRole = await prisma.userRole.create({
data: {
user: {
connect: {
id: userId
}
},
role: {
connect: {
id: role.id
}
}
}
});
return userRole;
}

56
src/db/users.ts Normal file
View file

@ -0,0 +1,56 @@
import { auth } from '$lib/server/lucia';
import prisma from '$lib/prisma';
import type { AuthUser } from '@prisma/client';
import { add_user_to_role } from './roles';
export function create_user(user: AuthUser) {
return prisma.authUser.create({
data: {
username: user.username
}
});
}
export async function find_or_create_user(user: AuthUser) {
const existing_user = await prisma.authUser.findUnique({
where: {
username: user.username
}
});
if (existing_user) {
return existing_user;
} else {
const new_user = await create_user(user);
add_user_to_role(new_user.id, 'user');
return new_user;
}
}
export async function find_user_with_roles(user_id: string) {
const user_with_roles = await prisma.authUser.findUnique({
where: {
id: user_id
},
include: {
roles: {
select: {
role: {
select: {
name: true
}
}
}
}
}
});
if (!user_with_roles) {
throw new Error('User not found');
}
const user = {
...user_with_roles,
roles: user_with_roles.roles.map((user_role) => user_role.role.name)
};
return user;
}

View file

@ -1,8 +1,9 @@
import { redirect, type Handle } from '@sveltejs/kit';
import type { HandleServerError } from '@sveltejs/kit';
import { sequence } from '@sveltejs/kit/hooks';
import { redirect, type HandleServerError, type Handle } from '@sveltejs/kit';
import { dev } from '$app/environment';
import { auth } from '$lib/server/lucia';
import log from '$lib/server/log';
import { dev } from '$app/environment';
import prisma from '$lib/config/prisma';
export const handleError: HandleServerError = async ({ error, event }) => {
const errorId = crypto.randomUUID();
@ -24,24 +25,28 @@ export const handleError: HandleServerError = async ({ error, event }) => {
};
};
export const handle: Handle = async ({ event, resolve }) => {
// export const prismaClient: Handle = async function ({ event, resolve }) {
// event.locals.prisma = prisma;
// const response = await resolve(event);
// return response;
// };
export const authentication: Handle = async function ({ event, resolve }) {
const startTimer = Date.now();
event.locals.startTimer = startTimer;
event.locals.auth = auth.handleRequest(event);
console.log(JSON.stringify(event));
if (event.locals?.auth) {
const { user } = await event.locals.auth.validateUser();
event.locals.user = user;
if (event.route.id?.startsWith('/(protected)')) {
if (!user) throw redirect(302, '/auth/sign-in');
if (!user.verified) throw redirect(302, '/auth/verify/email');
}
// if (event.route.id?.startsWith('/(protected)')) {
// if (!user) throw redirect(302, '/auth/sign-in');
// if (!user.verified) throw redirect(302, '/auth/verify/email');
// }
}
const response = await resolve(event);
if (!dev) {
log(response.status, event);
}
return response;
};
export const handle = sequence(authentication);

View file

@ -1,6 +1,6 @@
<script lang="ts">
import { enhance } from '$app/forms';
import Profile from '../preferences/profile.svelte';
// import Profile from '../preferences/profile.svelte';
import logo from './bored-game.png';
export let user: any;
@ -15,9 +15,9 @@
</div>
<!-- <TextSearch /> -->
<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>
{#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>
<form
use:enhance
action="/auth/signout"
@ -36,11 +36,11 @@
<span class="flex-auto">Sign Up</span></a
>
{/if}
<Profile />
<!-- <Profile /> -->
</nav>
</header>
<style lang="scss">
<style lang="postcss">
header {
display: flex;
justify-content: space-between;
@ -88,7 +88,6 @@
padding: 0 1em;
color: var(--heading-color);
font-weight: 700;
/* font-size: 0.8rem; */
text-transform: uppercase;
letter-spacing: 0.1em;
text-decoration: none;

View file

@ -1,5 +1,5 @@
<script lang="ts">
import type { Validation } from 'sveltekit-superforms/index';
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';
@ -7,7 +7,7 @@
import { superForm } from 'sveltekit-superforms/client';
import { toast } from '../../toast/toast';
export let data: Validation<SearchSchema>;
export let data: SuperValidated<SearchSchema>;
const { enhance } = superForm(data, {
onSubmit: () => {
gameStore.removeAll();

View file

@ -4,7 +4,7 @@
import { applyAction, type SubmitFunction } from '$app/forms';
import { superForm } from 'sveltekit-superforms/client';
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
import type { Validation } from 'sveltekit-superforms/index';
import type { SuperValidated } from 'sveltekit-superforms/index';
import { Disclosure, DisclosureButton, DisclosurePanel } from '@rgossiaux/svelte-headlessui';
import { ChevronRightIcon } from '@rgossiaux/svelte-heroicons/solid';
import { boredState } from '$lib/stores/boredState';
@ -15,7 +15,7 @@
import Pagination from '$lib/components/pagination/index.svelte';
import Game from '$lib/components/game/index.svelte';
import { ToastType, type GameType, type SavedGameType } from '$lib/types';
import SkeletonPlaceholder from '../../SkeletonPlaceholder.svelte';
// import SkeletonPlaceholder from '../../SkeletonPlaceholder.svelte';
import RemoveCollectionDialog from '../../dialog/RemoveCollectionDialog.svelte';
import RemoveWishlistDialog from '../../dialog/RemoveWishlistDialog.svelte';
import type { SearchSchema } from '$lib/zodValidation';
@ -24,7 +24,7 @@
detail: GameType | SavedGameType;
}
export let data: Validation<SearchSchema>;
export let data: SuperValidated<SearchSchema>;
const { form, constraints, errors } = superForm(data, {
onSubmit: () => {
boredState.update((n) => ({ ...n, loading: true }));
@ -213,11 +213,11 @@
<div class="games">
<h1>Games Found:</h1>
<div class="games-list">
{#each placeholderList as game, i}
<!-- {#each placeholderList as game, i}
<SkeletonPlaceholder
style="width: 100%; height: 500px; border-radius: var(--borderRadius);"
/>
{/each}
{/each} -->
</div>
</div>
{:else}

View file

@ -5,10 +5,8 @@ import prisma from '@lucia-auth/adapter-prisma';
import { PrismaClient } from '@prisma/client';
import { dev } from '$app/environment';
const client = new PrismaClient();
export const auth = lucia({
adapter: prisma(client),
adapter: prisma(new PrismaClient()),
env: dev ? 'DEV' : 'PROD',
middleware: sveltekit(),
transformDatabaseUser: (userData) => {
@ -23,6 +21,9 @@ export const auth = lucia({
receiveEmail: userData.receiveEmail,
token: userData.token
};
},
experimental: {
debugMode: false
}
});

View file

@ -16,6 +16,13 @@ export const saved_game_schema = z.object({
playtime: IntegerString(z.number())
});
export const list_game_request_schema = z.object({
id: z.string(),
externalId: z.string()
});
export type ListGameSchema = typeof list_game_request_schema;
// https://github.com/colinhacks/zod/discussions/330
function IntegerString<schema extends ZodNumber | ZodOptional<ZodNumber>>(schema: schema) {
return z.preprocess(

View file

@ -75,7 +75,7 @@
<Analytics />
{/if}
{#if dev}
<!-- {#if dev}
<Toy
register={{
boredState,
@ -85,7 +85,7 @@
toast
}}
/>
{/if}
{/if} -->
<div class="wrapper">
<Header user={data.user} />

View file

@ -4,6 +4,8 @@
import Random from '$lib/components/random/index.svelte';
export let data;
export let formData;
console.log('formData', formData);
</script>
<svelte:head>

View file

@ -0,0 +1,5 @@
import { redirect } from '@sveltejs/kit';
export const load = async function ({ locals }) {
if (!locals?.user?.role?.includes('admin')) throw redirect(302, '/');
};

View file

@ -0,0 +1 @@
<slot />

View file

View file

@ -1,7 +1,9 @@
import { fail, redirect } from '@sveltejs/kit';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { auth } from '$lib/server/lucia';
import prisma from '$lib/prisma.js';
import { userSchema } from '$lib/config/zod-schemas';
import { add_user_to_role } from '$db/roles';
const signInSchema = userSchema.pick({
username: true,
@ -13,10 +15,10 @@ export const load = async (event) => {
if (session) {
throw redirect(302, '/');
}
// const form = await superValidate(event, signInSchema);
// return {
// form
// };
const form = await superValidate(event, signInSchema);
return {
form
};
};
export const actions = {
@ -30,16 +32,32 @@ export const actions = {
});
}
// Adding user to the db
try {
const key = await auth.useKey('username', form.data.username, form.data.password);
const session = await auth.createSession(key.userId);
event.locals.auth.setSession(session);
const user = await prisma.authUser.findUnique({
where: {
id: session.userId
},
include: {
roles: {
select: {
role: true
}
}
}
});
if (user && user.roles.length === 0) {
add_user_to_role(user.id, 'user');
}
} catch (e) {
// TODO: need to return error message to the client
console.error(e);
form.data.password = '';
return setError(form, null, 'The username or password is incorrect.');
return setError(form, '', 'The username or password is incorrect.');
}
form.data.username = '';
form.data.password = '';

View file

@ -3,6 +3,7 @@
import { userSchema } from '$lib/config/zod-schemas.js';
export let data;
const signInSchema = userSchema.pick({ username: true, password: true });
const { form, errors, enhance, delayed } = superForm(data.form, {
taintedMessage: null,

View file

@ -2,6 +2,7 @@ import { fail, redirect } from '@sveltejs/kit';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { auth } from '$lib/server/lucia';
import { userSchema } from '$lib/config/zod-schemas';
import { add_user_to_role } from '$db/roles';
const signUpSchema = userSchema.pick({
firstName: true,
@ -55,6 +56,7 @@ export const actions = {
token
}
});
add_user_to_role(user.id, 'user');
console.log('User', user);

View file

@ -1,14 +1,65 @@
import type { PageServerLoad } from "../$types";
// import { redirect } from '@sveltejs/kit';
// import { superValidate } from 'sveltekit-superforms/server';
// import { search_schema } from '$lib/zodValidation';
export const load: PageServerLoad = async ({ fetch, url }) => {
const searchParams = Object.fromEntries(url?.searchParams);
const q = searchParams?.q;
const limit = parseInt(searchParams?.limit) || 10;
const skip = parseInt(searchParams?.skip) || 0;
export const load = async ({ fetch, url, locals }) => {
// const session = await locals.auth.validate();
// if (!session) {
// throw redirect(302, '/auth/signin');
// }
console.log('locals load', locals);
// const searchParams = Object.fromEntries(url?.searchParams);
// const q = searchParams?.q;
// const limit = parseInt(searchParams?.limit) || 10;
// const skip = parseInt(searchParams?.skip) || 0;
// const searchData = {
// q,
// limit,
// skip
// };
// const form = await superValidate(searchData, search_schema);
try {
// let collection = await locals.prisma.collection.findUnique({
// where: {
// user_id: session.userId
// }
// include: {
// collectionItems: {
// where: {
// title: {
// contains: q,
// mode: 'insensitive'
// }
// },
// take: limit,
// skip
// }
// }
// });
// console.log('collection', collection);
// if (!collection) {
// collection = await locals.prisma.collection.create({
// data: {
// user_id: session.userId
// }
// });
// }
return {
// form,
// collection
};
} catch (e) {
console.error(e);
}
return {
q,
limit,
skip
// form,
// collection: []
};
};

View file

@ -1,78 +1,78 @@
<script lang="ts">
import { tick, onDestroy } from 'svelte';
import Game from '$lib/components/game/index.svelte';
import { collectionStore } from '$lib/stores/collectionStore';
import type { GameType, SavedGameType } from '$lib/types';
import { boredState } from '$lib/stores/boredState';
import Pagination from '$lib/components/pagination/index.svelte';
import RemoveCollectionDialog from '$lib/components/dialog/RemoveCollectionDialog.svelte';
import RemoveWishlistDialog from '$lib/components/dialog/RemoveWishlistDialog.svelte';
import { createSearchStore, searchHandler } from '$lib/stores/search';
import type { PageData } from './$types';
// import { tick, onDestroy } from 'svelte';
// import Game from '$lib/components/game/index.svelte';
// import { collectionStore } from '$lib/stores/collectionStore';
// import type { GameType, SavedGameType } from '$lib/types';
// import { boredState } from '$lib/stores/boredState';
// import Pagination from '$lib/components/pagination/index.svelte';
// import RemoveCollectionDialog from '$lib/components/dialog/RemoveCollectionDialog.svelte';
// import RemoveWishlistDialog from '$lib/components/dialog/RemoveWishlistDialog.svelte';
// import { createSearchStore, searchHandler } from '$lib/stores/search';
export let data: PageData;
console.log(`Page data: ${JSON.stringify(data)}`)
export let data;
console.log(`Page data: ${JSON.stringify(data)}`);
// let collectionItems = data?.collection?.collectionItems;
let gameToRemove: GameType | SavedGameType;
let pageSize = 10;
let page = 1;
// let gameToRemove: GameType | SavedGameType;
// let pageSize = 10;
// let page = 1;
const searchStore = createSearchStore($collectionStore);
console.log('searchStore', $searchStore);
// const searchStore = createSearchStore($collectionStore);
// console.log('searchStore', $searchStore);
const unsubscribe = searchStore.subscribe((model) => searchHandler(model));
// const unsubscribe = searchStore.subscribe((model) => searchHandler(model));
onDestroy(() => {
unsubscribe();
});
// onDestroy(() => {
// unsubscribe();
// });
$: skip = (page - 1) * pageSize;
$: gamesShown = $searchStore.data.slice(skip, skip + pageSize);
$: totalItems = $searchStore.search === '' ? $collectionStore.length : $searchStore.filtered.length;
// $: skip = (page - 1) * pageSize;
// $: gamesShown = $searchStore.data.slice(skip, skip + pageSize);
// $: totalItems = $searchStore.search === '' ? $collectionStore.length : $searchStore.filtered.length;
interface RemoveGameEvent extends Event {
detail: GameType | SavedGameType;
}
// interface RemoveGameEvent extends Event {
// detail: GameType | SavedGameType;
// }
function handleRemoveCollection(event: RemoveGameEvent) {
console.log('Remove collection event handler');
console.log('event', event);
gameToRemove = event?.detail;
boredState.update((n) => ({
...n,
dialog: { isOpen: true, content: RemoveCollectionDialog, additionalData: gameToRemove }
}));
}
// function handleRemoveCollection(event: RemoveGameEvent) {
// console.log('Remove collection event handler');
// console.log('event', event);
// gameToRemove = event?.detail;
// boredState.update((n) => ({
// ...n,
// dialog: { isOpen: true, content: RemoveCollectionDialog, additionalData: gameToRemove }
// }));
// }
function handleRemoveWishlist(event: RemoveGameEvent) {
console.log('Remove wishlist event handler');
console.log('event', event);
gameToRemove = event?.detail;
boredState.update((n) => ({
...n,
dialog: { isOpen: true, content: RemoveWishlistDialog, additionalData: gameToRemove }
}));
}
// function handleRemoveWishlist(event: RemoveGameEvent) {
// console.log('Remove wishlist event handler');
// console.log('event', event);
// gameToRemove = event?.detail;
// boredState.update((n) => ({
// ...n,
// dialog: { isOpen: true, content: RemoveWishlistDialog, additionalData: gameToRemove }
// }));
// }
async function handleNextPageEvent(event: CustomEvent) {
if (+event?.detail?.page === page + 1) {
page += 1;
}
await tick();
}
// async function handleNextPageEvent(event: CustomEvent) {
// if (+event?.detail?.page === page + 1) {
// page += 1;
// }
// await tick();
// }
async function handlePreviousPageEvent(event: CustomEvent) {
if (+event?.detail?.page === page - 1) {
page -= 1;
}
await tick();
}
// async function handlePreviousPageEvent(event: CustomEvent) {
// if (+event?.detail?.page === page - 1) {
// page -= 1;
// }
// await tick();
// }
async function handlePerPageEvent(event: CustomEvent) {
page = 1;
pageSize = event.detail.pageSize;
await tick();
}
// async function handlePerPageEvent(event: CustomEvent) {
// page = 1;
// pageSize = event.detail.pageSize;
// await tick();
// }
</script>
<svelte:head>
@ -80,9 +80,9 @@
</svelte:head>
<h1>Your Collection</h1>
<input type="text" id="search" name="search" placeholder="Search Your Collection" bind:value={$searchStore.search} />
<!-- <input type="text" id="search" name="search" placeholder="Search Your Collection" bind:value={$searchStore.search} /> -->
<div class="games">
<!-- <div class="games">
<div class="games-list">
{#if $collectionStore.length === 0}
<h2>No games in your collection</h2>
@ -109,9 +109,9 @@
on:perPageEvent={handlePerPageEvent}
/>
{/if}
</div>
</div> -->
<style lang="scss">
<style lang="postcss">
h1 {
margin: 1.5rem 0rem;
width: 100%;

View file

@ -205,7 +205,7 @@
margin: 1rem;
place-items: center;
@media (max-width: env(--medium-viewport)) {
@media (max-width: 700px) {
grid-template-columns: 1fr;
place-items: center;
}
@ -220,7 +220,7 @@
margin: 1rem;
}
@media (max-width: env(--xsmall-viewport)) {
@media (max-width: 500px) {
grid-template-columns: 1fr;
}
}

View file

@ -0,0 +1,95 @@
import { redirect } from '@sveltejs/kit';
import { superValidate } from 'sveltekit-superforms/server';
import prisma from '$lib/prisma.js';
import { list_game_request_schema } from '$lib/zodValidation';
export async function load({ params, locals }) {
const session = await locals.auth.validate();
if (!session) {
throw redirect(302, '/auth/signin');
}
try {
let wishlists = await prisma.wishlist.findMany({
where: {
user_id: session.userId
},
include: {
items: true
}
});
if (wishlists.length === 0) {
const wishlist = await prisma.wishlist.create({
data: {
user_id: session.userId,
name: 'My Wishlist'
}
});
wishlists.push(wishlist);
}
return {
wishlists
};
} catch (e) {
console.error(e);
}
}
export const actions = {
// Add game to a wishlist
add: async (event) => {
const { params, locals, request } = event;
const form = await superValidate(event, list_game_request_schema);
const session = await locals.auth.validate();
if (!session) {
throw redirect(302, '/auth/signin');
}
const game = await prisma.game.findUnique({
where: {
id: form.id
}
});
// if (!game) {
// throw redirect(302, '/404');
// }
if (game) {
const wishlist = await prisma.wishlist.create({
data: {
user_id: session.userId,
name: form.name
}
});
}
return {
form
};
},
// Create new wishlist
create: async ({ params, locals, request }) => {
const session = await locals.auth.validate();
if (!session) {
throw redirect(302, '/auth/signin');
}
},
// Delete a wishlist
delete: async ({ params, locals, request }) => {
const session = await locals.auth.validate();
if (!session) {
throw redirect(302, '/auth/signin');
}
},
// Remove game from a wishlist
remove: async ({ params, locals, request }) => {
const session = await locals.auth.validate();
if (!session) {
throw redirect(302, '/auth/signin');
}
}
};

View file

@ -1,70 +1,72 @@
<script lang="ts">
import { tick, onDestroy } from 'svelte';
import Game from '$lib/components/game/index.svelte';
import { wishlistStore } from '$lib/stores/wishlistStore';
import type { GameType, SavedGameType } from '$lib/types';
import { boredState } from '$lib/stores/boredState';
import Pagination from '$lib/components/pagination/index.svelte';
import RemoveWishlistDialog from '$lib/components/dialog/RemoveWishlistDialog.svelte';
import RemoveCollectionDialog from '$lib/components/dialog/RemoveCollectionDialog.svelte';
import { createSearchStore, searchHandler } from '$lib/stores/search';
// import { tick, onDestroy } from 'svelte';
// import Game from '$lib/components/game/index.svelte';
// import { wishlistStore } from '$lib/stores/wishlistStore';
// import type { GameType, SavedGameType } from '$lib/types';
// import { boredState } from '$lib/stores/boredState';
// import Pagination from '$lib/components/pagination/index.svelte';
// import RemoveWishlistDialog from '$lib/components/dialog/RemoveWishlistDialog.svelte';
// import RemoveCollectionDialog from '$lib/components/dialog/RemoveCollectionDialog.svelte';
// import { createSearchStore, searchHandler } from '$lib/stores/search';
let gameToRemove: GameType | SavedGameType;
let pageSize = 10;
let page = 1;
// let gameToRemove: GameType | SavedGameType;
// let pageSize = 10;
// let page = 1;
const searchStore = createSearchStore($wishlistStore);
console.log('searchStore', $searchStore);
// const searchStore = createSearchStore($wishlistStore);
// console.log('searchStore', $searchStore);
const unsubscribe = searchStore.subscribe((model) => searchHandler(model));
// const unsubscribe = searchStore.subscribe((model) => searchHandler(model));
onDestroy(() => {
unsubscribe();
});
// onDestroy(() => {
// unsubscribe();
// });
$: skip = (page - 1) * pageSize;
$: gamesShown = $searchStore.filtered.slice(skip, skip + pageSize);
$: totalItems = $searchStore.search === '' ? $wishlistStore.length : $searchStore.filtered.length;
// $: skip = (page - 1) * pageSize;
// $: gamesShown = $searchStore.filtered.slice(skip, skip + pageSize);
// $: totalItems = $searchStore.search === '' ? $wishlistStore.length : $searchStore.filtered.length;
interface RemoveGameEvent extends Event {
detail: GameType | SavedGameType;
}
// interface RemoveGameEvent extends Event {
// detail: GameType | SavedGameType;
// }
function handleRemoveCollection(event: RemoveGameEvent) {
gameToRemove = event?.detail;
boredState.update((n) => ({
...n,
dialog: { isOpen: true, content: RemoveCollectionDialog, additionalData: gameToRemove }
}));
}
// 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 }
}));
}
// function handleRemoveWishlist(event: RemoveGameEvent) {
// gameToRemove = event?.detail;
// boredState.update((n) => ({
// ...n,
// dialog: { isOpen: true, content: RemoveWishlistDialog, additionalData: gameToRemove }
// }));
// }
async function handleNextPageEvent(event: CustomEvent) {
if (+event?.detail?.page === page + 1) {
page += 1;
}
await tick();
}
// async function handleNextPageEvent(event: CustomEvent) {
// if (+event?.detail?.page === page + 1) {
// page += 1;
// }
// await tick();
// }
async function handlePreviousPageEvent(event: CustomEvent) {
if (+event?.detail?.page === page - 1) {
page -= 1;
}
await tick();
}
// async function handlePreviousPageEvent(event: CustomEvent) {
// if (+event?.detail?.page === page - 1) {
// page -= 1;
// }
// await tick();
// }
async function handlePerPageEvent(event: CustomEvent) {
page = 1;
pageSize = event.detail.pageSize;
await tick();
}
// async function handlePerPageEvent(event: CustomEvent) {
// page = 1;
// pageSize = event.detail.pageSize;
// await tick();
// }
export let data;
const wishlists = data.wishlists || [];
</script>
<svelte:head>
@ -72,7 +74,10 @@
</svelte:head>
<h1>Your Wishlist</h1>
<input type="text" id="search" name="search" placeholder="Search Your Wishlist" bind:value={$searchStore.search} />
{#each wishlists as wishlist}
<h2>{wishlist.name}</h2>
{/each}
<!-- <input type="text" id="search" name="search" placeholder="Search Your Wishlist" bind:value={$searchStore.search} />
<div class="games">
<div class="games-list">
@ -101,7 +106,7 @@
on:perPageEvent={handlePerPageEvent}
/>
{/if}
</div>
</div> -->
<style lang="scss">
h1 {

View file

@ -6,22 +6,18 @@ import { vitePreprocess } from '@sveltejs/kit/vite';
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: [
vitePreprocess({
postcss: true
}),
preprocess({
postcss: true
})
],
preprocess: [vitePreprocess()],
vitePlugin: {
inspector: true,
},
kit: {
adapter: adapter(),
alias: {
$db: './src/db',
$assets: './src/assets',
$lib: './src/lib',
$styles: './src/styles',
$themes: './src/themes'
}
},
};