Compare commits
10 Commits
Author | SHA1 | Date |
---|---|---|
|
5f6e2a3f01 | |
|
af13ad4a08 | |
|
b6b4b5c8f5 | |
|
8dd959bb03 | |
|
1a1b4bb9dc | |
|
e3b6b8f38d | |
|
fc9aa54f8a | |
|
66dafca64f | |
|
4b9532ab23 | |
|
e5cab64698 |
|
@ -13,13 +13,13 @@ insert_final_newline = true
|
|||
[*.css]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
trim_trailing_whitespace = true
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# HTML
|
||||
[*.{htm,html}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
trim_trailing_whitespace = true
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# GNU make
|
||||
[Makefile]
|
||||
|
|
11
index.html
11
index.html
|
@ -8,16 +8,5 @@
|
|||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
|
||||
<script>
|
||||
// overwrite these options
|
||||
window.CONFIG = {
|
||||
API_URL: undefined,
|
||||
TITLE: undefined,
|
||||
CSS_VAR: undefined,
|
||||
LINK_TERMS: undefined,
|
||||
LINK_PRIVACY: undefined,
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.2",
|
||||
"@tsconfig/svelte": "^3.0.0",
|
||||
"lucide-svelte": "^0.102.0",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-svelte": "^2.8.0",
|
||||
"sass": "^1.55.0",
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
<script lang="ts">
|
||||
import {Router, Route, navigate, link} from "svelte-navigator";
|
||||
import {getUser} from "./api/login";
|
||||
import Menu from "./icons/Menu.svelte";
|
||||
import HeaderDropdown from "./lib/HeaderDropdown.svelte";
|
||||
import LazyComponent from "./lib/LazyComponent.svelte";
|
||||
import {loginStore, profileStore, type LoginStore, type ProfileData} from "./stores/login";
|
||||
import {getEnv} from "./utils/env";
|
||||
|
||||
let profile: ProfileData;
|
||||
|
||||
loginStore.subscribe((value: LoginStore) => {
|
||||
getMe();
|
||||
});
|
||||
|
||||
profileStore.subscribe((value: ProfileData) => (profile = value));
|
||||
|
||||
async function getMe() {
|
||||
try {
|
||||
let p = <ProfileData>await getUser("@me");
|
||||
|
@ -22,12 +18,14 @@
|
|||
profileStore.set(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
let mobileNavOpen = false;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{getEnv("TITLE")}</title>
|
||||
<link rel="stylesheet" href="{getEnv('CSS_VAR')}.light.css" media="screen" />
|
||||
<link rel="stylesheet" href="{getEnv('CSS_VAR')}.dark.css" media="screen and (prefers-color-scheme: dark)" />
|
||||
<title>{import.meta.env.VITE_TITLE}</title>
|
||||
<link rel="stylesheet" href="{import.meta.env.VITE_CSS_VAR}.light.css" media="screen" />
|
||||
<link rel="stylesheet" href="{import.meta.env.VITE_CSS_VAR}.dark.css" media="screen and (prefers-color-scheme: dark)" />
|
||||
</svelte:head>
|
||||
|
||||
<div id="app-router">
|
||||
|
@ -35,12 +33,19 @@
|
|||
<header>
|
||||
<div class="central-header">
|
||||
<a href="/" use:link>
|
||||
<h1>{getEnv("TITLE")}</h1>
|
||||
<h1>{import.meta.env.VITE_TITLE}</h1>
|
||||
</a>
|
||||
|
||||
<nav>
|
||||
{#if profile !== undefined}
|
||||
<HeaderDropdown {profile} />
|
||||
<div class="mobile-shade {mobileNavOpen ? 'mobile-open' : ''}" />
|
||||
<button class="mobile-nav-toggle {mobileNavOpen ? 'mobile-active' : ''}" on:click={() => (mobileNavOpen = !mobileNavOpen)}>
|
||||
<Menu size={32} />
|
||||
</button>
|
||||
|
||||
<nav class={mobileNavOpen ? "mobile-open" : ""}>
|
||||
{#if $profileStore !== undefined}
|
||||
<div style="height:50px;">
|
||||
<HeaderDropdown profile={$profileStore} />
|
||||
</div>
|
||||
{:else}
|
||||
<a href="/register" use:link>Register</a>
|
||||
<a href="/login" use:link>Login</a>
|
||||
|
@ -107,6 +112,10 @@
|
|||
max-width: min(100%, 1000px);
|
||||
margin: auto;
|
||||
|
||||
> .mobile-nav-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> nav {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
@ -135,5 +144,51 @@
|
|||
> footer {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
> header {
|
||||
border-radius: 0;
|
||||
> .central-header {
|
||||
> .mobile-nav-toggle {
|
||||
display: flex;
|
||||
padding: 0;
|
||||
width: 50px;
|
||||
aspect-ratio: 1/1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.mobile-active {
|
||||
background-color: var(--primary-hover);
|
||||
}
|
||||
}
|
||||
|
||||
> nav {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: auto;
|
||||
z-index: 9998;
|
||||
display: none;
|
||||
|
||||
&.mobile-open {
|
||||
display: block;
|
||||
background-color: var(--bg-panel-action);
|
||||
}
|
||||
}
|
||||
|
||||
> .mobile-shade.mobile-open {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
content: "";
|
||||
z-index: 9997;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import {getEnv} from "~/utils/env";
|
||||
import {loginStore} from "~/stores/login";
|
||||
import {get} from "svelte/store";
|
||||
|
||||
export function URL() {
|
||||
return getEnv("API_URL");
|
||||
}
|
||||
export const URL = import.meta.env.VITE_API_URL;
|
||||
|
||||
export async function sendApiRequest(path: string, opt: RequestInit) {
|
||||
return fetch(URL() + path, opt);
|
||||
return fetch(URL + path, opt);
|
||||
}
|
||||
|
||||
export async function sendSessionRequest(path: string, opt: RequestInit) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {sendSessionRequest} from "./api";
|
||||
|
||||
export async function postRegister(data: object, token?: string) {
|
||||
let headers = new Headers();
|
||||
headers.set("Accept", "application/json");
|
||||
|
|
|
@ -70,3 +70,8 @@ button {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// useful utility styles
|
||||
.flex-gap {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
@mixin form-styles() {
|
||||
h1 {
|
||||
margin: 0;
|
||||
padding: 32px 32px 0;
|
||||
line-height: normal;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h2 {
|
||||
//margin-right: 300px;
|
||||
|
||||
> .optional {
|
||||
font-size: 75%;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
padding-bottom: 24px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, auto);
|
||||
gap: 16px;
|
||||
|
||||
&.first-section {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
label {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 8px;
|
||||
line-height: 24px;
|
||||
width: 300px;
|
||||
height: 16px;
|
||||
border-radius: var(--small-curve);
|
||||
border: 2px solid var(--primary-hover);
|
||||
transition: border-color 100ms;
|
||||
|
||||
&:focus {
|
||||
border: 2px solid var(--primary-text);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
border-radius: var(--small-curve);
|
||||
border: 2px solid var(--primary-hover);
|
||||
transition: border-color 100ms;
|
||||
|
||||
&:focus {
|
||||
border: 2px solid var(--primary-text);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@
|
|||
</section>
|
||||
</div>
|
||||
<div class="email-code-action">
|
||||
<div class="flex-gap" />
|
||||
<button type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -80,6 +81,7 @@
|
|||
background: var(--bg-panel-action);
|
||||
padding: 24px;
|
||||
border-radius: 0 0 var(--large-curve) var(--large-curve);
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
import {createEventDispatcher} from "svelte";
|
||||
import {navigate} from "svelte-navigator";
|
||||
|
||||
let inputEmail: string = "";
|
||||
let inputUsername: string = "";
|
||||
let inputPassword: string = "";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function submitLogin() {
|
||||
dispatch("submit", {email: inputEmail, password: inputPassword});
|
||||
dispatch("submit", {username: inputUsername, password: inputPassword});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -17,8 +17,8 @@
|
|||
<h1>Sign In</h1>
|
||||
|
||||
<section>
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="email" name="email" placeholder=" " autocomplete="username" required bind:value={inputEmail} />
|
||||
<label for="username">Username</label>
|
||||
<input id="username" type="text" name="username" placeholder=" " autocomplete="username" required bind:value={inputUsername} />
|
||||
|
||||
<label for="current-password">Password</label>
|
||||
<input id="current-password" name="current-password" type="password" autocomplete="current-password" required bind:value={inputPassword} />
|
||||
|
@ -27,10 +27,10 @@
|
|||
|
||||
<div class="login-action">
|
||||
<section>
|
||||
<button type="submit">Login</button>
|
||||
<button on:click={() => navigate("/register")} class="grey-btn">Register</button>
|
||||
<div class="flex-gap" />
|
||||
<!-- <button on:click={() => navigate("/forgot-password")} class="grey-btn">Forgot My Password</button> -->
|
||||
<button on:click={() => navigate("/register")} class="grey-btn">Register</button>
|
||||
<button type="submit">Login</button>
|
||||
</section>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -92,12 +92,14 @@
|
|||
display: flex;
|
||||
|
||||
> .grey-btn {
|
||||
color: var(--primary-hover);
|
||||
margin-left: 8px;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary-text);
|
||||
background-color: #616161;
|
||||
}
|
||||
|
||||
> .flex-gap {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
<script lang="ts">
|
||||
import {CheckCircle, ExternalLink, Link2, Lock, Slash, XCircle} from "lucide-svelte";
|
||||
import CheckCircle from "~/icons/CheckCircle.svelte";
|
||||
import Link2 from "~/icons/Link2.svelte";
|
||||
import Lock from "~/icons/Lock.svelte";
|
||||
import XCircle from "~/icons/XCircle.svelte";
|
||||
import {onMount} from "svelte";
|
||||
import {navigate, useLocation} from "svelte-navigator";
|
||||
import {getUser} from "~/api/login";
|
||||
import {get} from "svelte/store";
|
||||
import {getOAuthApp, getOAuthScopes, postAuthorize} from "~/api/oauth";
|
||||
import LazyDelay from "~/lib/LazyDelay.svelte";
|
||||
import {loginStore, profileStore} from "~/stores/login";
|
||||
import {PromiseAllUnique} from "~/utils/promise-all-unique";
|
||||
import MoreHorizontal from "~/icons/MoreHorizontal.svelte";
|
||||
|
||||
const fakeScope = [
|
||||
"Eat cake",
|
||||
|
@ -26,6 +32,7 @@
|
|||
let app: {
|
||||
app_name: string;
|
||||
app_desc: string;
|
||||
app_icon: string;
|
||||
privacy?: string;
|
||||
terms?: string;
|
||||
};
|
||||
|
@ -39,22 +46,23 @@
|
|||
|
||||
onMount(async () => {
|
||||
let params = new URLSearchParams($location.search);
|
||||
try {
|
||||
await getUser("@me");
|
||||
try {
|
||||
app = await getOAuthApp(params.get("client_id"));
|
||||
try {
|
||||
scopes = await getOAuthScopes(params.get("scope"));
|
||||
} catch (err) {
|
||||
navigate("/oauth/invalid-scope?" + new URLSearchParams({redirect: params.get("redirect_uri")}));
|
||||
}
|
||||
} catch (_) {
|
||||
navigate("/oauth/invalid-app?" + new URLSearchParams({redirect: params.get("redirect_uri")}));
|
||||
}
|
||||
} catch (_) {
|
||||
if (get(loginStore) == null) {
|
||||
let backParams = new URLSearchParams();
|
||||
backParams.set("back", window.location.pathname + window.location.search);
|
||||
navigate("/login?" + backParams.toString());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
[app, scopes] = await PromiseAllUnique([getOAuthApp(params.get("client_id")), getOAuthScopes(params.get("scope"))]);
|
||||
} catch (err) {
|
||||
switch (err.index) {
|
||||
case 0:
|
||||
navigate("/oauth/invalid-app?" + new URLSearchParams({redirect: params.get("redirect_uri")}));
|
||||
break;
|
||||
case 1:
|
||||
navigate("/oauth/invalid-scope?" + new URLSearchParams({redirect: params.get("redirect_uri")}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -87,7 +95,18 @@
|
|||
{#if app}
|
||||
<div class="oauth-widget">
|
||||
<div class="oauth-content">
|
||||
<div class="oauth-pictures">
|
||||
<img src={app.app_icon} alt="" />
|
||||
<span class="oauth-picture-separator">
|
||||
<MoreHorizontal />
|
||||
</span>
|
||||
{#if $profileStore}
|
||||
<img src={$profileStore.icon} alt="" />
|
||||
{/if}
|
||||
</div>
|
||||
<h3 class="oauth-subtext">An external application</h3>
|
||||
<h2 class="oauth-title">{app.app_name}</h2>
|
||||
<h3 class="oauth-subtext">wants to access your Melon ID account</h3>
|
||||
<div class="oauth-desc">{app.app_desc}</div>
|
||||
<div class="oauth-scopes">
|
||||
<ul>
|
||||
|
@ -124,8 +143,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="oauth-btns">
|
||||
<button class="authorize-btn" on:click|preventDefault={runAuthorize}>Authorize</button>
|
||||
<button class="cancel-btn secondary" on:click|preventDefault={runCancel}>Cancel</button>
|
||||
<button class="authorize-btn" on:click|preventDefault={runAuthorize}>Authorize</button>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
|
@ -143,30 +162,62 @@
|
|||
border-radius: var(--large-curve) var(--large-curve) 0 0;
|
||||
padding: 0 16px;
|
||||
|
||||
> .oauth-title {
|
||||
> .oauth-pictures {
|
||||
padding: 2em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
> img {
|
||||
width: 100px;
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
|
||||
> .oauth-picture-separator {
|
||||
margin: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--primary-text);
|
||||
}
|
||||
}
|
||||
|
||||
> .oauth-subtext {
|
||||
margin: 0;
|
||||
padding: 32px 32px 16px 32px;
|
||||
line-height: normal;
|
||||
text-align: center;
|
||||
font-size: 1em;
|
||||
font-weight: 100;
|
||||
color: #adadad;
|
||||
}
|
||||
|
||||
> .oauth-title {
|
||||
margin: 0;
|
||||
padding: 8px 32px 8px 32px;
|
||||
line-height: normal;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
> .oauth-desc {
|
||||
padding-bottom: 16px;
|
||||
padding: 16px 0;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid var(--bg-panel-action);
|
||||
margin-top: 16px;
|
||||
border-top: 1px solid var(--primary-text);
|
||||
border-bottom: 1px solid var(--primary-text);
|
||||
}
|
||||
|
||||
> .oauth-scopes {
|
||||
margin: 0;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid var(--bg-panel-action);
|
||||
border-bottom: 1px solid var(--primary-text);
|
||||
|
||||
> ul {
|
||||
margin: 0;
|
||||
padding-left: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
> li {
|
||||
margin-top: 8px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
|
|
|
@ -2,24 +2,26 @@
|
|||
import {onMount} from "svelte";
|
||||
import {getUser} from "~/api/login";
|
||||
import LazyDelay from "~/lib/LazyDelay.svelte";
|
||||
import type {ProfileData} from "~/stores/login";
|
||||
import {profileStore, type ProfileData} from "~/stores/login";
|
||||
|
||||
export let id: "@me" | number;
|
||||
let user: ProfileData;
|
||||
let userIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg==";
|
||||
const defaultIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg==";
|
||||
|
||||
onMount(async () => {
|
||||
if (id == "@me") user = $profileStore;
|
||||
else {
|
||||
try {
|
||||
user = await getUser(id);
|
||||
userIcon = user.icon;
|
||||
} catch (_) {}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="profile-widget">
|
||||
<div class="profile-content">
|
||||
{#if user}
|
||||
<img class="icon" src={userIcon} alt="Profile Icon" />
|
||||
<img class="icon" src={user.icon || defaultIcon} alt="Profile Icon" />
|
||||
<h1 class="displayName">{user.display_name}</h1>
|
||||
<h2 class="username">{user.username}</h2>
|
||||
<h3 class="email">{user.email}</h3>
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<script lang="ts">
|
||||
import {ExternalLink} from "lucide-svelte";
|
||||
import ExternalLink from "~/icons/ExternalLink.svelte";
|
||||
import {createEventDispatcher} from "svelte";
|
||||
import {navigate} from "svelte-navigator";
|
||||
import {getEnv} from "~/utils/env";
|
||||
import PasswordConstraints from "~/lib/PasswordConstraints.svelte";
|
||||
|
||||
export let err: {message: string; log_id: string};
|
||||
|
||||
// Account
|
||||
let inputEmail: string = "";
|
||||
let inputTag: string = "";
|
||||
let inputUsername: string = "";
|
||||
let inputPassword: string = "";
|
||||
let inputRepeat: string = "";
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
|||
function submitLogin() {
|
||||
dispatch("submit", {
|
||||
email: inputEmail,
|
||||
username: inputTag,
|
||||
username: inputUsername,
|
||||
password: inputPassword,
|
||||
repeatPassword: inputRepeat,
|
||||
displayName: inputDisplayName,
|
||||
|
@ -32,10 +32,6 @@
|
|||
birthday: inputBirthday,
|
||||
});
|
||||
}
|
||||
|
||||
function conDone(v: boolean): string {
|
||||
return v ? "follows-constraint" : "missing-constraint";
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="register-widget" method="post" on:submit|preventDefault={submitLogin}>
|
||||
|
@ -46,10 +42,10 @@
|
|||
|
||||
<section class="first-section">
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="email" name="email" placeholder=" " autocomplete="username" required bind:value={inputEmail} />
|
||||
<input id="email" type="email" name="email" placeholder=" " autocomplete="email" required bind:value={inputEmail} />
|
||||
|
||||
<label for="tag">Tag</label>
|
||||
<input id="tag" type="text" name="tag" placeholder=" " autocomplete="off" required bind:value={inputTag} />
|
||||
<label for="username">Username</label>
|
||||
<input id="username" type="text" name="tag" placeholder=" " autocomplete="uesrname" required bind:value={inputUsername} />
|
||||
|
||||
<label for="new-password">Password</label>
|
||||
<input
|
||||
|
@ -75,15 +71,7 @@
|
|||
</section>
|
||||
|
||||
<section>
|
||||
<div id="password-constraints">
|
||||
<ul>
|
||||
<li class={conDone(inputPassword.length >= 8)}>Eight or more characters</li>
|
||||
<li class={conDone(inputPassword.toLocaleLowerCase() != inputPassword)}>Uppercase characters</li>
|
||||
<li class={conDone(inputPassword.toLocaleUpperCase() != inputPassword)}>Lowercase characters</li>
|
||||
<li class={conDone(/\d/.test(inputPassword))}>Numeric digit</li>
|
||||
<li class={conDone(/\W/.test(inputPassword))}>Special symbol</li>
|
||||
</ul>
|
||||
</div>
|
||||
<PasswordConstraints password={inputPassword} />
|
||||
</section>
|
||||
|
||||
<h2>Profile <span class="optional">(optional)</span></h2>
|
||||
|
@ -112,9 +100,9 @@
|
|||
<section>
|
||||
<div>
|
||||
You must agree to the
|
||||
<a href={getEnv("LINK_TERMS")} target="_blank">Terms <ExternalLink size={16} /></a>
|
||||
<a href={import.meta.env.VITE_LINK_TERMS} rel="noreferrer" target="_blank">Terms <ExternalLink size={16} /></a>
|
||||
and
|
||||
<a href={getEnv("LINK_PRIVACY")} target="_blank">Privacy <ExternalLink size={16} /></a>
|
||||
<a href={import.meta.env.VITE_LINK_PRIVACY} rel="noreferrer" target="_blank">Privacy <ExternalLink size={16} /></a>
|
||||
documents.
|
||||
</div>
|
||||
</section>
|
||||
|
@ -128,15 +116,16 @@
|
|||
</section>
|
||||
{/if}
|
||||
<section>
|
||||
<button type="submit">Register</button>
|
||||
<div class="flex-gap" />
|
||||
<button on:click={() => navigate("/login")} class="grey-btn">Login</button>
|
||||
<div class="flex-gap" />
|
||||
<button type="submit">Register</button>
|
||||
</section>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../assets/panel.scss";
|
||||
@import "../../assets/form.scss";
|
||||
|
||||
.register-widget {
|
||||
@include panel;
|
||||
|
@ -149,64 +138,7 @@
|
|||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
> h1 {
|
||||
margin: 0;
|
||||
padding: 32px 32px 0;
|
||||
line-height: normal;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
> h2 {
|
||||
//margin-right: 300px;
|
||||
|
||||
> .optional {
|
||||
font-size: 75%;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
|
||||
> section {
|
||||
padding-bottom: 24px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, auto);
|
||||
gap: 16px;
|
||||
|
||||
&.first-section {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
> label {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
> input {
|
||||
padding: 8px;
|
||||
line-height: 24px;
|
||||
width: 300px;
|
||||
height: 16px;
|
||||
border-radius: var(--small-curve);
|
||||
border: 2px solid var(--primary-hover);
|
||||
transition: border-color 100ms;
|
||||
|
||||
&:focus {
|
||||
border: 2px solid var(--primary-text);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
> select {
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
border-radius: var(--small-curve);
|
||||
border: 2px solid var(--primary-hover);
|
||||
transition: border-color 100ms;
|
||||
|
||||
&:focus {
|
||||
border: 2px solid var(--primary-text);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@include form-styles();
|
||||
}
|
||||
|
||||
> .register-action {
|
||||
|
@ -218,29 +150,19 @@
|
|||
display: flex;
|
||||
|
||||
> .grey-btn {
|
||||
color: var(--primary-hover);
|
||||
margin-left: 0 8px;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary-text);
|
||||
background-color: #616161;
|
||||
}
|
||||
|
||||
> .flex-gap {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#password-constraints > ul {
|
||||
margin: 0;
|
||||
|
||||
> li.follows-constraint {
|
||||
color: darken(#bdd358, 5);
|
||||
}
|
||||
|
||||
> li.missing-constraint {
|
||||
color: darken(#e5625e, 5);
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-bottom: 16px;
|
||||
background-color: darken(#e5625e, 10);
|
||||
|
|
|
@ -1,12 +1,23 @@
|
|||
<script lang="ts">
|
||||
import LazyDelay from "~/lib/LazyDelay.svelte";
|
||||
import PasswordConstraints from "~/lib/PasswordConstraints.svelte";
|
||||
import {profileStore} from "~/stores/login";
|
||||
|
||||
let inputEmail: string = "";
|
||||
let inputPassword: string = "";
|
||||
let inputNewPassword: string = "";
|
||||
let inputRepeatPassword: string = "";
|
||||
|
||||
function conDone(v: boolean): string {
|
||||
return v ? "follows-constraint" : "missing-constraint";
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="settings-widget">
|
||||
<div class="settings-content">
|
||||
{#if $profileStore}
|
||||
<div>Honestly I'm just too lazy to make this yet</div>
|
||||
<h1>Settings</h1>
|
||||
<section>Honestly I'm just too lazy to make this yet</section>
|
||||
{:else}
|
||||
<LazyDelay delayMs={500}>Loading...</LazyDelay>
|
||||
{/if}
|
||||
|
@ -15,6 +26,7 @@
|
|||
|
||||
<style lang="scss">
|
||||
@import "../../assets/panel.scss";
|
||||
@import "../../assets/form.scss";
|
||||
|
||||
.settings-widget {
|
||||
@include panel;
|
||||
|
@ -26,7 +38,9 @@
|
|||
flex-direction: column;
|
||||
padding: 12px 24px;
|
||||
|
||||
> .icon {
|
||||
@include form-styles();
|
||||
|
||||
/*> .icon {
|
||||
margin: 0 auto 12px auto;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
|
@ -44,7 +58,7 @@
|
|||
|
||||
> .email {
|
||||
margin: 0;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
export let size = 24;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
|
||||
<polyline points="22 4 12 14.01 9 11.01" />
|
||||
</svg>
|
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts">
|
||||
export let size = 24;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9" />
|
||||
</svg>
|
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts">
|
||||
export let size = 24;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="18 15 12 9 6 15" />
|
||||
</svg>
|
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts">
|
||||
export let size = 24;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
||||
<polyline points="15 3 21 3 21 9" />
|
||||
<line x1="10" y1="14" x2="21" y2="3" />
|
||||
</svg>
|
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts">
|
||||
export let size = 24;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M9 17H7A5 5 0 0 1 7 7h2" />
|
||||
<path d="M15 7h2a5 5 0 1 1 0 10h-2" />
|
||||
<line x1="8" y1="12" x2="16" y2="12" />
|
||||
</svg>
|
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
export let size = 24;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||
</svg>
|
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts">
|
||||
export let size = 24;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
||||
<polyline points="16 17 21 12 16 7" />
|
||||
<line x1="21" y1="12" x2="9" y2="12" />
|
||||
</svg>
|
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts">
|
||||
export let size = 24;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="4" y1="12" x2="20" y2="12" />
|
||||
<line x1="4" y1="6" x2="20" y2="6" />
|
||||
<line x1="4" y1="18" x2="20" y2="18" />
|
||||
</svg>
|
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts">
|
||||
export let size = 24;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="1" />
|
||||
<circle cx="19" cy="12" r="1" />
|
||||
<circle cx="5" cy="12" r="1" />
|
||||
</svg>
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
export let size = 24;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path
|
||||
d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"
|
||||
/>
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</svg>
|
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
export let size = 24;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="12" cy="7" r="4" />
|
||||
</svg>
|
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts">
|
||||
export let size = 24;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<line x1="15" y1="9" x2="9" y2="15" />
|
||||
<line x1="9" y1="9" x2="15" y2="15" />
|
||||
</svg>
|
|
@ -1,6 +1,10 @@
|
|||
<script lang="ts">
|
||||
import {loginStore, type ProfileData} from "~/stores/login";
|
||||
import {User, Settings, LogOut, ChevronUp, ChevronDown} from "lucide-svelte";
|
||||
import User from "~/icons/User.svelte";
|
||||
import Settings from "~/icons/Settings.svelte";
|
||||
import LogOut from "~/icons/LogOut.svelte";
|
||||
import ChevronUp from "~/icons/ChevronUp.svelte";
|
||||
import ChevronDown from "~/icons/ChevronDown.svelte";
|
||||
import {link, navigate} from "svelte-navigator";
|
||||
|
||||
export let profile: ProfileData;
|
||||
|
@ -55,7 +59,8 @@
|
|||
</div>
|
||||
{#if open}
|
||||
<div class="dropdown-floating">
|
||||
<div class="dropdown-body">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="dropdown-body" on:click={() => (open = false)}>
|
||||
<a href="/profile" use:link>
|
||||
<User />
|
||||
<span>Profile</span>
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<script lang="ts">
|
||||
export let password: string;
|
||||
|
||||
function conDone(v: boolean): string {
|
||||
return v ? "follows-constraint" : "missing-constraint";
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="password-constraints">
|
||||
<ul>
|
||||
<li class={conDone(password.length >= 8)}>Eight or more characters</li>
|
||||
<li class={conDone(password.toLocaleLowerCase() != password)}>Uppercase characters</li>
|
||||
<li class={conDone(password.toLocaleUpperCase() != password)}>Lowercase characters</li>
|
||||
<li class={conDone(/\d/.test(password))}>Numeric digit</li>
|
||||
<li class={conDone(/\W/.test(password))}>Special symbol</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
#password-constraints > ul {
|
||||
margin: 0;
|
||||
|
||||
> li.follows-constraint {
|
||||
color: darken(#bdd358, 5);
|
||||
}
|
||||
|
||||
> li.missing-constraint {
|
||||
color: darken(#e5625e, 5);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -26,6 +26,9 @@
|
|||
if (params.has("back")) navigate(params.get("back"));
|
||||
else navigate("/profile");
|
||||
}
|
||||
if (z.hasOwnProperty("log_id")) {
|
||||
updatePage(postLogin({}, ""));
|
||||
}
|
||||
loading = false;
|
||||
}
|
||||
|
||||
|
@ -51,5 +54,7 @@
|
|||
<EmailCodeForm on:submit={submitLogin} />
|
||||
{:else if step == "mfa"}
|
||||
<!-- add mfa step -->
|
||||
{:else}
|
||||
<div>Something broke...</div>
|
||||
{/if}
|
||||
</Page>
|
||||
|
|
|
@ -3,11 +3,10 @@
|
|||
import {navigate} from "svelte-navigator";
|
||||
import Page from "~/lib/Page.svelte";
|
||||
import Profile from "~/components/profile/Profile.svelte";
|
||||
import {get} from "svelte/store";
|
||||
import {profileStore} from "~/stores/login";
|
||||
|
||||
onMount(async () => {
|
||||
if (get(profileStore) === undefined) navigate("/login?back=/profile");
|
||||
if ($profileStore === undefined) navigate("/login?back=/profile");
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
export function getEnv(key: string) {
|
||||
key = key.toUpperCase();
|
||||
return window.CONFIG[key] ?? import.meta.env["VITE_" + key];
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
export async function PromiseAllUnique(promises: Promise<any>[]): Promise<any> {
|
||||
return await Promise.all(
|
||||
promises.map((promise, i) =>
|
||||
promise.catch(err => {
|
||||
err.index = i;
|
||||
throw err;
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
|
@ -442,11 +442,6 @@ lower-case@^2.0.2:
|
|||
dependencies:
|
||||
tslib "^2.0.3"
|
||||
|
||||
lucide-svelte@^0.102.0:
|
||||
version "0.102.0"
|
||||
resolved "https://registry.yarnpkg.com/lucide-svelte/-/lucide-svelte-0.102.0.tgz#4a8bca665e6f01d21d60bb057996c35a6e878eb5"
|
||||
integrity sha512-r8Nmz3XnRiesT3BxTaQvJnkbvutJMDv7HHADfDVZ1VLo3tWInfSBzWQya1V12dFfdBoz/bMwBX0abpccw77v4A==
|
||||
|
||||
magic-string@^0.25.7:
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
|
||||
|
|
Reference in New Issue