frontend: initial commit
This commit is contained in:
5
frontend/src/routes/+layout.js
Normal file
5
frontend/src/routes/+layout.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/** @type {import('./$types').LayoutLoad} */
|
||||
export async function load({ fetch, url, data }) {
|
||||
const { user } = data;
|
||||
return { fetch, url: url.pathname, user };
|
||||
}
|
||||
6
frontend/src/routes/+layout.server.js
Normal file
6
frontend/src/routes/+layout.server.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/** @type {import('./$types').LayoutServerLoad} */
|
||||
export async function load({ locals }) {
|
||||
return {
|
||||
user: locals.user,
|
||||
};
|
||||
}
|
||||
17
frontend/src/routes/+layout.svelte
Normal file
17
frontend/src/routes/+layout.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script>
|
||||
import Footer from "$lib/components/Footer.svelte";
|
||||
import Header from "$lib/components/Header.svelte";
|
||||
import Transition from "$lib/components/Transition.svelte";
|
||||
import "$lib/css/styles.min.css";
|
||||
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<Transition key={data.url} duration={600}>
|
||||
<Header />
|
||||
|
||||
<slot />
|
||||
|
||||
<Footer />
|
||||
</Transition>
|
||||
19
frontend/src/routes/+page.svelte
Normal file
19
frontend/src/routes/+page.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<!-- <script>
|
||||
import Developer from "$lib/img/hero-image.png";
|
||||
</script> -->
|
||||
|
||||
<div class="hero-container">
|
||||
<!-- <div class="hero-logo"><img src={Developer} alt="Alexander Stölting" /></div> -->
|
||||
<h3 class="hero-subtitle subtitle">
|
||||
This application is the demonstration of a series of tutorials on
|
||||
session-based authentication using Go at the backend and JavaScript
|
||||
(SvelteKit) on the front-end.
|
||||
</h3>
|
||||
<div class="hero-buttons-container">
|
||||
<a
|
||||
class="button-dark"
|
||||
href="https://dev.to/sirneij/series/23239"
|
||||
data-learn-more>Learn more</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
98
frontend/src/routes/auth/login/+page.server.js
Normal file
98
frontend/src/routes/auth/login/+page.server.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import { BASE_API_URI } from "$lib/utils/constants";
|
||||
import { formatError } from "$lib/utils/helpers";
|
||||
import { fail, redirect } from "@sveltejs/kit";
|
||||
|
||||
/** @type {import('./$types').PageServerLoad} */
|
||||
export async function load({ locals }) {
|
||||
// redirect user if logged in
|
||||
if (locals.user) {
|
||||
throw redirect(302, "/");
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {import('./$types').Actions} */
|
||||
export const actions = {
|
||||
/**
|
||||
*
|
||||
* @param request - The request object
|
||||
* @param fetch - Fetch object from sveltekit
|
||||
* @param cookies - SvelteKit's cookie object
|
||||
* @returns Error data or redirects user to the home page or the previous page
|
||||
*/
|
||||
login: async ({ request, fetch, cookies }) => {
|
||||
const data = await request.formData();
|
||||
const email = String(data.get("email"));
|
||||
const password = String(data.get("password"));
|
||||
const next = String(data.get("next"));
|
||||
|
||||
/** @type {RequestInit} */
|
||||
const requestInitOptions = {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
password: password,
|
||||
}),
|
||||
};
|
||||
|
||||
const res = await fetch(`${BASE_API_URI}/users/login/`, requestInitOptions);
|
||||
|
||||
console.log("Login response status:", res.status);
|
||||
console.log("Login response headers:", Object.fromEntries(res.headers));
|
||||
|
||||
if (!res.ok) {
|
||||
let errorMessage = `HTTP error! status: ${res.status}`;
|
||||
try {
|
||||
const errorData = await res.json();
|
||||
errorMessage = errorData.error || errorMessage;
|
||||
} catch (parseError) {
|
||||
console.error("Failed to parse error response:", parseError);
|
||||
errorMessage = await res.text(); // Get the raw response text if JSON parsing fails
|
||||
}
|
||||
console.error("Login failed:", errorMessage);
|
||||
return fail(res.status, {
|
||||
errors: [{ error: errorMessage, id: Date.now() }],
|
||||
});
|
||||
}
|
||||
|
||||
const responseBody = await res.json();
|
||||
console.log("Login response body:", responseBody);
|
||||
|
||||
// Check for the cookie in the response headers
|
||||
const setCookieHeader = res.headers.get("set-cookie");
|
||||
console.log("Set-Cookie header:", setCookieHeader);
|
||||
|
||||
if (setCookieHeader) {
|
||||
// Parse the Set-Cookie header to get the JWT
|
||||
const jwtCookie = setCookieHeader.split(";")[0];
|
||||
const [cookieName, cookieValue] = jwtCookie.split("=");
|
||||
if (cookieName.trim() === "jwt") {
|
||||
console.log("JWT cookie found in response");
|
||||
cookies.set("jwt", cookieValue.trim(), {
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
sameSite: "strict",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
});
|
||||
} else {
|
||||
console.log("JWT cookie not found in response");
|
||||
}
|
||||
} else {
|
||||
console.log("No Set-Cookie header in response");
|
||||
}
|
||||
|
||||
console.log("Redirecting to:", next || "/");
|
||||
throw redirect(303, next || "/");
|
||||
},
|
||||
// if (!res.ok) {
|
||||
// const response = await res.json();
|
||||
// const errors = formatError(response.error);
|
||||
// return fail(400, { errors: errors });
|
||||
// }
|
||||
|
||||
// throw redirect(303, next || "/");
|
||||
// },
|
||||
};
|
||||
76
frontend/src/routes/auth/login/+page.svelte
Normal file
76
frontend/src/routes/auth/login/+page.svelte
Normal file
@@ -0,0 +1,76 @@
|
||||
<script>
|
||||
import { applyAction, enhance } from "$app/forms";
|
||||
import { page } from "$app/stores";
|
||||
import { receive, send } from "$lib/utils/helpers";
|
||||
|
||||
/** @type {import('./$types').ActionData} */
|
||||
export let form;
|
||||
|
||||
/** @type {import('./$types').SubmitFunction} */
|
||||
const handleLogin = async () => {
|
||||
return async ({ result }) => {
|
||||
await applyAction(result);
|
||||
};
|
||||
};
|
||||
|
||||
let message = "";
|
||||
if ($page.url.searchParams.get("message")) {
|
||||
message = $page.url.search.split("=")[1].replaceAll("%20", " ");
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<form
|
||||
class="content"
|
||||
method="POST"
|
||||
action="?/login"
|
||||
use:enhance={handleLogin}
|
||||
>
|
||||
<h1 class="step-title">Login User</h1>
|
||||
{#if form?.errors}
|
||||
{#each form?.errors as error (error.id)}
|
||||
<h4
|
||||
class="step-subtitle warning"
|
||||
in:receive={{ key: error.id }}
|
||||
out:send={{ key: error.id }}
|
||||
>
|
||||
{error.error}
|
||||
</h4>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#if message}
|
||||
<h4 class="step-subtitle">{message}</h4>
|
||||
{/if}
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
name="next"
|
||||
value={$page.url.searchParams.get("next")}
|
||||
/>
|
||||
<div class="input-box">
|
||||
<span class="label">Email:</span>
|
||||
<input
|
||||
class="input"
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="Email address"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-box">
|
||||
<span class="label">Password:</span>
|
||||
<input
|
||||
class="input"
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
/>
|
||||
<a href="/auth/password/request-change" style="margin-left: 1rem;"
|
||||
>Forgot password?</a
|
||||
>
|
||||
</div>
|
||||
<div class="btn-container">
|
||||
<button class="button-dark">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
43
frontend/src/routes/auth/logout/+page.server.js
Normal file
43
frontend/src/routes/auth/logout/+page.server.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { BASE_API_URI } from "$lib/utils/constants";
|
||||
import { fail, redirect } from "@sveltejs/kit";
|
||||
|
||||
/** @type {import('./$types').PageServerLoad} */
|
||||
export async function load({ locals }) {
|
||||
// redirect user if not logged in
|
||||
if (!locals.user) {
|
||||
throw redirect(302, `/auth/login?next=/auth/logout`);
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {import('./$types').Actions} */
|
||||
export const actions = {
|
||||
default: async ({ fetch, cookies }) => {
|
||||
/** @type {RequestInit} */
|
||||
const requestInitOptions = {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Cookie: `jwt=${cookies.get("jwt")}`,
|
||||
},
|
||||
};
|
||||
|
||||
const res = await fetch(
|
||||
`${BASE_API_URI}/users/backend/logout/`,
|
||||
requestInitOptions
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
const response = await res.json();
|
||||
const errors = [];
|
||||
errors.push({ error: response.error, id: 0 });
|
||||
return fail(400, { errors: errors });
|
||||
}
|
||||
|
||||
// eat the cookie
|
||||
cookies.delete("jwt", { path: "/" });
|
||||
|
||||
// redirect the user
|
||||
throw redirect(302, "/auth/login");
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user