Working login modal pointed to the correct api endpoint

This commit is contained in:
Imbus 2023-10-21 07:54:47 +02:00
parent 508cf528af
commit c2103071bc
3 changed files with 174 additions and 27 deletions

View file

@ -9,7 +9,7 @@
<title>FrostByteSolid</title>
</head>
<body class="min-h-screen">
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>

View file

@ -1,4 +1,4 @@
import { createSignal } from "solid-js";
import { Accessor, createSignal, useContext } from "solid-js";
import { createContext } from "solid-js";
import { Route, Routes, A } from "@solidjs/router";
@ -6,30 +6,71 @@ import { Route, Routes, A } from "@solidjs/router";
import { createPost, getPosts } from "./api";
import { Post, NewPost } from "./api";
export const TestContext = createContext("Test123");
// Representing the state of varoious modals.
// So far we only have one modal, but we can add more later
// by adding more fields to this interface, or maybe an enum
interface ModalContextType {
loginModalOpen: Accessor<boolean>;
setLoginModalOpen: (value: boolean) => void;
}
interface LoginContextType {
token: Accessor<string>;
setToken: (value: string) => void;
username: Accessor<string>;
setUsername: (value: string) => void;
}
// It is unclear to me if this is the idiomatic way to do this in Solid
export const ModalContext = createContext<ModalContextType>();
export const LoginContext = createContext<LoginContextType>();
function Root() {
// All of these are passed into context providers
const [loginModalOpen, setLoginModalOpen] = createSignal(false);
const [token, setToken] = createSignal("");
const [username, setUsername] = createSignal("");
// This may not be the best place to do this.
localStorage.getItem("token") && setToken(localStorage.getItem("token")!);
localStorage.getItem("username") &&
setUsername(localStorage.getItem("username")!);
return (
<>
<TestContext.Provider value="Test123">
<ModalContext.Provider value={{ loginModalOpen, setLoginModalOpen }}>
<LoginContext.Provider
value={{ token, setToken, username, setUsername }}
>
<div class="flex flex-col items-center my-2">
<Navbar />
<Login />
<div class="flex flex-col items-center md:w-96 space-y-2">
<Primary />
</div>
</div>
</TestContext.Provider>
</LoginContext.Provider>
</ModalContext.Provider>
</>
);
}
function Navbar() {
let modal_ctx = useContext(ModalContext);
let login_ctx = useContext(LoginContext);
return (
<div class="navbar bg-base-100 max-w-3xl max-w flex justify-evenly">
<a class="btn btn-ghost normal-case text-xl">FrostByte</a>
<Menu />
<A href="/login" class="btn btn-ghost normal-case text-sm">
Login
<A
href="#"
class="btn btn-ghost normal-case text-sm"
onClick={(b) => {
b.preventDefault();
modal_ctx?.setLoginModalOpen(true);
}}
>
{login_ctx?.token() != "" ? login_ctx?.username() : "Login"}
</A>
</div>
);
@ -54,11 +95,14 @@ function Menu() {
function NewPostInputArea() {
const [content, setContent] = createSignal("");
const login_ctx = useContext(LoginContext);
return (
<div class="flex flex-col space-y-2">
<textarea
class="textarea textarea-bordered"
placeholder="Speak your mind..."
maxLength={500}
oninput={(input) => {
setContent(input.target.value);
}}
@ -70,7 +114,10 @@ function NewPostInputArea() {
}
onclick={() => {
if (content() == "") return;
createPost({ content: content(), token: "" } as NewPost);
createPost({
content: content(),
token: login_ctx?.token(),
} as NewPost);
}}
>
Submit
@ -85,12 +132,16 @@ function Posts() {
getPosts().then((posts) => {
setPosts(posts as any);
setLoading(false)
setLoading(false);
});
return (
<div class="flex flex-col space-y-2 w-full md:w-96">
{ loading() ? <span class="loading loading-spinner loading-lg self-center"></span> : <></> }
{loading() ? (
<span class="loading loading-spinner loading-lg self-center"></span>
) : (
<></>
)}
{posts().map((post) => {
if (post.content == "") return; // Filtering out empty posts, remove this later
return <PostSegment post={post}></PostSegment>;
@ -103,9 +154,7 @@ function PostSegment({ post }: { post: Post }) {
return (
<div class="card bg-base-200 shadow-lg compact text-base-content w-full">
<div class="card-body">
<p class="text-base-content">{post.content}</p>
{/* <p>{post.votes.up}</p>
<p>{post.votes.down}</p> */}
<p class="text-base-content break-words">{post.content}</p>
</div>
</div>
);
@ -116,18 +165,118 @@ function Primary() {
<Routes>
<Route path="/" element={<Posts />} />
<Route path="/new" element={<NewPostInputArea />} />
<Route path="/boards" element={<div>Boards</div>} />
<Route path="/login" element={<Login />} />
</Routes>
);
}
// This is a modal
function Login() {
const modal_ctx = useContext(ModalContext);
return (
<div>
Login
<input class="input input-bordered" type="text" />
<dialog id="login_modal" class="modal" open={modal_ctx?.loginModalOpen()}>
<div class="modal-box">
<h3 class="font-bold text-lg">Hello!</h3>
<p class="py-4">Login to your FrostByte account.</p>
<LoginForm />
</div>
<form
method="dialog"
// This backdrop renders choppy on my machine. Likely because of the blur filter or misuse of css transisions
class="modal-backdrop backdrop-brightness-50 backdrop-blur-sm backdrop-contrast-100 transition-all transition-300"
onsubmit={(e) => {
// This is just needed to set the state to false
// The modal will close itself without this code, but without setting the state
e.preventDefault();
modal_ctx?.setLoginModalOpen(false);
}}
>
<button>close</button>
</form>
</dialog>
);
}
// This function is responsible for sending the login request to the server
// and storing the token in localstorage
async function submitLogin(
username: string,
password: string
): Promise<string> {
const response = await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});
if (response.ok) {
const data = await response.json();
if (data.token && data.username) {
localStorage.setItem("token", data.token);
localStorage.setItem("username", data.username);
return data.token;
}
}
return "";
}
function LoginForm() {
const modal_ctx = useContext(ModalContext);
const [username, setUsername] = createSignal("");
const [password, setPassword] = createSignal("");
const [waiting, setWaiting] = createSignal(false);
const [error, setError] = createSignal(false);
async function loginFailed() {
setError(true);
setWaiting(false);
setTimeout(() => {
setError(false);
}, 1000);
}
return (
<form class="form-control">
<label class="label">
<span class="label-text">Username</span>
</label>
<input
type="text"
placeholder="username"
class="input input-bordered"
onChange={(e) => {
setUsername(e.target.value);
}}
/>
<label class="label">
<span class="label-text">Password</span>
</label>
<input
type="password"
placeholder="password"
class="input input-bordered"
onChange={(e) => {
setPassword(e.target.value);
}}
/>
<button
class={"btn btn-primary mt-4" + (error() ? " btn-error" : "")}
onClick={(b) => {
b.preventDefault();
setWaiting(true);
submitLogin(username(), password()).then((token) => {
if (token != "") {
setWaiting(false);
setError(false);
modal_ctx?.setLoginModalOpen(false);
} else {
loginFailed();
}
});
}}
>
{waiting() ? "Logging in..." : "Login"}
</button>
</form>
);
}

View file

@ -1,6 +1,4 @@
// const PORT = 3000;
// const API_URL = `http://localhost:${PORT}/api/`;
// const API_URL2 = new URL(API_URL);
// This file contains types and functions related to interacting with the API.
export interface NewPost {
content: string;