rework login page, deduplicate code by using layouts

This commit is contained in:
maxDorninger
2025-08-02 14:18:18 +02:00
parent 2eaa1b94c0
commit 79ccfe701d
9 changed files with 476 additions and 469 deletions

View File

@@ -1,265 +1,271 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button/index.js';
import * as Card from '$lib/components/ui/card/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import { goto } from '$app/navigation';
import { env } from '$env/dynamic/public';
import * as Tabs from '$lib/components/ui/tabs/index.js';
import { toast } from 'svelte-sonner';
import LoadingBar from '$lib/components/loading-bar.svelte';
import { base } from '$app/paths';
import {Button} from '$lib/components/ui/button/index.js';
import * as Card from '$lib/components/ui/card/index.js';
import {Input} from '$lib/components/ui/input/index.js';
import {Label} from '$lib/components/ui/label/index.js';
import {goto} from '$app/navigation';
import {env} from '$env/dynamic/public';
import * as Tabs from '$lib/components/ui/tabs/index.js';
import {toast} from 'svelte-sonner';
import LoadingBar from '$lib/components/loading-bar.svelte';
import {base} from '$app/paths';
import * as Alert from "$lib/components/ui/alert/index.js";
import CheckCircle2Icon from "@lucide/svelte/icons/check-circle-2";
import AlertCircleIcon from "@lucide/svelte/icons/alert-circle";
const apiUrl = env.PUBLIC_API_URL;
const apiUrl = env.PUBLIC_API_URL;
let { oauthProvider } = $props();
let oauthProviderName = $derived(oauthProvider.oauth_name);
let {oauthProvider, login}: { oauthProvider: { oauth_name: string }, login: boolean } = $props();
let oauthProviderName = $derived(oauthProvider.oauth_name);
let email = $state('');
let password = $state('');
let errorMessage = $state('');
let isLoading = $state(false);
let tabValue = $state('login');
let email = $state('');
let password = $state('');
let errorMessage = $state('');
let isLoading = $state(false);
let tabValue = $state('login');
async function handleLogin(event: Event) {
event.preventDefault();
async function handleLogin(event: Event) {
event.preventDefault();
isLoading = true;
errorMessage = '';
isLoading = true;
errorMessage = '';
const formData = new URLSearchParams();
formData.append('username', email);
formData.append('password', password);
try {
const response = await fetch(apiUrl + '/auth/cookie/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: formData.toString(),
credentials: 'include'
});
const formData = new URLSearchParams();
formData.append('username', email);
formData.append('password', password);
try {
const response = await fetch(apiUrl + '/auth/cookie/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: formData.toString(),
credentials: 'include'
});
if (response.ok) {
console.log('Login successful!');
console.log('Received User Data: ', response);
errorMessage = 'Login successful! Redirecting...';
toast.success(errorMessage);
goto(base + '/dashboard');
} else {
let errorText = await response.text();
try {
const errorData = JSON.parse(errorText);
errorMessage = errorData.message || 'Login failed. Please check your credentials.';
} catch {
errorMessage = errorText || 'Login failed. Please check your credentials.';
}
toast.error(errorMessage);
console.error('Login failed:', response.status, errorText);
}
} catch (error) {
console.error('Login request failed:', error);
errorMessage = 'An error occurred during the login request.';
toast.error(errorMessage);
} finally {
isLoading = false;
}
}
if (response.ok) {
console.log('Login successful!');
console.log('Received User Data: ', response);
errorMessage = 'Login successful! Redirecting...';
toast.success(errorMessage);
goto(base + '/dashboard');
} else {
let errorText = await response.text();
try {
const errorData = JSON.parse(errorText);
errorMessage = errorData.message || 'Login failed. Please check your credentials.';
} catch {
errorMessage = errorText || 'Login failed. Please check your credentials.';
}
toast.error(errorMessage);
console.error('Login failed:', response.status, errorText);
}
} catch (error) {
console.error('Login request failed:', error);
errorMessage = 'An error occurred during the login request.';
toast.error(errorMessage);
} finally {
isLoading = false;
}
}
async function handleSignup(event: Event) {
event.preventDefault();
async function handleSignup(event: Event) {
event.preventDefault();
isLoading = true;
errorMessage = '';
isLoading = true;
errorMessage = '';
try {
const response = await fetch(apiUrl + '/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
password: password
}),
credentials: 'include'
});
try {
const response = await fetch(apiUrl + '/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
password: password
}),
credentials: 'include'
});
if (response.ok) {
console.log('Registration successful!');
console.log('Received User Data: ', response);
tabValue = 'login'; // Switch to login tab after successful registration
errorMessage = 'Registration successful! Please login.';
toast.success(errorMessage);
} else {
let errorText = await response.text();
try {
const errorData = JSON.parse(errorText);
errorMessage = errorData.message || 'Registration failed. Please check your credentials.';
} catch {
errorMessage = errorText || 'Registration failed. Please check your credentials.';
}
toast.error(errorMessage);
console.error('Registration failed:', response.status, errorText);
}
} catch (error) {
console.error('Registration request failed:', error);
errorMessage = 'An error occurred during the Registration request.';
toast.error(errorMessage);
} finally {
isLoading = false;
}
}
if (response.ok) {
console.log('Registration successful!');
console.log('Received User Data: ', response);
tabValue = 'login'; // Switch to login tab after successful registration
errorMessage = 'Registration successful! Please login.';
toast.success(errorMessage);
} else {
let errorText = await response.text();
try {
const errorData = JSON.parse(errorText);
errorMessage = errorData.message || 'Registration failed. Please check your credentials.';
} catch {
errorMessage = errorText || 'Registration failed. Please check your credentials.';
}
toast.error(errorMessage);
console.error('Registration failed:', response.status, errorText);
}
} catch (error) {
console.error('Registration request failed:', error);
errorMessage = 'An error occurred during the Registration request.';
toast.error(errorMessage);
} finally {
isLoading = false;
}
}
async function handleOauth() {
try {
const response = await fetch(
apiUrl + '/auth/cookie/' + oauthProviderName + '/authorize?scopes=email',
{
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}
);
if (response.ok) {
let result = await response.json();
console.log(
'Redirecting to OAuth provider:',
oauthProviderName,
'url: ',
result.authorization_url
);
toast.success('Redirecting to ' + oauthProviderName + ' for authentication...');
window.location = result.authorization_url;
} else {
let errorText = await response.text();
toast.error(errorMessage);
console.error('Login failed:', response.status, errorText);
}
} catch (error) {
console.error('Login request failed:', error);
errorMessage = 'An error occurred during the login request.';
toast.error(errorMessage);
}
}
async function handleOauth() {
try {
const response = await fetch(
apiUrl + '/auth/cookie/' + oauthProviderName + '/authorize?scopes=email',
{
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}
);
if (response.ok) {
let result = await response.json();
console.log(
'Redirecting to OAuth provider:',
oauthProviderName,
'url: ',
result.authorization_url
);
toast.success('Redirecting to ' + oauthProviderName + ' for authentication...');
window.location = result.authorization_url;
} else {
let errorText = await response.text();
toast.error(errorMessage);
console.error('Login failed:', response.status, errorText);
}
} catch (error) {
console.error('Login request failed:', error);
errorMessage = 'An error occurred during the login request.';
toast.error(errorMessage);
}
}
</script>
{#snippet oauthLogin()}
{#await oauthProvider}
<LoadingBar />
{:then result}
{#if result.oauth_name != null}
<div
class="relative mt-2 text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border"
>
{#await oauthProvider}
<LoadingBar/>
{:then result}
{#if result.oauth_name != null}
<div
class="relative mt-2 text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border"
>
<span class="relative z-10 bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
<Button class="mt-2 w-full" onclick={() => handleOauth()} variant="outline"
>Login with {result.oauth_name}</Button
>
{/if}
{/await}
</div>
<Button class="mt-2 w-full" onclick={() => handleOauth()} variant="outline"
>Login with {result.oauth_name}</Button
>
{/if}
{/await}
{/snippet}
<Tabs.Root value={tabValue}>
<Tabs.Content value="login">
<Card.Root class="mx-auto max-w-sm">
<Card.Header>
<Card.Title class="text-2xl">Login</Card.Title>
<Card.Description>Enter your email below to log in to your account</Card.Description>
</Card.Header>
<Card.Content>
<form class="grid gap-4" onsubmit={handleLogin}>
<div class="grid gap-2">
<Label for="email">Email</Label>
<Input
bind:value={email}
id="email"
placeholder="m@example.com"
required
type="email"
/>
</div>
<div class="grid gap-2">
<div class="flex items-center">
<Label for="password">Password</Label>
<a class="ml-auto inline-block text-sm underline" href="{base}/login/forgot-password">
Forgot your password?
</a>
</div>
<Input bind:value={password} id="password" required type="password" />
</div>
{#if login}
<Card.Root class="mx-auto max-w-sm">
<Card.Header>
<Card.Title class="text-2xl">Login</Card.Title>
<Card.Description>Enter your email below to log in to your account</Card.Description>
</Card.Header>
<Card.Content>
<form class="grid gap-4" onsubmit={handleLogin}>
<div class="grid gap-2">
<Label for="email">Email</Label>
<Input
bind:value={email}
id="email"
placeholder="m@example.com"
required
type="email"
/>
</div>
<div class="grid gap-2">
<div class="flex items-center">
<Label for="password">Password</Label>
<a class="ml-auto inline-block text-sm underline" href="{base}/login/forgot-password">
Forgot your password?
</a>
</div>
<Input bind:value={password} id="password" required type="password"/>
</div>
{#if errorMessage}
<p class="text-sm text-red-500">{errorMessage}</p>
{/if}
{#if errorMessage}
<Alert.Root variant="destructive">
<AlertCircleIcon class="size-4"/>
<Alert.Title>Error</Alert.Title>
<Alert.Description
>{errorMessage}</Alert.Description
>
</Alert.Root>
{/if}
<Button class="w-full" disabled={isLoading} type="submit">
{#if isLoading}
Logging in...
{:else}
Login
{/if}
</Button>
</form>
<Button class="w-full" disabled={isLoading} type="submit">
{#if isLoading}
Logging in...
{:else}
Login
{/if}
</Button>
</form>
{@render oauthLogin()}
{@render oauthLogin()}
<div class="mt-4 text-center text-sm">
<Button onclick={() => (tabValue = 'register')} variant="link">
Don't have an account? Sign up
</Button>
</div>
</Card.Content>
</Card.Root>
</Tabs.Content>
<Tabs.Content value="register">
<Card.Root class="mx-auto max-w-sm">
<Card.Header>
<Card.Title class="text-2xl">Sign up</Card.Title>
<Card.Description>Enter your email and password below to sign up.</Card.Description>
</Card.Header>
<Card.Content>
<form class="grid gap-4" onsubmit={handleSignup}>
<div class="grid gap-2">
<Label for="email2">Email</Label>
<Input
bind:value={email}
id="email2"
placeholder="m@example.com"
required
type="email"
/>
</div>
<div class="grid gap-2">
<div class="flex items-center">
<Label for="password2">Password</Label>
</div>
<Input bind:value={password} id="password2" required type="password" />
</div>
<div class="mt-4 text-center text-sm">
<Button href="{base}/login/signup/" variant="link">
Don't have an account? Sign up
</Button>
</div>
</Card.Content>
</Card.Root>
{:else}
<Card.Root class="mx-auto max-w-sm">
<Card.Header>
<Card.Title class="text-2xl">Sign up</Card.Title>
<Card.Description>Enter your email and password below to sign up.</Card.Description>
</Card.Header>
<Card.Content>
<form class="grid gap-4" onsubmit={handleSignup}>
<div class="grid gap-2">
<Label for="email2">Email</Label>
<Input
bind:value={email}
id="email2"
placeholder="m@example.com"
required
type="email"
/>
</div>
<div class="grid gap-2">
<div class="flex items-center">
<Label for="password2">Password</Label>
</div>
<Input bind:value={password} id="password2" required type="password"/>
</div>
{#if errorMessage}
<p class="text-sm text-red-500">{errorMessage}</p>
{/if}
{#if errorMessage}
<p class="text-sm text-red-500">{errorMessage}</p>
{/if}
<Button class="w-full" disabled={isLoading} type="submit">
{#if isLoading}
Signing up...
{:else}
Sign up
{/if}
</Button>
</form>
{@render oauthLogin()}
<Button class="w-full" disabled={isLoading} type="submit">
{#if isLoading}
Signing up...
{:else}
Sign up
{/if}
</Button>
</form>
{@render oauthLogin()}
<div class="mt-4 text-center text-sm">
<Button onclick={() => (tabValue = 'login')} variant="link"
>Already have an account? Login
</Button>
</div>
</Card.Content>
</Card.Root>
</Tabs.Content>
</Tabs.Root>
<div class="mt-4 text-center text-sm">
<Button href="{base}/login/" variant="link"
>Already have an account? Login
</Button>
</div>
</Card.Content>
</Card.Root>
{/if}

View File

@@ -0,0 +1,60 @@
<script lang="ts">
import logo from "$lib/images/logo.svg";
import { Separator } from "$lib/components/ui/separator/index.js";
import background from '$lib/images/pawel-czerwinski-NTYYL9Eb9y8-unsplash.jpg?enhanced';
import { PUBLIC_VERSION } from '$env/static/public';
import { base } from '$app/paths';
import { page } from '$app/state';
import {setContext} from "svelte";
setContext("oauthProvider", () => page.data.oauthProvider)
let {data, children} = $props();
</script>
<div class="grid min-h-svh lg:grid-cols-2">
<div class="flex flex-col gap-4 p-6 md:p-10">
<div class="flex justify-center gap-2 md:justify-start">
<a class="flex items-center gap-2 " href="{base}/">
<div class="flex size-16 items-center justify-center rounded-md text-primary-foreground">
<img alt="MediaManager Logo" class="size-12" src={logo} />
</div>
<div>
<h1 class="text-2xl font-bold">Media Manager</h1>
<span class="truncate text-xs">{PUBLIC_VERSION}</span>
</div>
</a>
</div>
<div class="flex flex-1 items-center justify-center">
<div class="w-full max-w-[90vw]">
{@render children()}
</div>
</div>
<div class="flex flex-col items-center justify-center gap-3 text-center">
<a class="underline" href="https://maxdorninger.github.io/MediaManager/troubleshooting.html">
Trouble logging in?
</a>
<div
class="flex flex-wrap items-center justify-center gap-x-3 gap-y-1 text-sm text-muted-foreground"
>
<a class="underline" href="https://github.com/maxdorninger/MediaManager">GitHub</a>
<Separator class="h-4" orientation="vertical" />
<a class="underline" href="https://github.com/sponsors/maxdorninger">Donate</a>
<Separator class="h-4" orientation="vertical" />
<a
class="underline"
href="https://unsplash.com/photos/blue-white-and-red-abstract-painting-NTYYL9Eb9y8"
>
Image Credit
</a>
</div>
</div>
</div>
<div class="relative hidden lg:block">
<enhanced:img
src={background}
alt="background"
class="absolute inset-0 h-full w-full rounded-l-3xl object-cover dark:brightness-[0.8]"
/>
</div>
</div>

View File

@@ -0,0 +1,16 @@
import type {LayoutLoad} from './$types';
import {env} from '$env/dynamic/public';
const apiUrl = env.PUBLIC_API_URL;
export const load: LayoutLoad = async ({fetch}) => {
const response = await fetch(apiUrl + '/auth/metadata', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
});
return {oauthProvider: await response.json()};
};

View File

@@ -1,10 +1,9 @@
<script lang="ts">
import LoginForm from '$lib/components/login-form.svelte';
import logo from '$lib/images/logo.svg';
import background from '$lib/images/pawel-czerwinski-NTYYL9Eb9y8-unsplash.jpg?enhanced';
import { page } from '$app/state';
import {getContext} from "svelte";
let oauthProvider = page.data.oauthProvider;
let oauthProvider: () => {oauth_name: string} = getContext("oauthProvider");
</script>
<svelte:head>
@@ -15,27 +14,5 @@
/>
</svelte:head>
<div class="grid min-h-svh lg:grid-cols-2">
<div class="flex flex-col gap-4 p-6 md:p-10">
<div class="flex justify-center gap-2 md:justify-start">
<a class="flex items-center gap-2 font-medium" href="##">
<div class="flex size-16 items-center justify-center rounded-md text-primary-foreground">
<img alt="MediaManager Logo" class="size-12" src={logo} />
</div>
<h1 class="scale-110">Media Manager</h1>
</a>
</div>
<div class="flex flex-1 items-center justify-center">
<div class="w-full max-w-[90vw]">
<LoginForm {oauthProvider} />
</div>
</div>
</div>
<div class="relative hidden lg:block">
<enhanced:img
src={background}
alt="background"
class="absolute inset-0 h-full w-full rounded-l-3xl object-cover dark:brightness-[0.8]"
/>
</div>
</div>
<LoginForm oauthProvider={oauthProvider()} login={true} />

View File

@@ -1,16 +0,0 @@
import { env } from '$env/dynamic/public';
import type { PageLoad } from './$types';
const apiUrl = env.PUBLIC_API_URL;
export const load: PageLoad = async ({ fetch }) => {
const response = await fetch(apiUrl + '/auth/metadata', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
});
return { oauthProvider: await response.json() };
};

View File

@@ -67,90 +67,67 @@
/>
</svelte:head>
<div class="grid min-h-svh lg:grid-cols-2">
<div class="flex flex-col gap-4 p-6 md:p-10">
<div class="flex justify-center gap-2 md:justify-start">
<a class="flex items-center gap-2 font-medium" href="{base}/login">
<div class="flex size-16 items-center justify-center rounded-md text-primary-foreground">
<img alt="MediaManager Logo" class="size-12" src={logo} />
<Card class="mx-auto max-w-sm">
<CardHeader>
<CardTitle class="text-2xl">Forgot Password</CardTitle>
<CardDescription>
{#if isSuccess}
We've sent a password reset link to your email address if a SMTP server is
configured. Check your inbox and follow the instructions to reset your password. If
you didn't receive an email, please contact an administrator, the reset link will be
in the logs of MediaManager.
{:else}
Enter your email address and we'll send you a link to reset your password.
{/if}
</CardDescription>
</CardHeader>
<CardContent>
{#if isSuccess}
<div class="space-y-4">
<div class="rounded-lg bg-green-50 p-4 text-center dark:bg-green-950">
<p class="text-sm text-green-700 dark:text-green-300">
Password reset email sent successfully!
</p>
</div>
<h1 class="scale-110">Media Manager</h1>
<div class="text-center text-sm text-muted-foreground">
<p>Didn't receive the email? Check your spam folder or</p>
<button
class="text-primary hover:underline"
onclick={() => {
isSuccess = false;
email = '';
}}
>
try again
</button>
</div>
</div>
{:else}
<form class="grid gap-4" onsubmit={handleSubmit}>
<div class="grid gap-2">
<Label for="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
bind:value={email}
disabled={isLoading}
required
/>
</div>
<Button type="submit" class="w-full" disabled={isLoading || !email}>
{#if isLoading}
Sending Reset Email...
{:else}
Send Reset Email
{/if}
</Button>
</form>
{/if}
<div class="mt-4 text-center text-sm">
<a href="{base}/login" class="font-semibold text-primary hover:underline">
Back to Login
</a>
</div>
<div class="flex flex-1 items-center justify-center">
<div class="w-full max-w-[90vw]">
<Card class="mx-auto max-w-sm">
<CardHeader>
<CardTitle class="text-2xl">Forgot Password</CardTitle>
<CardDescription>
{#if isSuccess}
We've sent a password reset link to your email address if a SMTP server is
configured. Check your inbox and follow the instructions to reset your password. If
you didn't receive an email, please contact an administrator, the reset link will be
in the logs of MediaManager.
{:else}
Enter your email address and we'll send you a link to reset your password.
{/if}
</CardDescription>
</CardHeader>
<CardContent>
{#if isSuccess}
<div class="space-y-4">
<div class="rounded-lg bg-green-50 p-4 text-center dark:bg-green-950">
<p class="text-sm text-green-700 dark:text-green-300">
Password reset email sent successfully!
</p>
</div>
<div class="text-center text-sm text-muted-foreground">
<p>Didn't receive the email? Check your spam folder or</p>
<button
class="text-primary hover:underline"
onclick={() => {
isSuccess = false;
email = '';
}}
>
try again
</button>
</div>
</div>
{:else}
<form class="grid gap-4" onsubmit={handleSubmit}>
<div class="grid gap-2">
<Label for="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
bind:value={email}
disabled={isLoading}
required
/>
</div>
<Button type="submit" class="w-full" disabled={isLoading || !email}>
{#if isLoading}
Sending Reset Email...
{:else}
Send Reset Email
{/if}
</Button>
</form>
{/if}
<div class="mt-4 text-center text-sm">
<a href="{base}/login" class="font-semibold text-primary hover:underline">
Back to Login
</a>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
<div class="relative hidden lg:block">
<enhanced:img
src={background}
alt="background"
class="absolute inset-0 h-full w-full rounded-l-3xl object-cover dark:brightness-[0.8]"
/>
</div>
</div>
</CardContent>
</Card>

View File

@@ -81,80 +81,57 @@
<meta content="Reset your MediaManager password with a secure token" name="description" />
</svelte:head>
<div class="grid min-h-svh lg:grid-cols-2">
<div class="flex flex-col gap-4 p-6 md:p-10">
<div class="flex justify-center gap-2 md:justify-start">
<a class="flex items-center gap-2 font-medium" href="{base}/login">
<div class="flex size-16 items-center justify-center rounded-md text-primary-foreground">
<img alt="MediaManager Logo" class="size-12" src={logo} />
</div>
<h1 class="scale-110">Media Manager</h1>
<Card class="mx-auto max-w-sm">
<CardHeader>
<CardTitle class="text-2xl">Reset Password</CardTitle>
<CardDescription>Enter your new password below.</CardDescription>
</CardHeader>
<CardContent>
<form class="grid gap-4" onsubmit={handleSubmit}>
<div class="grid gap-2">
<Label for="new-password">New Password</Label>
<Input
id="new-password"
type="password"
placeholder="Enter your new password"
bind:value={newPassword}
disabled={isLoading}
required
minlength={1}
/>
</div>
<div class="grid gap-2">
<Label for="confirm-password">Confirm Password</Label>
<Input
id="confirm-password"
type="password"
placeholder="Confirm your new password"
bind:value={confirmPassword}
disabled={isLoading}
required
minlength={1}
/>
</div>
<Button
type="submit"
class="w-full"
disabled={isLoading || !newPassword || !confirmPassword}
>
{#if isLoading}
Resetting Password...
{:else}
Reset Password
{/if}
</Button>
</form>
<div class="mt-4 text-center text-sm">
<a href="{base}/login" class="font-semibold text-primary hover:underline">
Back to Login
</a>
<span class="mx-2 text-muted-foreground"></span>
<a href="{base}/login/forgot-password" class="text-primary hover:underline">
Request New Reset Link
</a>
</div>
<div class="flex flex-1 items-center justify-center">
<div class="w-full max-w-[90vw]">
<Card class="mx-auto max-w-sm">
<CardHeader>
<CardTitle class="text-2xl">Reset Password</CardTitle>
<CardDescription>Enter your new password below.</CardDescription>
</CardHeader>
<CardContent>
<form class="grid gap-4" onsubmit={handleSubmit}>
<div class="grid gap-2">
<Label for="new-password">New Password</Label>
<Input
id="new-password"
type="password"
placeholder="Enter your new password"
bind:value={newPassword}
disabled={isLoading}
required
minlength={1}
/>
</div>
<div class="grid gap-2">
<Label for="confirm-password">Confirm Password</Label>
<Input
id="confirm-password"
type="password"
placeholder="Confirm your new password"
bind:value={confirmPassword}
disabled={isLoading}
required
minlength={1}
/>
</div>
<Button
type="submit"
class="w-full"
disabled={isLoading || !newPassword || !confirmPassword}
>
{#if isLoading}
Resetting Password...
{:else}
Reset Password
{/if}
</Button>
</form>
<div class="mt-4 text-center text-sm">
<a href="{base}/login" class="font-semibold text-primary hover:underline">
Back to Login
</a>
<span class="mx-2 text-muted-foreground"></span>
<a href="{base}/login/forgot-password" class="text-primary hover:underline">
Request New Reset Link
</a>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
<div class="relative hidden lg:block">
<enhanced:img
src={background}
alt="background"
class="absolute inset-0 h-full w-full rounded-l-3xl object-cover dark:brightness-[0.8]"
/>
</div>
</div>
</CardContent>
</Card>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import LoginForm from '$lib/components/login-form.svelte';
import {getContext} from "svelte";
let oauthProvider: () => {oauth_name: string} = getContext("oauthProvider");
</script>
<svelte:head>
<title>Login - MediaManager</title>
<meta
content="Signup - MediaManager"
name="description"
/>
</svelte:head>
<LoginForm login={false} oauthProvider={oauthProvider()}/>

View File

@@ -11,33 +11,27 @@
<meta content="Verify your MediaManager account to complete registration" name="description" />
</svelte:head>
<div
class="flex min-h-screen flex-col items-center justify-center bg-background px-4 py-12 sm:px-6 lg:px-8"
>
<div class="absolute left-4 top-4">
<Logo />
</div>
<div class="absolute right-4 top-4">
<Button onclick={() => handleLogout()} variant="outline">Logout</Button>
</div>
<div class="mx-auto w-full max-w-md text-center">
<div class="mb-6">
<UserCheck class="mx-auto h-16 w-16 text-primary" />
</div>
<h1 class="mt-4 text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
Account Pending Activation
</h1>
<p class="mt-4 text-lg text-muted-foreground">
Your account has been successfully created, but activation by an administrator is required.
</p>
<div class="mt-8">
<Button href="{base}/dashboard">Go to Dashboard</Button>
</div>
<p class="mt-10 text-sm text-muted-foreground">
The above button will only work once your account is verified.
</p>
<p class="end mt-10 text-sm text-muted-foreground">
If you have any questions, please contact an administrator.
</p>
</div>
<div class="absolute right-4 top-4">
<Button onclick={() => handleLogout()} variant="outline">Logout</Button>
</div>
<div class="mx-auto w-full max-w-md text-center">
<div class="mb-6">
<UserCheck class="mx-auto h-16 w-16 text-primary" />
</div>
<h1 class="mt-4 text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
Account Pending Activation
</h1>
<p class="mt-4 text-lg text-muted-foreground">
Your account has been successfully created, but activation by an administrator is required.
</p>
<div class="mt-8">
<Button href="{base}/dashboard">Go to Dashboard</Button>
</div>
<p class="mt-10 text-sm text-muted-foreground">
The above button will only work once your account is verified.
</p>
<p class="end mt-10 text-sm text-muted-foreground">
If you have any questions, please contact an administrator.
</p>
</div>