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]
|
[*.css]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
# HTML
|
# HTML
|
||||||
[*.{htm,html}]
|
[*.{htm,html}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
# GNU make
|
# GNU make
|
||||||
[Makefile]
|
[Makefile]
|
||||||
|
|
11
index.html
11
index.html
|
@ -8,16 +8,5 @@
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^1.0.2",
|
"@sveltejs/vite-plugin-svelte": "^1.0.2",
|
||||||
"@tsconfig/svelte": "^3.0.0",
|
"@tsconfig/svelte": "^3.0.0",
|
||||||
"lucide-svelte": "^0.102.0",
|
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"prettier-plugin-svelte": "^2.8.0",
|
"prettier-plugin-svelte": "^2.8.0",
|
||||||
"sass": "^1.55.0",
|
"sass": "^1.55.0",
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Router, Route, navigate, link} from "svelte-navigator";
|
import {Router, Route, navigate, link} from "svelte-navigator";
|
||||||
import {getUser} from "./api/login";
|
import {getUser} from "./api/login";
|
||||||
|
import Menu from "./icons/Menu.svelte";
|
||||||
import HeaderDropdown from "./lib/HeaderDropdown.svelte";
|
import HeaderDropdown from "./lib/HeaderDropdown.svelte";
|
||||||
import LazyComponent from "./lib/LazyComponent.svelte";
|
import LazyComponent from "./lib/LazyComponent.svelte";
|
||||||
import {loginStore, profileStore, type LoginStore, type ProfileData} from "./stores/login";
|
import {loginStore, profileStore, type LoginStore, type ProfileData} from "./stores/login";
|
||||||
import {getEnv} from "./utils/env";
|
|
||||||
|
|
||||||
let profile: ProfileData;
|
|
||||||
|
|
||||||
loginStore.subscribe((value: LoginStore) => {
|
loginStore.subscribe((value: LoginStore) => {
|
||||||
getMe();
|
getMe();
|
||||||
});
|
});
|
||||||
|
|
||||||
profileStore.subscribe((value: ProfileData) => (profile = value));
|
|
||||||
|
|
||||||
async function getMe() {
|
async function getMe() {
|
||||||
try {
|
try {
|
||||||
let p = <ProfileData>await getUser("@me");
|
let p = <ProfileData>await getUser("@me");
|
||||||
|
@ -22,12 +18,14 @@
|
||||||
profileStore.set(undefined);
|
profileStore.set(undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mobileNavOpen = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>{getEnv("TITLE")}</title>
|
<title>{import.meta.env.VITE_TITLE}</title>
|
||||||
<link rel="stylesheet" href="{getEnv('CSS_VAR')}.light.css" media="screen" />
|
<link rel="stylesheet" href="{import.meta.env.VITE_CSS_VAR}.light.css" media="screen" />
|
||||||
<link rel="stylesheet" href="{getEnv('CSS_VAR')}.dark.css" media="screen and (prefers-color-scheme: dark)" />
|
<link rel="stylesheet" href="{import.meta.env.VITE_CSS_VAR}.dark.css" media="screen and (prefers-color-scheme: dark)" />
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div id="app-router">
|
<div id="app-router">
|
||||||
|
@ -35,12 +33,19 @@
|
||||||
<header>
|
<header>
|
||||||
<div class="central-header">
|
<div class="central-header">
|
||||||
<a href="/" use:link>
|
<a href="/" use:link>
|
||||||
<h1>{getEnv("TITLE")}</h1>
|
<h1>{import.meta.env.VITE_TITLE}</h1>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<nav>
|
<div class="mobile-shade {mobileNavOpen ? 'mobile-open' : ''}" />
|
||||||
{#if profile !== undefined}
|
<button class="mobile-nav-toggle {mobileNavOpen ? 'mobile-active' : ''}" on:click={() => (mobileNavOpen = !mobileNavOpen)}>
|
||||||
<HeaderDropdown {profile} />
|
<Menu size={32} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<nav class={mobileNavOpen ? "mobile-open" : ""}>
|
||||||
|
{#if $profileStore !== undefined}
|
||||||
|
<div style="height:50px;">
|
||||||
|
<HeaderDropdown profile={$profileStore} />
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<a href="/register" use:link>Register</a>
|
<a href="/register" use:link>Register</a>
|
||||||
<a href="/login" use:link>Login</a>
|
<a href="/login" use:link>Login</a>
|
||||||
|
@ -107,6 +112,10 @@
|
||||||
max-width: min(100%, 1000px);
|
max-width: min(100%, 1000px);
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
|
||||||
|
> .mobile-nav-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
> nav {
|
> nav {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -135,5 +144,51 @@
|
||||||
> footer {
|
> footer {
|
||||||
padding: 16px;
|
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>
|
</style>
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import {getEnv} from "~/utils/env";
|
|
||||||
import {loginStore} from "~/stores/login";
|
import {loginStore} from "~/stores/login";
|
||||||
import {get} from "svelte/store";
|
import {get} from "svelte/store";
|
||||||
|
|
||||||
export function URL() {
|
export const URL = import.meta.env.VITE_API_URL;
|
||||||
return getEnv("API_URL");
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function sendApiRequest(path: string, opt: RequestInit) {
|
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) {
|
export async function sendSessionRequest(path: string, opt: RequestInit) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {sendSessionRequest} from "./api";
|
import {sendSessionRequest} from "./api";
|
||||||
|
|
||||||
export async function postRegister(data: object, token?: string) {
|
export async function postRegister(data: object, token?: string) {
|
||||||
let headers = new Headers();
|
let headers = new Headers();
|
||||||
headers.set("Accept", "application/json");
|
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>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="email-code-action">
|
<div class="email-code-action">
|
||||||
|
<div class="flex-gap" />
|
||||||
<button type="submit">Submit</button>
|
<button type="submit">Submit</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -80,6 +81,7 @@
|
||||||
background: var(--bg-panel-action);
|
background: var(--bg-panel-action);
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
border-radius: 0 0 var(--large-curve) var(--large-curve);
|
border-radius: 0 0 var(--large-curve) var(--large-curve);
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
import {createEventDispatcher} from "svelte";
|
import {createEventDispatcher} from "svelte";
|
||||||
import {navigate} from "svelte-navigator";
|
import {navigate} from "svelte-navigator";
|
||||||
|
|
||||||
let inputEmail: string = "";
|
let inputUsername: string = "";
|
||||||
let inputPassword: string = "";
|
let inputPassword: string = "";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
function submitLogin() {
|
function submitLogin() {
|
||||||
dispatch("submit", {email: inputEmail, password: inputPassword});
|
dispatch("submit", {username: inputUsername, password: inputPassword});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -17,8 +17,8 @@
|
||||||
<h1>Sign In</h1>
|
<h1>Sign In</h1>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<label for="email">Email</label>
|
<label for="username">Username</label>
|
||||||
<input id="email" type="email" name="email" placeholder=" " autocomplete="username" required bind:value={inputEmail} />
|
<input id="username" type="text" name="username" placeholder=" " autocomplete="username" required bind:value={inputUsername} />
|
||||||
|
|
||||||
<label for="current-password">Password</label>
|
<label for="current-password">Password</label>
|
||||||
<input id="current-password" name="current-password" type="password" autocomplete="current-password" required bind:value={inputPassword} />
|
<input id="current-password" name="current-password" type="password" autocomplete="current-password" required bind:value={inputPassword} />
|
||||||
|
@ -27,10 +27,10 @@
|
||||||
|
|
||||||
<div class="login-action">
|
<div class="login-action">
|
||||||
<section>
|
<section>
|
||||||
<button type="submit">Login</button>
|
<button on:click={() => navigate("/register")} class="grey-btn">Register</button>
|
||||||
<div class="flex-gap" />
|
<div class="flex-gap" />
|
||||||
<!-- <button on:click={() => navigate("/forgot-password")} class="grey-btn">Forgot My Password</button> -->
|
<!-- <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>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -92,12 +92,14 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
> .grey-btn {
|
> .grey-btn {
|
||||||
|
color: var(--primary-hover);
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
background-color: #616161;
|
background-color: transparent;
|
||||||
}
|
|
||||||
|
|
||||||
> .flex-gap {
|
&:hover {
|
||||||
flex-grow: 1;
|
color: var(--primary-text);
|
||||||
|
background-color: #616161;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
<script lang="ts">
|
<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 {onMount} from "svelte";
|
||||||
import {navigate, useLocation} from "svelte-navigator";
|
import {navigate, useLocation} from "svelte-navigator";
|
||||||
import {getUser} from "~/api/login";
|
import {get} from "svelte/store";
|
||||||
import {getOAuthApp, getOAuthScopes, postAuthorize} from "~/api/oauth";
|
import {getOAuthApp, getOAuthScopes, postAuthorize} from "~/api/oauth";
|
||||||
import LazyDelay from "~/lib/LazyDelay.svelte";
|
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 = [
|
const fakeScope = [
|
||||||
"Eat cake",
|
"Eat cake",
|
||||||
|
@ -26,6 +32,7 @@
|
||||||
let app: {
|
let app: {
|
||||||
app_name: string;
|
app_name: string;
|
||||||
app_desc: string;
|
app_desc: string;
|
||||||
|
app_icon: string;
|
||||||
privacy?: string;
|
privacy?: string;
|
||||||
terms?: string;
|
terms?: string;
|
||||||
};
|
};
|
||||||
|
@ -39,22 +46,23 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
let params = new URLSearchParams($location.search);
|
let params = new URLSearchParams($location.search);
|
||||||
try {
|
if (get(loginStore) == null) {
|
||||||
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 (_) {
|
|
||||||
let backParams = new URLSearchParams();
|
let backParams = new URLSearchParams();
|
||||||
backParams.set("back", window.location.pathname + window.location.search);
|
backParams.set("back", window.location.pathname + window.location.search);
|
||||||
navigate("/login?" + backParams.toString());
|
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}
|
{#if app}
|
||||||
<div class="oauth-widget">
|
<div class="oauth-widget">
|
||||||
<div class="oauth-content">
|
<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>
|
<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-desc">{app.app_desc}</div>
|
||||||
<div class="oauth-scopes">
|
<div class="oauth-scopes">
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -124,8 +143,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="oauth-btns">
|
<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="cancel-btn secondary" on:click|preventDefault={runCancel}>Cancel</button>
|
||||||
|
<button class="authorize-btn" on:click|preventDefault={runAuthorize}>Authorize</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -143,30 +162,62 @@
|
||||||
border-radius: var(--large-curve) var(--large-curve) 0 0;
|
border-radius: var(--large-curve) var(--large-curve) 0 0;
|
||||||
padding: 0 16px;
|
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;
|
margin: 0;
|
||||||
padding: 32px 32px 16px 32px;
|
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
text-align: center;
|
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 {
|
> .oauth-desc {
|
||||||
padding-bottom: 16px;
|
padding: 16px 0;
|
||||||
text-align: center;
|
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 {
|
> .oauth-scopes {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
border-bottom: 1px solid var(--bg-panel-action);
|
border-bottom: 1px solid var(--primary-text);
|
||||||
|
|
||||||
> ul {
|
> ul {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-left: 24px;
|
padding-left: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
> li {
|
> li {
|
||||||
margin-top: 8px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
|
|
@ -2,24 +2,26 @@
|
||||||
import {onMount} from "svelte";
|
import {onMount} from "svelte";
|
||||||
import {getUser} from "~/api/login";
|
import {getUser} from "~/api/login";
|
||||||
import LazyDelay from "~/lib/LazyDelay.svelte";
|
import LazyDelay from "~/lib/LazyDelay.svelte";
|
||||||
import type {ProfileData} from "~/stores/login";
|
import {profileStore, type ProfileData} from "~/stores/login";
|
||||||
|
|
||||||
export let id: "@me" | number;
|
export let id: "@me" | number;
|
||||||
let user: ProfileData;
|
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 () => {
|
onMount(async () => {
|
||||||
try {
|
if (id == "@me") user = $profileStore;
|
||||||
user = await getUser(id);
|
else {
|
||||||
userIcon = user.icon;
|
try {
|
||||||
} catch (_) {}
|
user = await getUser(id);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="profile-widget">
|
<div class="profile-widget">
|
||||||
<div class="profile-content">
|
<div class="profile-content">
|
||||||
{#if user}
|
{#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>
|
<h1 class="displayName">{user.display_name}</h1>
|
||||||
<h2 class="username">{user.username}</h2>
|
<h2 class="username">{user.username}</h2>
|
||||||
<h3 class="email">{user.email}</h3>
|
<h3 class="email">{user.email}</h3>
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {ExternalLink} from "lucide-svelte";
|
import ExternalLink from "~/icons/ExternalLink.svelte";
|
||||||
import {createEventDispatcher} from "svelte";
|
import {createEventDispatcher} from "svelte";
|
||||||
import {navigate} from "svelte-navigator";
|
import {navigate} from "svelte-navigator";
|
||||||
import {getEnv} from "~/utils/env";
|
import PasswordConstraints from "~/lib/PasswordConstraints.svelte";
|
||||||
|
|
||||||
export let err: {message: string; log_id: string};
|
export let err: {message: string; log_id: string};
|
||||||
|
|
||||||
// Account
|
// Account
|
||||||
let inputEmail: string = "";
|
let inputEmail: string = "";
|
||||||
let inputTag: string = "";
|
let inputUsername: string = "";
|
||||||
let inputPassword: string = "";
|
let inputPassword: string = "";
|
||||||
let inputRepeat: string = "";
|
let inputRepeat: string = "";
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
function submitLogin() {
|
function submitLogin() {
|
||||||
dispatch("submit", {
|
dispatch("submit", {
|
||||||
email: inputEmail,
|
email: inputEmail,
|
||||||
username: inputTag,
|
username: inputUsername,
|
||||||
password: inputPassword,
|
password: inputPassword,
|
||||||
repeatPassword: inputRepeat,
|
repeatPassword: inputRepeat,
|
||||||
displayName: inputDisplayName,
|
displayName: inputDisplayName,
|
||||||
|
@ -32,10 +32,6 @@
|
||||||
birthday: inputBirthday,
|
birthday: inputBirthday,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function conDone(v: boolean): string {
|
|
||||||
return v ? "follows-constraint" : "missing-constraint";
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="register-widget" method="post" on:submit|preventDefault={submitLogin}>
|
<form class="register-widget" method="post" on:submit|preventDefault={submitLogin}>
|
||||||
|
@ -46,10 +42,10 @@
|
||||||
|
|
||||||
<section class="first-section">
|
<section class="first-section">
|
||||||
<label for="email">Email</label>
|
<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>
|
<label for="username">Username</label>
|
||||||
<input id="tag" type="text" name="tag" placeholder=" " autocomplete="off" required bind:value={inputTag} />
|
<input id="username" type="text" name="tag" placeholder=" " autocomplete="uesrname" required bind:value={inputUsername} />
|
||||||
|
|
||||||
<label for="new-password">Password</label>
|
<label for="new-password">Password</label>
|
||||||
<input
|
<input
|
||||||
|
@ -75,15 +71,7 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<div id="password-constraints">
|
<PasswordConstraints password={inputPassword} />
|
||||||
<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>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<h2>Profile <span class="optional">(optional)</span></h2>
|
<h2>Profile <span class="optional">(optional)</span></h2>
|
||||||
|
@ -112,9 +100,9 @@
|
||||||
<section>
|
<section>
|
||||||
<div>
|
<div>
|
||||||
You must agree to the
|
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
|
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.
|
documents.
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -128,15 +116,16 @@
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
<section>
|
<section>
|
||||||
<button type="submit">Register</button>
|
|
||||||
<div class="flex-gap" />
|
|
||||||
<button on:click={() => navigate("/login")} class="grey-btn">Login</button>
|
<button on:click={() => navigate("/login")} class="grey-btn">Login</button>
|
||||||
|
<div class="flex-gap" />
|
||||||
|
<button type="submit">Register</button>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../assets/panel.scss";
|
@import "../../assets/panel.scss";
|
||||||
|
@import "../../assets/form.scss";
|
||||||
|
|
||||||
.register-widget {
|
.register-widget {
|
||||||
@include panel;
|
@include panel;
|
||||||
|
@ -149,64 +138,7 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
> h1 {
|
@include form-styles();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .register-action {
|
> .register-action {
|
||||||
|
@ -218,29 +150,19 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
> .grey-btn {
|
> .grey-btn {
|
||||||
|
color: var(--primary-hover);
|
||||||
margin-left: 0 8px;
|
margin-left: 0 8px;
|
||||||
background-color: #616161;
|
background-color: transparent;
|
||||||
}
|
|
||||||
|
|
||||||
> .flex-gap {
|
&:hover {
|
||||||
flex-grow: 1;
|
color: var(--primary-text);
|
||||||
|
background-color: #616161;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#password-constraints > ul {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
> li.follows-constraint {
|
|
||||||
color: darken(#bdd358, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
> li.missing-constraint {
|
|
||||||
color: darken(#e5625e, 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
.error-message {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
background-color: darken(#e5625e, 10);
|
background-color: darken(#e5625e, 10);
|
||||||
|
|
|
@ -1,12 +1,23 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import LazyDelay from "~/lib/LazyDelay.svelte";
|
import LazyDelay from "~/lib/LazyDelay.svelte";
|
||||||
|
import PasswordConstraints from "~/lib/PasswordConstraints.svelte";
|
||||||
import {profileStore} from "~/stores/login";
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="settings-widget">
|
<div class="settings-widget">
|
||||||
<div class="settings-content">
|
<div class="settings-content">
|
||||||
{#if $profileStore}
|
{#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}
|
{:else}
|
||||||
<LazyDelay delayMs={500}>Loading...</LazyDelay>
|
<LazyDelay delayMs={500}>Loading...</LazyDelay>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -15,6 +26,7 @@
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../assets/panel.scss";
|
@import "../../assets/panel.scss";
|
||||||
|
@import "../../assets/form.scss";
|
||||||
|
|
||||||
.settings-widget {
|
.settings-widget {
|
||||||
@include panel;
|
@include panel;
|
||||||
|
@ -26,7 +38,9 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 12px 24px;
|
padding: 12px 24px;
|
||||||
|
|
||||||
> .icon {
|
@include form-styles();
|
||||||
|
|
||||||
|
/*> .icon {
|
||||||
margin: 0 auto 12px auto;
|
margin: 0 auto 12px auto;
|
||||||
width: 150px;
|
width: 150px;
|
||||||
height: 150px;
|
height: 150px;
|
||||||
|
@ -44,7 +58,7 @@
|
||||||
|
|
||||||
> .email {
|
> .email {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</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">
|
<script lang="ts">
|
||||||
import {loginStore, type ProfileData} from "~/stores/login";
|
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";
|
import {link, navigate} from "svelte-navigator";
|
||||||
|
|
||||||
export let profile: ProfileData;
|
export let profile: ProfileData;
|
||||||
|
@ -55,7 +59,8 @@
|
||||||
</div>
|
</div>
|
||||||
{#if open}
|
{#if open}
|
||||||
<div class="dropdown-floating">
|
<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>
|
<a href="/profile" use:link>
|
||||||
<User />
|
<User />
|
||||||
<span>Profile</span>
|
<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"));
|
if (params.has("back")) navigate(params.get("back"));
|
||||||
else navigate("/profile");
|
else navigate("/profile");
|
||||||
}
|
}
|
||||||
|
if (z.hasOwnProperty("log_id")) {
|
||||||
|
updatePage(postLogin({}, ""));
|
||||||
|
}
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,5 +54,7 @@
|
||||||
<EmailCodeForm on:submit={submitLogin} />
|
<EmailCodeForm on:submit={submitLogin} />
|
||||||
{:else if step == "mfa"}
|
{:else if step == "mfa"}
|
||||||
<!-- add mfa step -->
|
<!-- add mfa step -->
|
||||||
|
{:else}
|
||||||
|
<div>Something broke...</div>
|
||||||
{/if}
|
{/if}
|
||||||
</Page>
|
</Page>
|
||||||
|
|
|
@ -3,11 +3,10 @@
|
||||||
import {navigate} from "svelte-navigator";
|
import {navigate} from "svelte-navigator";
|
||||||
import Page from "~/lib/Page.svelte";
|
import Page from "~/lib/Page.svelte";
|
||||||
import Profile from "~/components/profile/Profile.svelte";
|
import Profile from "~/components/profile/Profile.svelte";
|
||||||
import {get} from "svelte/store";
|
|
||||||
import {profileStore} from "~/stores/login";
|
import {profileStore} from "~/stores/login";
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (get(profileStore) === undefined) navigate("/login?back=/profile");
|
if ($profileStore === undefined) navigate("/login?back=/profile");
|
||||||
});
|
});
|
||||||
</script>
|
</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:
|
dependencies:
|
||||||
tslib "^2.0.3"
|
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:
|
magic-string@^0.25.7:
|
||||||
version "0.25.9"
|
version "0.25.9"
|
||||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
|
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
|
||||||
|
|
Reference in New Issue