first commit

This commit is contained in:
Omer 2023-04-20 20:37:55 +02:00
parent dcc30ded70
commit f72df76a02
39 changed files with 4594 additions and 178 deletions

View File

@ -2,7 +2,7 @@
"useTabs": true, "useTabs": true,
"singleQuote": true, "singleQuote": true,
"trailingComma": "none", "trailingComma": "none",
"semi": false, "semi": true,
"printWidth": 100, "printWidth": 100,
"plugins": [ "plugins": [
"prettier-plugin-svelte" "prettier-plugin-svelte"

3383
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
{ {
"name": "sk-prisma-quickstart", "name": "mind.am",
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"scripts": { "scripts": {
@ -24,7 +24,7 @@
"eslint-plugin-svelte3": "^4.0.0", "eslint-plugin-svelte3": "^4.0.0",
"prettier": "^2.8.3", "prettier": "^2.8.3",
"prettier-plugin-svelte": "^2.9.0", "prettier-plugin-svelte": "^2.9.0",
"prisma": "^4.9.0", "prisma": "^4.13.0",
"rome": "^11.0.0", "rome": "^11.0.0",
"svelte": "^3.55.1", "svelte": "^3.55.1",
"svelte-check": "^3.0.3", "svelte-check": "^3.0.3",
@ -35,10 +35,8 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@lucia-auth/adapter-prisma": "^0.4.0", "@lucia-auth/adapter-prisma": "^1.0.0",
"@lucia-auth/sveltekit": "^0.6.2", "@prisma/client": "^4.13.0",
"@picocss/pico": "^1.5.7", "lucia-auth": "^1.1.0"
"@prisma/client": "^4.9.0",
"lucia-auth": "^0.6.0"
} }
} }

377
plan.excalidraw Normal file
View File

@ -0,0 +1,377 @@
{
"type": "excalidraw",
"version": 2,
"source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor",
"elements": [
{
"type": "text",
"version": 210,
"versionNonce": 1101517802,
"isDeleted": false,
"id": "BBh4tqJDdAtmiGS_T9NhB",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 346,
"y": 132,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 386.299560546875,
"height": 175,
"seed": 173011999,
"groupIds": [],
"roundness": null,
"boundElements": [],
"updated": 1681666786640,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "Main features\n- view tasks in list\n- add tasks\n- filter tasks\n- add events\n- view events on a calendar/time table\n- productivity stats",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Main features\n- view tasks in list\n- add tasks\n- filter tasks\n- add events\n- view events on a calendar/time table\n- productivity stats",
"lineHeight": 1.25,
"baseline": 168
},
{
"type": "text",
"version": 68,
"versionNonce": 1783641974,
"isDeleted": false,
"id": "yhIvmdxt78nVjxVMbg817",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 346.42857142857133,
"y": 466.7142857142858,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 243.93699645996094,
"height": 35,
"seed": 145102353,
"groupIds": [],
"roundness": null,
"boundElements": [],
"updated": 1681658965824,
"link": null,
"locked": false,
"fontSize": 28,
"fontFamily": 1,
"text": "View Tasks in list",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "View Tasks in list",
"lineHeight": 1.25,
"baseline": 25
},
{
"type": "rectangle",
"version": 136,
"versionNonce": 1209570673,
"isDeleted": false,
"id": "GJ07SGnaoG015S8qKJLRH",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 359.3078862861435,
"y": 545.6229442427883,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 158.20707565456237,
"height": 297.1927309024958,
"seed": 773169521,
"groupIds": [
"58NIHvz3-h0w-wpDv2qvX"
],
"roundness": null,
"boundElements": [],
"updated": 1681652636015,
"link": null,
"locked": false
},
{
"type": "text",
"version": 53,
"versionNonce": 1166399670,
"isDeleted": false,
"id": "sgV5gLSoJU8ahlPzVWirQ",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 363.7435986876732,
"y": 545.6229442427883,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 148.82061767578125,
"height": 35,
"seed": 266673009,
"groupIds": [
"58NIHvz3-h0w-wpDv2qvX"
],
"roundness": null,
"boundElements": [],
"updated": 1681658965826,
"link": null,
"locked": false,
"fontSize": 28,
"fontFamily": 1,
"text": "Task name",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Task name",
"lineHeight": 1.25,
"baseline": 25
},
{
"type": "rectangle",
"version": 293,
"versionNonce": 803399999,
"isDeleted": false,
"id": "Ot3R5vdTppcVPfNkkWKAf",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 744.8648696156656,
"y": 546.7515194397288,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 158.20707565456237,
"height": 297.1927309024958,
"seed": 270974801,
"groupIds": [
"4ig37QJCWH5WzEsDoZa_9"
],
"roundness": null,
"boundElements": [],
"updated": 1681652677439,
"link": null,
"locked": false
},
{
"type": "text",
"version": 218,
"versionNonce": 552187306,
"isDeleted": false,
"id": "QssO4ADI4QybG77uAi4T5",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 749.3005820171953,
"y": 546.7515194397288,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 113.6524658203125,
"height": 35,
"seed": 2049202751,
"groupIds": [
"4ig37QJCWH5WzEsDoZa_9"
],
"roundness": null,
"boundElements": [],
"updated": 1681658965826,
"link": null,
"locked": false,
"fontSize": 28,
"fontFamily": 1,
"text": "Deadline",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Deadline",
"lineHeight": 1.25,
"baseline": 25
},
{
"type": "rectangle",
"version": 334,
"versionNonce": 1486707231,
"isDeleted": false,
"id": "3hVNY2-d-wsWmq25MKBJF",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 902.7219496666585,
"y": 544.9229530356494,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 84.27853562906617,
"height": 297.1927309024958,
"seed": 333160465,
"groupIds": [
"-7vVeIJHP7GrJWtZlI9Qp"
],
"roundness": null,
"boundElements": [],
"updated": 1681652691941,
"link": null,
"locked": false
},
{
"type": "text",
"version": 240,
"versionNonce": 66408950,
"isDeleted": false,
"id": "C0Sl-rFjn7SxXayob92Gz",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 907.1576620681882,
"y": 544.9229530356494,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 74.87232971191406,
"height": 35,
"seed": 1631249791,
"groupIds": [
"-7vVeIJHP7GrJWtZlI9Qp"
],
"roundness": null,
"boundElements": [],
"updated": 1681658965827,
"link": null,
"locked": false,
"fontSize": 28,
"fontFamily": 1,
"text": "Check",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Check",
"lineHeight": 1.25,
"baseline": 25
},
{
"type": "rectangle",
"version": 329,
"versionNonce": 1935340159,
"isDeleted": false,
"id": "uDpNt6DRsp_-nRDXNaA8k",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 518.2935415340769,
"y": 544.9229530356494,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 224.74276167750924,
"height": 297.1927309024958,
"seed": 1020428785,
"groupIds": [
"KoZaThEPF0xicVDvsXM6v"
],
"roundness": null,
"boundElements": [],
"updated": 1681652674302,
"link": null,
"locked": false
},
{
"type": "text",
"version": 267,
"versionNonce": 1580822634,
"isDeleted": false,
"id": "OP-njsWFGJLXrTxK6HgOK",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 522.7292539356066,
"y": 544.9229530356494,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 214.95692443847656,
"height": 35,
"seed": 285446559,
"groupIds": [
"KoZaThEPF0xicVDvsXM6v"
],
"roundness": null,
"boundElements": [],
"updated": 1681658965828,
"link": null,
"locked": false,
"fontSize": 28,
"fontFamily": 1,
"text": "Content preview",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Content preview",
"lineHeight": 1.25,
"baseline": 25
},
{
"type": "text",
"version": 188,
"versionNonce": 1352901430,
"isDeleted": false,
"id": "rxmez1KN1SGu8hwPP2lre",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 360.7864570866533,
"y": 869.4299495544628,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 974.8241577148438,
"height": 105,
"seed": 558546495,
"groupIds": [],
"roundness": null,
"boundElements": [],
"updated": 1681658965829,
"link": null,
"locked": false,
"fontSize": 28,
"fontFamily": 1,
"text": "Events:\n- Clicking it should bring up a full window showing the full content\n- Pressing check should gray out the task and move it to the bottom",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Events:\n- Clicking it should bring up a full window showing the full content\n- Pressing check should gray out the task and move it to the bottom",
"lineHeight": 1.25,
"baseline": 95
}
],
"appState": {
"gridSize": null,
"viewBackgroundColor": "#ffffff"
},
"files": {}
}

View File

@ -0,0 +1,81 @@
-- CreateTable
CREATE TABLE "Task" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT,
"deadline" TIMESTAMP(3),
"userId" TEXT NOT NULL,
CONSTRAINT "Task_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Event" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT,
"length" INTEGER NOT NULL,
"start_time" TIMESTAMP(3) NOT NULL,
"userId" TEXT NOT NULL,
CONSTRAINT "Event_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "auth_user" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"username" TEXT NOT NULL,
CONSTRAINT "auth_user_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "auth_session" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"active_expires" BIGINT NOT NULL,
"idle_expires" BIGINT NOT NULL,
CONSTRAINT "auth_session_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "auth_key" (
"primary_id" TEXT NOT NULL,
"hashed_password" TEXT,
"user_id" TEXT NOT NULL,
"primary" BOOLEAN NOT NULL,
CONSTRAINT "auth_key_pkey" PRIMARY KEY ("primary_id")
);
-- CreateIndex
CREATE UNIQUE INDEX "auth_user_id_key" ON "auth_user"("id");
-- CreateIndex
CREATE UNIQUE INDEX "auth_user_username_key" ON "auth_user"("username");
-- CreateIndex
CREATE UNIQUE INDEX "auth_session_id_key" ON "auth_session"("id");
-- CreateIndex
CREATE INDEX "auth_session_user_id_idx" ON "auth_session"("user_id");
-- CreateIndex
CREATE UNIQUE INDEX "auth_key_primary_id_key" ON "auth_key"("primary_id");
-- CreateIndex
CREATE INDEX "auth_key_user_id_idx" ON "auth_key"("user_id");
-- AddForeignKey
ALTER TABLE "Task" ADD CONSTRAINT "Task_userId_fkey" FOREIGN KEY ("userId") REFERENCES "auth_user"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Event" ADD CONSTRAINT "Event_userId_fkey" FOREIGN KEY ("userId") REFERENCES "auth_user"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "auth_session" ADD CONSTRAINT "auth_session_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth_user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "auth_key" ADD CONSTRAINT "auth_key_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth_user"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"

View File

@ -6,47 +6,95 @@ generator client {
} }
datasource db { datasource db {
provider = "sqlite" provider = "postgres"
url = "file:./dev.sqlite" url = "postgresql://postgres:b359a3f0b1129da7@141.147.61.193:3021/postgres"
} }
model Article { model Song {
id Int @id @default(autoincrement()) id String @id @default(uuid())
title String name String
content String image String
User User @relation(fields: [userId], references: [id]) hasMultipleNELs Boolean
userId String neuralEffectLevel Float
instrumentations String[]
genre Genre @relation(fields: [genreId], references: [id])
genreId String
complexityDisplayValue ComplexityLevel
neuralEffectLevelDisplayValue NELevel
usersFavorited AuthUser? @relation(fields: [usersFavoritedId], references: [id])
usersFavoritedId String?
} }
model User { model MentalState {
id String @id @unique id String @id @default(uuid())
name String name String
username String @unique
articles Article[]
session Session[]
Key Key[]
@@map("user") genres Genre[]
} }
model Session { model Genre {
id String @id @unique id String @id @default(uuid())
name String
mentalState MentalState[]
isNature Boolean
activities Activity[]
Song Song[]
}
model Activity {
id String @id @default(uuid())
name String
genre Genre @relation(fields: [genreId], references: [id])
genreId String
}
model AuthUser {
id String @id @unique
name String
username String @unique
favorites Song[]
auth_session AuthSession[]
auth_key AuthKey[]
@@map("auth_user")
}
model AuthSession {
id String @id @unique
user_id String user_id String
active_expires BigInt active_expires BigInt
idle_expires BigInt idle_expires BigInt
user User @relation(references: [id], fields: [user_id], onDelete: Cascade) auth_user AuthUser @relation(references: [id], fields: [user_id], onDelete: Cascade)
@@index([user_id]) @@index([user_id])
@@map("session") @@map("auth_session")
} }
model Key { model AuthKey {
id String @id @unique id String @id @unique
hashed_password String? hashed_password String?
user_id String user_id String
primary Boolean primary_key Boolean
user User @relation(references: [id], fields: [user_id], onDelete: Cascade) expires BigInt?
auth_user AuthUser @relation(references: [id], fields: [user_id], onDelete: Cascade)
@@index([user_id]) @@index([user_id])
@@map("key") @@map("auth_key")
}
enum ComplexityLevel {
Low
Medium
High
}
enum NELevel {
Low
Medium
High
} }

