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,
"singleQuote": true,
"trailingComma": "none",
"semi": false,
"semi": true,
"printWidth": 100,
"plugins": [
"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",
"private": true,
"scripts": {
@ -24,7 +24,7 @@
"eslint-plugin-svelte3": "^4.0.0",
"prettier": "^2.8.3",
"prettier-plugin-svelte": "^2.9.0",
"prisma": "^4.9.0",
"prisma": "^4.13.0",
"rome": "^11.0.0",
"svelte": "^3.55.1",
"svelte-check": "^3.0.3",
@ -35,10 +35,8 @@
},
"type": "module",
"dependencies": {
"@lucia-auth/adapter-prisma": "^0.4.0",
"@lucia-auth/sveltekit": "^0.6.2",
"@picocss/pico": "^1.5.7",
"@prisma/client": "^4.9.0",
"lucia-auth": "^0.6.0"
"@lucia-auth/adapter-prisma": "^1.0.0",
"@prisma/client": "^4.13.0",
"lucia-auth": "^1.1.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 {
provider = "sqlite"
url = "file:./dev.sqlite"
provider = "postgres"
url = "postgresql://postgres:b359a3f0b1129da7@141.147.61.193:3021/postgres"
}
model Article {
id Int @id @default(autoincrement())
title String
content String
User User @relation(fields: [userId], references: [id])
userId String
model Song {
id String @id @default(uuid())
name String
image String
hasMultipleNELs Boolean
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 @default(uuid())
name String
genres Genre[]
}
model Genre {
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
articles Article[]
session Session[]
Key Key[]
favorites Song[]
auth_session AuthSession[]
auth_key AuthKey[]
@@map("user")
@@map("auth_user")
}
model Session {
model AuthSession {
id String @id @unique
user_id String
active_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])
@@map("session")
@@map("auth_session")
}
model Key {
model AuthKey {
id String @id @unique
hashed_password String?
user_id String
primary Boolean
user User @relation(references: [id], fields: [user_id], onDelete: Cascade)
primary_key Boolean
expires BigInt?
auth_user AuthUser @relation(references: [id], fields: [user_id], onDelete: Cascade)
@@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 {
// interface Error {}
interface Locals {
validate: import("@lucia-auth/sveltekit").Validate
validateUser: import("@lucia-auth/sveltekit").ValidateUser
setSession: import("@lucia-auth/sveltekit").SetSession
auth: import("lucia-auth").AuthRequest;
}
// interface PageData {}
// interface Platform {}

View File

@ -5,6 +5,83 @@
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%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>
<body data-sveltekit-preload-data="hover">
<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 type { Handle } from "@sveltejs/kit"
import { sequence } from "@sveltejs/kit/hooks"
@ -7,4 +6,7 @@ export const customHandle: Handle = async ({ 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"
import prismaAdapter from "@lucia-auth/adapter-prisma"
import { dev } from "$app/environment"
import { prisma } from "$lib/server/prisma"
// @ts-ignore
import lucia from 'lucia-auth'
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({
adapter: prismaAdapter(prisma),
env: dev ? "DEV" : "PROD",
transformUserData: (userData) => {
return {
userId: userData.id,
username: userData.username,
name: userData.name,
}
},
// @ts-expect-error
adapter: prisma(prismaClient),
env: dev ? 'DEV' : 'PROD',
middleware: sveltekit()
})
export type Auth = typeof auth

View File

@ -1,6 +1,7 @@
import type { LayoutServerLoad } from "./$types"
export const load: LayoutServerLoad = async ({ locals }) => {
const { user, session } = await locals.validateUser()
const { user, session } = await locals.auth.validateUser()
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 = {
createArticle: async ({ request, locals }) => {
const { user, session } = await locals.validateUser()
const { user, session } = await locals.auth.validateUser()
if (!(user && session)) {
throw redirect(302, "/")
}
@ -37,7 +37,7 @@ export const actions: Actions = {
}
},
deleteArticle: async ({ url, locals }) => {
const { user, session } = await locals.validateUser()
const { user, session } = await locals.auth.validateUser()
if (!(user && session)) {
throw redirect(302, "/")
}

View File

@ -1,7 +1,7 @@
<script lang="ts">
import type { PageData } from './$types'
export let data: PageData
export let data: PageData;
$: ({ articles } = data)
</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"
export const load: PageServerLoad = async ({ locals }) => {
const session = await locals.validate()
const session = await locals.auth.validate()
if (session) {
throw redirect(302, "/")
}
@ -16,9 +16,9 @@ export const actions: Actions = {
) as Record<string, string>
try {
const key = await auth.validateKeyPassword("username", username, password)
const key = await auth.useKey("username", username, password)
const session = await auth.createSession(key.userId)
locals.setSession(session)
locals.auth.setSession(session)
} catch (err) {
console.error(err)
return fail(400, { message: "Could not login user." })

View File

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

View File

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