Skip to content

Commit

Permalink
feat: add sign in and sign out logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Slartibartfass2 committed Dec 17, 2024
1 parent 3825a83 commit 2b714a5
Show file tree
Hide file tree
Showing 28 changed files with 174 additions and 59 deletions.
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"eslint-plugin-svelte": "^2.46.1",
"globals": "^15.13.0",
"jsdom": "^25.0.1",
"jwt-decode": "^4.0.0",
"lucide-svelte": "^0.456.0",
"msw": "^2.6.9",
"postcss": "^8.4.49",
Expand Down
2 changes: 1 addition & 1 deletion src/lib/backend-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export interface IAuthorController {
}

export interface IBackendController {
signIn(email: string, password: string): Promise<User>;
signIn(email: string, password: string): Promise<void>;
signOut(): Promise<void>;
requestEmailForgottenPassword(email: string): Promise<void>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import type { User } from "$lib/model/backend";
interface Props {
user: User;
user?: User;
backRef?: string | undefined;
tabs: Tab[];
defaultTabValue: (typeof tabs)[number]["value"];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import type { Paper, PaperSpec, User } from "$lib/model/backend";
interface Props {
user: User;
user?: User;
backRef?: string | undefined;
paper: Paper | PaperSpec;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
type TabValue = (typeof tabs)[number]["value"];
interface Props {
user: User;
user?: User;
project: Project;
defaultTabValue: TabValue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import type { User } from "$lib/model/backend";
interface Props {
user: User;
user?: User;
backRef?: string | undefined;
title: string;
tabs: Tab[];
Expand Down
16 changes: 13 additions & 3 deletions src/lib/components/composites/navigation-bar/UserMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
import type { Icon } from "lucide-svelte";
import type { User } from "$lib/model/backend";
import UserAvatar from "$lib/components/composites/user-avatar/UserAvatar.svelte";
import { goto } from "$app/navigation";
interface Props {
user: User;
user?: User;
}
const { user }: Props = $props();
Expand Down Expand Up @@ -47,6 +48,11 @@
href: "/settings/account",
},
];
function signOut() {
localStorage.removeItem("token");
goto("/signin");
}
</script>

<DropdownMenu.Root>
Expand All @@ -56,7 +62,11 @@
<DropdownMenu.Content class="w-60" side="bottom" sideOffset={0} align="start">
<DropdownMenu.Group>
<DropdownMenu.GroupHeading>
{`${user.firstName} ${user.lastName}`}
{#if user}
{`${user.firstName} ${user.lastName}`}
{:else}
Loading User...
{/if}
</DropdownMenu.GroupHeading>
<DropdownMenu.Separator />
<DropdownMenu.Group>
Expand All @@ -73,7 +83,7 @@
{/each}
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Item>
<DropdownMenu.Item onclick={signOut}>
<LogOut class="mr-2 size-4" />
<span>Sign out</span>
<DropdownMenu.Shortcut>⇧⌘Q</DropdownMenu.Shortcut>
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/composites/user-avatar/UserAvatar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
import X from "lucide-svelte/icons/x";
interface Props {
user: User;
user?: User;
reviewDecision?: ReviewDecision;
}
const { user, reviewDecision }: Props = $props();
const getInitial = (text: string) => (text.length > 0 ? text[0].toUpperCase() : "");
const userInitials = `${getInitial(user.firstName)}${getInitial(user.lastName)}`;
const userInitials = `${getInitial(user?.firstName ?? "")}${getInitial(user?.lastName ?? "")}`;
</script>

<!--
Expand Down
14 changes: 11 additions & 3 deletions src/lib/controller/backend-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
AuthorSpec,
Paper,
PaperSpec,
SignInResponse,
} from "../model/backend";
import { AuthorController } from "./author-controller";
import { HttpClient } from "./http-client";
Expand All @@ -33,9 +34,16 @@ export class BackendController implements IBackendController {
return BackendController.instance;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async signIn(email: string, password: string): Promise<User> {
throw new Error("Method not implemented.");
async signIn(email: string, password: string): Promise<void> {
const payload = {
email: email,
password: password,
};
// TODO: Omit trailing slash, when backend doesn't hardcode unauthorized routes anymore
const response: SignInResponse = await this.client
.post("login/", payload)
.then((response) => response.json());
localStorage.setItem("token", response.token);
}

async signOut(): Promise<void> {
Expand Down
21 changes: 17 additions & 4 deletions src/lib/controller/http-client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { browser } from "$app/environment";
import { PUBLIC_API_BASE_URL } from "$env/static/public";

/**
Expand Down Expand Up @@ -37,12 +38,24 @@ export class HttpClient {
body?: T,
headers?: HeadersInit,
): Promise<Response> {
let jwt: string | null = null;
if (browser) {
jwt = localStorage.getItem("token");
}

const requestHeaders: Headers = new Headers({
"Content-Type": "application/json",
...headers,
});
if (jwt) {
// TODO: Change to "Authorization" when backend is updated
// requestHeaders.set("Authorization", `Bearer ${jwt}`);
requestHeaders.set("authenticationToken", jwt);
}

return fetch(`${this.basePath}/${path}`, {
method,
headers: {
"Content-Type": "application/json",
...headers,
},
headers: requestHeaders,
body: body ? JSON.stringify(body) : undefined,
});
}
Expand Down
28 changes: 28 additions & 0 deletions src/lib/current-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { goto } from "$app/navigation";
import { jwtDecode } from "jwt-decode";
import type { User } from "./model/backend";
import { browser } from "$app/environment";

/**
* Resolves the current user from the local storage token
*
* @param redirectOnMissing - When true, and the jwt is missing, the page will be redirected to the sign-in page
* @returns The currently signed in user - if they exist, otherwise undefined
*/
export function getCurrentUser(redirectOnMissing: boolean = true): User | undefined {
// If the script is not running in the browser, the local storage cannot be accessed
if (!browser) {
return undefined;
}

const jwt = localStorage.getItem("token");
if (jwt) {
return jwtDecode(jwt) as User;
}

// When no JWT is stored, redirect to the sign-in page
if (redirectOnMissing) {
goto("/signin");
}
return undefined;
}
4 changes: 4 additions & 0 deletions src/lib/model/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,7 @@ export interface Author {
}

export type AuthorSpec = Omit<Author, "id">;

export interface SignInResponse {
token: string;
}
55 changes: 47 additions & 8 deletions src/routes/(auth)/signin/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,33 @@
import PasswordInput from "$lib/components/composites/input/PasswordInput.svelte";
import { Button } from "$lib/components/primitives/button/index.js";
import * as Card from "$lib/components/primitives/card/index.js";
import { BackendController } from "$lib/controller/backend-controller";
import CircleAlert from "lucide-svelte/icons/circle-alert";
import * as Alert from "$lib/components/primitives/alert/index.js";
import LoaderCircle from "lucide-svelte/icons/loader-circle";
import { goto } from "$app/navigation";
import { getCurrentUser } from "$lib/current-user";
let email = $state("");
let password = $state("");
let error = $state<string | undefined>(undefined);
let loading = $state(false);
const user = getCurrentUser();
function handleSubmit(event: Event) {
async function handleSubmit(event: Event) {
event.preventDefault();
// Don't validate the input fields as they only need to match data on the server
const signInData = { email, password };
// TODO: CALL API TO SIGN IN
console.log("sign in with", signInData);
error = undefined;
loading = true;
await BackendController.getInstance()
.signIn(email, password)
.then(() => {
goto("/");
})
.catch((signInError) => {
error = signInError.message;
});
loading = false;
}
</script>

Expand All @@ -25,7 +41,16 @@
<Card.Title class="text-3xl">Sign In</Card.Title>
<Card.Description>Enter your credentials below to sign in to your account</Card.Description>
</Card.Header>
<Card.Content class="flex flex-col w-full">
<Card.Content class="flex flex-col w-full gap-4">
{#if user}
<Alert.Root variant="default">
<CircleAlert class="size-4" />
<Alert.Title>Already Signed In</Alert.Title>
<Alert.Description>
Do you want to preceed to the <a href="/" class="underline">Home Page</a>?
</Alert.Description>
</Alert.Root>
{/if}
<form class="flex flex-col gap-5" onsubmit={handleSubmit}>
<Input
class="w-full"
Expand All @@ -37,11 +62,25 @@
bind:value={email}
/>
<PasswordInput class="w-full" showForgotPasswordLink bind:value={password} />
<Button type="submit" class="w-full">Sign In</Button>
{#if loading}
<Button type="submit" class="w-full" disabled>
<LoaderCircle class="animate-spin" />
Signing In
</Button>
{:else}
<Button type="submit" class="w-full">Sign In</Button>
{/if}
</form>
<div class="mt-4 text-center text-sm">
<div class="text-center text-sm">
You don't have an account?
<a href="/signup" class="underline"> Sign Up </a>
</div>
{#if error}
<Alert.Root variant="destructive">
<CircleAlert class="size-4" />
<Alert.Title>Error</Alert.Title>
<Alert.Description>{error}</Alert.Description>
</Alert.Root>
{/if}
</Card.Content>
</Card.Root>
16 changes: 0 additions & 16 deletions src/routes/+layout.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<script lang="ts">
import SimpleNavigationBar from "$lib/components/composites/navigation-bar/SimpleNavigationBar.svelte";
import { getCurrentUser } from "$lib/current-user.js";
const { data } = $props();
const { user } = data;
const user = getCurrentUser();
</script>

<svelte:head>
Expand Down
4 changes: 2 additions & 2 deletions src/routes/archivedprojects/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<script lang="ts">
import SimpleNavigationBar from "$lib/components/composites/navigation-bar/SimpleNavigationBar.svelte";
import { getCurrentUser } from "$lib/current-user.js";
const { data } = $props();
const { user } = data;
const user = getCurrentUser();
</script>

<svelte:head>
Expand Down
4 changes: 3 additions & 1 deletion src/routes/componentsusage/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
import SimpleNavigationBar from "$lib/components/composites/navigation-bar/SimpleNavigationBar.svelte";
import SearchBar from "$lib/components/composites/search-bar/SearchBar.svelte";
import PaperListEntry from "$lib/components/composites/paper-components/PaperListEntry.svelte";
import { getCurrentUser } from "$lib/current-user.js";
const { data } = $props();
const { user, paper, paper2 } = data;
const { paper, paper2 } = data;
const user = getCurrentUser();
const searchPlaceholderText: string = "Search paper";
</script>
Expand Down
4 changes: 2 additions & 2 deletions src/routes/invitations/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<script lang="ts">
import SimpleNavigationBar from "$lib/components/composites/navigation-bar/SimpleNavigationBar.svelte";
import { getCurrentUser } from "$lib/current-user.js";
const { data } = $props();
const { user } = data;
const user = getCurrentUser();
</script>

<svelte:head>
Expand Down
4 changes: 3 additions & 1 deletion src/routes/paper/[paperId]/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<script lang="ts">
import PaperNavigationBar from "$lib/components/composites/navigation-bar/PaperNavigationBar.svelte";
import { getCurrentUser } from "$lib/current-user.js";
const { data } = $props();
const { user, paper } = data;
const { paper } = data;
const user = getCurrentUser();
</script>

<svelte:head>
Expand Down
Loading

0 comments on commit 2b714a5

Please sign in to comment.