4
src/app.d.ts vendored
View File

@ -4,9 +4,7 @@ declare global {
namespace App { namespace App {
// interface Error {} // interface Error {}
interface Locals { interface Locals {
validate: import("@lucia-auth/sveltekit").Validate auth: import("lucia-auth").AuthRequest;
validateUser: import("@lucia-auth/sveltekit").ValidateUser
setSession: import("@lucia-auth/sveltekit").SetSession
} }
// interface PageData {} // interface PageData {}
// interface Platform {} // interface Platform {}

View File

@ -5,6 +5,83 @@
<link rel="icon" href="%sveltekit.assets%/favicon.png" /> <link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
%sveltekit.head% %sveltekit.head%
<link
rel="preload"
href="/fonts/TTNormsPro-Bold.ttf"
as="font"
type="font/ttf"
crossorigin="anonymous"
data-react-helmet="true"
/>
<link
rel="preload"
href="/fonts/TTNormsPro-Regular.ttf"
as="font"
type="font/ttf"
crossorigin="anonymous"
data-react-helmet="true"
/>
<link
rel="preload"
href="/fonts/TTNormsPro-Medium.ttf"
as="font"
type="font/ttf"
crossorigin="anonymous"
data-react-helmet="true"
/>
<style data-react-helmet="true">
@font-face {
font-family: TTNormsPro-Regular;
src: url('/fonts/TTNormsPro-Regular.ttf') format('truetype');
font-style: normal;
font-weight: 400;
font-display: swap;
}
@font-face {
font-family: TTNormsPro-Medium;
src: url('/fonts/TTNormsPro-Medium.ttf') format('truetype');
font-style: normal;
font-weight: 500;
font-display: swap;
}
@font-face {
font-family: TTNormsPro-Bold;
src: url('/fonts/TTNormsPro-Bold.ttf') format('truetype');
font-style: normal;
font-weight: 700;
font-display: swap;
}
</style>
<link
href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;500;700&amp;display=swap"
rel="stylesheet"
/>
<style>
body,
html {
height: 100%;
}
*,
::after,
::before {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html {
background: rgb(25, 23, 54);
color: white;
font-family: Poppins, TTNormsPro-Regular, sans-serif;
}
</style>
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>

View File

@ -1,4 +1,3 @@
import { handleHooks } from "@lucia-auth/sveltekit"
import { auth } from "$lib/server/lucia" import { auth } from "$lib/server/lucia"
import type { Handle } from "@sveltejs/kit" import type { Handle } from "@sveltejs/kit"
import { sequence } from "@sveltejs/kit/hooks" import { sequence } from "@sveltejs/kit/hooks"
@ -7,4 +6,7 @@ export const customHandle: Handle = async ({ resolve, event }) => {
return resolve(event) return resolve(event)
} }
export const handle: Handle = sequence(handleHooks(auth), customHandle) export const handle: Handle = async ({ event, resolve }) => {
event.locals.auth = auth.handleRequest(event);
return await resolve(event);
};

View File

@ -0,0 +1,82 @@
<script>
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher();
export let title = "";
export let description = "";
export let selected = false;
</script>
<div class="large-button" class:active={selected} on:click={() => dispatch('click')}>
<div class="content-wrapper">
<h6>{title}</h6>
<p>{description}</p>
</div>
</div>
<style>
.large-button.active {
opacity: 1;
background: rgb(25, 23, 54);
}
.large-button {
padding: 1rem;
display: flex;
flex-direction: row;
margin-bottom: 0.5rem;
border-radius: 0.75rem;
background: rgba(25, 23, 54, 0.5);
opacity: 0.5;
transition: background 0.2s ease-in-out 0s, opacity 0.2s ease-in-out 0s;
min-height: 6rem;
cursor: pointer;
position: relative;
}
:global(.large-button.active)::before {
content: '';
position: absolute;
top: 0px;
left: 0px;
border-radius: 0.78rem;
width: 100%;
height: 100%;
background: linear-gradient(100.23deg, rgb(73, 21, 248) -26.81%, rgb(255, 73, 107) 134.77%);
opacity: 1;
z-index: -1;
transition: transform 0.15s ease-in-out 0s;
transform: scaleX(1.005) scaleY(1.02);
}
.large-button:hover {
background: rgb(37, 35, 64);
opacity: 1;
}
.content-wrapper {
display: flex;
flex-direction: column;
-moz-box-pack: start;
justify-content: flex-start;
flex: 1 1 0%;
}
.content-wrapper h6 {
-moz-box-align: center;
align-items: center;
font-family: TTNormsPro-Bold;
font-size: 1rem;
color: rgb(255, 255, 255);
display: flex;
text-transform: lowercase;
}
.content-wrapper p {
margin-top: 0.5rem;
font-family: TTNormsPro-Regular;
font-size: 0.875rem;
color: rgb(212, 210, 234);
}
</style>

View File

@ -0,0 +1,67 @@
<script lang="ts">
import Switch from './switch.svelte';
export let toggled: boolean = false;
export let title: String;
</script>
<div class="large-switch" class:active={toggled} on:click={() => (toggled = !toggled)}>
<div class="title">{title}</div>
<Switch {toggled} />
</div>
<style>
.large-switch.active {
opacity: 1;
background: rgb(25, 23, 54);
}
.large-switch {
-moz-box-align: center;
align-items: center;
background: rgba(25, 23, 54, 0.5);
border-radius: 0.75rem;
border: medium none;
cursor: pointer;
display: flex;
height: 3.5rem;
-moz-box-pack: justify;
justify-content: space-between;
margin: 0.25rem 0px;
opacity: 0.5;
outline: none;
padding: 0.25rem 1.25rem;
position: relative;
text-align: left;
text-transform: lowercase;
transition: background 0.2s ease-in-out 0s, opacity 0.2s ease-in-out 0s;
width: 100%;
}
:global(.large-switch.active)::before {
content: '';
position: absolute;
top: 0px;
left: 0px;
border-radius: 0.78rem;
width: 100%;
height: 100%;
background: linear-gradient(100.23deg, rgb(73, 21, 248) -26.81%, rgb(255, 73, 107) 134.77%);
opacity: 1;
z-index: -1;
transition: transform 0.15s ease-in-out 0s;
transform: scaleX(1.005) scaleY(1.02);
}
.large-switch:hover {
background: rgb(37, 35, 64);
opacity: 1;
}
.title {
font-family: TTNormsPro-Bold;
font-size: 0.875rem;
font-weight: 400;
}
</style>

View File

@ -0,0 +1,33 @@
<script>
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher();
</script>
<div>
<div class="play" on:click={() => dispatch('click')}>
<img src="/icons/play.svg" alt="" />
</div>
</div>
<style>
.play {
background: rgba(25, 23, 54, 0.5);
outline: none;
border: medium none;
display: flex;
-moz-box-pack: center;
justify-content: center;
-moz-box-align: center;
align-items: center;
width: clamp(40px, -2rem + 8vw, 4.75rem);
height: clamp(40px, -2rem + 8vw, 4.75rem);
border-radius: 100%;
cursor: pointer;
box-shadow: rgba(86, 84, 135, 0.49) 0px 0px 0px 1px;
margin: 0px clamp(0.25rem, -2rem + 8vw, 1rem);
backdrop-filter: blur(34px);
transition: box-shadow 0.4s ease-in-out 0s, background 0.3s ease-in-out 0s;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,40 @@
<div class="player-wrapper">
<div class="image-wrapper">
<img src="https://images.unsplash.com/photo-1610412458272-c4032377e721?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwyMjcyMjd8MHwxfHNlYXJjaHwxNDMwfHxhYnN0cmFjdHxlbnwxfHx8fDE2MjE2MDY0Nzc&ixlib=rb-1.2.1&q=80&w=1080" alt="">
</div>
<div class="song-info-wrapper">
<h3 class="song-title">perfect moments</h3>
<p class="song-info">ATMOSPHERIC • MEDIUM NEURAL EFFECT</p>
</div>
</div>
<style>
.player-wrapper {
padding: 1rem;
display: flex;
flex-direction: row;
}
.image-wrapper {
margin-right: 1.5rem
}
.image-wrapper img {
width: 64px;
height: 64px;
border-radius: 1rem;
}
.song-title {
font-size: 1rem;
margin-bottom: 0.25rem;
}
.song-info {
font-size: 0.5rem;
margin-bottom: 0.25rem;
letter-spacing: 0.2rem;
font-weight: 700;
font-family: TTNormsPro-Regular;
}
</style>

View File

@ -0,0 +1,45 @@
<script>
export let toggled = false;
</script>
<div class="switch" class:active={toggled}>
<div class="notch" />
</div>
<style>
.switch {
cursor: pointer;
border-radius: 22px;
width: 22px;
height: 13.75px;
position: relative;
transition: background 0.2s ease-in-out 0s, box-shadow 0.2s ease-in-out 0s,
border 0.2s ease-in-out 0s;
border: 1px solid rgba(255, 255, 255, 0.5);
background: transparent;
}
.switch.active {
border: 0;
background: linear-gradient(100.23deg, rgb(73, 21, 248) -26.81%, rgb(255, 73, 107) 84.28%);
box-shadow: rgba(147, 35, 102, 0.4) 0px 2.54251px 11.4413px;
}
.switch.active > .notch {
left: calc(100% - 11.75px);
}
.notch {
cursor: pointer;
background-color: rgb(255, 255, 255);
width: 9.75px;
height: 9.75px;
border-radius: 100%;
position: absolute;
top: 50%;
left: 1px;
transform: translateY(-50%);
box-shadow: rgba(0, 0, 0, 0.2) 2px 4px 6px;
transition: all 0.3s ease 0s;
}
</style>

1
src/lib/modal.js Normal file
View File

@ -0,0 +1 @@
export const ssr = false;

90
src/lib/modal.svelte Normal file
View File

@ -0,0 +1,90 @@
<script context="module" lang="ts">
let onTop: string //keeping track of which open modal is on top
const modals = {} //all modals get registered here for easy future access
// returns an object for the modal specified by `id`, which contains the API functions (`open` and `close` )
export function getModal(id = '') {
return modals[id]
}
</script>
<script lang="ts">
import { onDestroy } from 'svelte'
let topDiv
let visible = false
let prevOnTop
let closeCallback: Function
export let id: string = ''
export let title: string = id
function keyPress(ev: KeyboardEvent) {
//only respond if the current modal is the top one
if (ev.key == 'Escape' && onTop == topDiv) close() //ESC
}
/** API **/
function open(callback: Function) {
closeCallback = callback
if (visible) return
prevOnTop = onTop
onTop = topDiv
//this prevents scrolling of the main window on larger screens
document.body.style.overflow = 'hidden'
visible = true
//Move the modal in the DOM to be the last child of <BODY> so that it can be on top of everything
document.body.appendChild(topDiv)
}
function close(retVal: any) {
if (!visible) return
onTop = prevOnTop
if (onTop == null) document.body.style.overflow = ''
visible = false
if (closeCallback) closeCallback(retVal)
}
//expose the API
modals[id] = { open, close }
onDestroy(() => {
delete modals[id]
})
</script>
<svelte:window on:keydown={keyPress} />
<dialog id="topModal" class:visible bind:this={topDiv} on:click={() => close()}>
<article id="modal" on:click|stopPropagation={() => {}}>
<header>
<a href="#" aria-label="Close" class="close" on:click={()=>close()} />
{title}
</header>
<div id="modal-content">
<slot />
</div>
</article>
</dialog>
<style>
#topModal {
visibility: hidden;
z-index: 9999;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #4448;
display: flex;
align-items: center;
justify-content: center;
}
.visible {
visibility: visible !important;
}
</style>

View File

@ -0,0 +1,102 @@
<script lang="ts">
import LargeButton from '$lib/components/large-button.svelte';
import LargeSwitch from '$lib/components/large-switch.svelte';
let nodeRef: Node;
</script>
<div class="filter-component" bind:this={nodeRef}>
<div class="filter-wrapper">
<div class="filter-close" on:click={() => nodeRef.parentNode?.removeChild(nodeRef)}>
<img src="/icons/close.svg" alt="" />
</div>
<div class="filter-sections-wrapper">
<div class="filter-section">
<div class="filter-title-wrapper">
<div class="filter-title">Activity</div>
</div>
<div class="filter-options-wrapper">
<LargeButton
title="deep sleep"
description="Music designed to promote healthy and prolonged rest."
/>
</div>
</div>
<div class="filter-section">
<div class="filter-title-wrapper">
<div class="filter-title">Genre</div>
</div>
<div class="filter-options-wrapper">
<LargeSwitch title="Beach" />
</div>
</div>
<div class="filter-section" />
</div>
</div>
</div>
<style>
.filter-component {
display: flex;
height: 100vh;
overflow: hidden;
padding: 15vh 5vw;
position: absolute;
right: 0px;
top: 0px;
width: 100vw;
z-index: 100;
}
.filter-wrapper {
border-radius: 20px;
padding: 0px;
position: relative;
width: 100%;
overflow: hidden;
scrollbar-width: none;
background: rgb(33, 29, 63);
backdrop-filter: blur(64px);
border: 1px solid rgba(55, 62, 91, 0.7);
box-shadow: rgba(25, 23, 54, 0.65) 0px 20px 50px;
}
.filter-sections-wrapper {
display: flex;
overflow: hidden;
height: 100%;
width: 100%;
}
.filter-section:nth-child(2) {
border-left: 2px solid black;
border-right: 2px solid black;
}
.filter-section {
padding: 4rem;
overflow: hidden auto;
width: 50%;
}
.filter-title-wrapper {
margin-bottom: 2.75rem;
letter-spacing: 0.2rem;
text-align: center;
text-transform: uppercase;
}
.filter-title {
font-family: TTNormsPro-Regular;
font-size: 0.9375rem;
font-weight: 400;
}
.filter-close {
position: absolute;
top: 2rem;
right: 2rem;
cursor: pointer;
z-index: 999;
}
</style>

View File

@ -0,0 +1,20 @@
<script>
import Song from '$lib/components/song.svelte'
import PlayerControls from '$lib/components/player-controls.svelte'
</script>
<div class="player">
<Song />
<PlayerControls />
</div>
<style>
.player {
display: flex;
flex-direction: row;
align-items: center;
padding: 1rem;
}
</style>

View File

@ -1,18 +1,16 @@
import lucia from "lucia-auth" // @ts-ignore
import prismaAdapter from "@lucia-auth/adapter-prisma"
import { dev } from "$app/environment" import lucia from 'lucia-auth'
import { prisma } from "$lib/server/prisma" import { sveltekit } from 'lucia-auth/middleware'
import prisma from '@lucia-auth/adapter-prisma'
import { prisma as prismaClient } from '$lib/server/prisma'
import { dev } from '$app/environment'
export const auth = lucia({ export const auth = lucia({
adapter: prismaAdapter(prisma), // @ts-expect-error
env: dev ? "DEV" : "PROD", adapter: prisma(prismaClient),
transformUserData: (userData) => { env: dev ? 'DEV' : 'PROD',
return { middleware: sveltekit()
userId: userData.id,
username: userData.username,
name: userData.name,
}
},
}) })
export type Auth = typeof auth export type Auth = typeof auth

View File

@ -1,6 +1,7 @@
import type { LayoutServerLoad } from "./$types" import type { LayoutServerLoad } from "./$types"
export const load: LayoutServerLoad = async ({ locals }) => { export const load: LayoutServerLoad = async ({ locals }) => {
const { user, session } = await locals.validateUser() const { user, session } = await locals.auth.validateUser()
return { user } return { user }
} }

View File

@ -1,31 +0,0 @@
<script lang="ts">
import '@picocss/pico'
import type { PageData } from './$types'
export let data: PageData
</script>
<div class="container">
<nav>
<ul>
<li>
<strong>
<a href="/"> Blogly </a>
</strong>
</li>
</ul>
<ul>
<form method="POST">
<li><a href="/">Home</a></li>
{#if !data.user}
<li><a href="/register">Register</a></li>
<li><a href="/login" role="button">Login</a></li>
{:else}
<li>
<button formaction="/logout" type="submit" role="button">Logout</button>
</li>
{/if}
</form>
</ul>
</nav>
<slot />
</div>

View File

@ -10,7 +10,7 @@ export const load: PageServerLoad = async () => {
export const actions: Actions = { export const actions: Actions = {
createArticle: async ({ request, locals }) => { createArticle: async ({ request, locals }) => {
const { user, session } = await locals.validateUser() const { user, session } = await locals.auth.validateUser()
if (!(user && session)) { if (!(user && session)) {
throw redirect(302, "/") throw redirect(302, "/")
} }
@ -37,7 +37,7 @@ export const actions: Actions = {
} }
}, },
deleteArticle: async ({ url, locals }) => { deleteArticle: async ({ url, locals }) => {
const { user, session } = await locals.validateUser() const { user, session } = await locals.auth.validateUser()
if (!(user && session)) { if (!(user && session)) {
throw redirect(302, "/") throw redirect(302, "/")
} }

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { PageData } from './$types' import type { PageData } from './$types'
export let data: PageData export let data: PageData;
$: ({ articles } = data) $: ({ articles } = data)
</script> </script>

View File

@ -1,71 +0,0 @@
import type { Actions, PageServerLoad } from "./$types"
import { prisma } from "$lib/server/prisma"
import { error, fail } from "@sveltejs/kit"
export const load: PageServerLoad = async ({ params, locals }) => {
const { user, session } = await locals.validateUser()
if (!(user && session)) {
throw error(401, "Unauthorized")
}
const getArticle = async (userId: string) => {
const article = await prisma.article.findUnique({
where: {
id: Number(params.articleId),
},
})
if (!article) {
throw error(404, "Article not found")
}
if (article.userId !== user.userId) {
throw error(403, "Unauthorized")
}
return article
}
return {
article: getArticle(user.userId),
}
}
export const actions: Actions = {
updateArticle: async ({ request, params, locals }) => {
const { user, session } = await locals.validateUser()
if (!(user && session)) {
throw error(401, "Unauthorized")
}
const { title, content } = Object.fromEntries(
await request.formData(),
) as Record<string, string>
try {
const article = await prisma.article.findUniqueOrThrow({
where: {
id: Number(params.articleId),
},
})
if (article.userId !== user.userId) {
throw error(403, "Forbidden to edit this article.")
}
await prisma.article.update({
where: {
id: Number(params.articleId),
},
data: {
title,
content,
},
})
} catch (err) {
console.error(err)
return fail(500, { message: "Could not update article" })
}
return {
status: 200,
}
},
}

View File

@ -1,15 +0,0 @@
<script lang="ts">
import type { PageData } from './$types'
export let data: PageData
$: ({ article } = data)
</script>
<form action="?/updateArticle" method="POST">
<h3>Editing: {article.title}</h3>
<label for="title"> Title </label>
<input type="text" id="title" name="title" value={article.title} />
<label for="title"> Title </label>
<textarea id="content" name="content" rows={5} value={article.content} />
<button type="submit">Update Article</button>
</form>

View File

@ -0,0 +1,41 @@
<header class="header">
<div class="left-wrapper">
<div class="logo-wrapper">
<img src="/logo.svg" alt="" class="logo" />
</div>
<div class="activity-selector-wrapper">
<select name="activity" id="">
<option value="deep_work">deep work</option>
<option value="learning">learning</option>
<option value="creativity">creativity</option>
</select>
</div>
</div>
</header>
<slot />
<style>
.header {
display: flex;
flex-direction: row;
align-items: center;
padding: 1rem;
}
.left-wrapper {
display: flex;
flex-direction: row;
align-items: center;
gap: 1rem;
}
.logo-wrapper {
height: 56px;
}
.logo {
height: 100%;
}
</style>

View File

@ -0,0 +1,24 @@
<script>
import Player from '$lib/module/player.svelte'
import Filter from '$lib/module/filter.svelte';
</script>
<main>
<Filter />
<div class="player-wrapper">
<Player />
</div>
</main>
<style>
main {
width: 100%;
}
.player-wrapper {
position: fixed;
bottom: 0
}
</style>

View File

@ -0,0 +1,7 @@
import { writable } from "svelte/store";
const isPlaying = writable(false);
const songPreferedActivity = writable("")
const songPreferedGenres = writable([])
const songPreferedNEL = writable("")

View File

@ -3,7 +3,7 @@ import { fail, redirect } from "@sveltejs/kit"
import type { Actions, PageServerLoad } from "./$types" import type { Actions, PageServerLoad } from "./$types"
export const load: PageServerLoad = async ({ locals }) => { export const load: PageServerLoad = async ({ locals }) => {
const session = await locals.validate() const session = await locals.auth.validate()
if (session) { if (session) {
throw redirect(302, "/") throw redirect(302, "/")
} }
@ -16,9 +16,9 @@ export const actions: Actions = {
) as Record<string, string> ) as Record<string, string>
try { try {
const key = await auth.validateKeyPassword("username", username, password) const key = await auth.useKey("username", username, password)
const session = await auth.createSession(key.userId) const session = await auth.createSession(key.userId)
locals.setSession(session) locals.auth.setSession(session)
} catch (err) { } catch (err) {
console.error(err) console.error(err)
return fail(400, { message: "Could not login user." }) return fail(400, { message: "Could not login user." })

View File

@ -3,7 +3,7 @@ import { redirect } from "@sveltejs/kit"
import type { RequestHandler } from "./$types" import type { RequestHandler } from "./$types"
export const POST: RequestHandler = async ({ locals }) => { export const POST: RequestHandler = async ({ locals }) => {
const session = await locals.validate() const session = await locals.auth.validate()
if (!session) { if (!session) {
throw redirect(302, "/") throw redirect(302, "/")
} }

View File

@ -3,7 +3,7 @@ import { fail, redirect } from "@sveltejs/kit"
import type { Actions, PageServerLoad } from "./$types" import type { Actions, PageServerLoad } from "./$types"
export const load: PageServerLoad = async ({ locals }) => { export const load: PageServerLoad = async ({ locals }) => {
const session = await locals.validate() const session = await locals.auth.validate()
if (session) { if (session) {
throw redirect(302, "/") throw redirect(302, "/")
} }
@ -17,7 +17,7 @@ export const actions: Actions = {
try { try {
await auth.createUser({ await auth.createUser({
key: { primaryKey: {
providerId: "username", providerId: "username",
providerUserId: username, providerUserId: username,
password, password,

Binary file not shown.

Binary file not shown.

Binary file not shown.

4
static/icons/close.svg Normal file
View File

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 6L18 18" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 306 B

10
static/icons/play.svg Normal file
View File

@ -0,0 +1,10 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1_2)">
<path d="M16.3441 8.31926L3.86508 0.137426C3.60968 -0.0294838 3.28107 -0.0458474 3.01152 0.0965164C2.74031 0.23888 2.57143 0.516244 2.57143 0.818154V17.1818C2.57143 17.4837 2.74031 17.7611 3.01069 17.9035C3.13381 17.9681 3.26859 18 3.40336 18C3.56476 18 3.72615 17.9534 3.86508 17.8625L16.3441 9.68071C16.5754 9.52853 16.7143 9.27326 16.7143 8.99999C16.7143 8.72671 16.5754 8.47144 16.3441 8.31926Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_1_2">
<rect width="18" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 651 B

1
static/logo.svg Normal file
View File

@ -0,0 +1 @@
<svg height="2115" width="2500" xmlns="http://www.w3.org/2000/svg" viewBox="0.01 -0.004 140.12 118.62019900826458"><path d="M70.014 23.941a47.318 47.318 0 1 0 47.533 47.317 47.481 47.481 0 0 0-47.533-47.317zm18.733 33.571a6.792 6.792 0 1 1-6.822 6.791 6.8 6.8 0 0 1 6.822-6.792zm-36.069 0a6.792 6.792 0 1 1-6.822 6.791 6.8 6.8 0 0 1 6.822-6.792zm44.453 24.455c-.56 3.288-4.185 22.167-26.32 22.167-21.595 0-26.75-20.546-26.75-22.167 0 0-1.127-4.324 4.054-3.451 6.309 1.063 15.754 1.345 21.865 1.345 7.317 0 17.679-.538 22.2-1 4.992-.503 5.197 1.669 4.951 3.106zm34.62-38.328h-1.345C126.288 32.727 110.646-.004 71.131-.004c-44.712 0-58.535 35.513-61.106 43.643H8.388C.366 43.639.01 52.517.01 52.517v21.471c0 12.422 12.3 9.758 16.578 9.758s4.456-5.5 4.456-5.5V46.658a4.814 4.814 0 0 0-3.989-2.9c3.3-9.462 16.137-37.042 54.073-37.042 32.339 0 46.676 24.03 52.062 37.022a4.862 4.862 0 0 0-4.094 2.92V78.25s.178 5.5 4.456 5.5 16.578 2.664 16.578-9.758V52.517s-.355-8.878-8.378-8.878z" fill="#fff" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1018 B