Splitting client code
This commit is contained in:
parent
c2103071bc
commit
3c48698522
5 changed files with 233 additions and 196 deletions
92
client-solid/src/Login.tsx
Normal file
92
client-solid/src/Login.tsx
Normal file
|
@ -0,0 +1,92 @@
|
|||
import { createSignal, useContext } from "solid-js";
|
||||
import { LoginContext, ModalContext } from "./Root";
|
||||
|
||||
export function LoginForm() {
|
||||
const modal_ctx = useContext(ModalContext);
|
||||
const login_ctx = useContext(LoginContext);
|
||||
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"
|
||||
value={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"
|
||||
value={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);
|
||||
login_ctx?.setUsername(username());
|
||||
setUsername("");
|
||||
setPassword("");
|
||||
login_ctx?.setToken(token);
|
||||
modal_ctx?.setLoginModalOpen(false);
|
||||
} else {
|
||||
loginFailed();
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
{waiting() ? "Logging in..." : "Login"}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
// This function is responsible for sending the login request to the server
|
||||
// and storing the token in localstorage
|
||||
export 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 "";
|
||||
}
|
||||
|
83
client-solid/src/Navbar.tsx
Normal file
83
client-solid/src/Navbar.tsx
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { useContext } from "solid-js";
|
||||
import { A } from "@solidjs/router";
|
||||
import { LoginContext } from "./Root";
|
||||
import { ModalContext } from "./Root";
|
||||
import { LoginForm } from "./Login";
|
||||
|
||||
function Menu() {
|
||||
let login_ctx = useContext(LoginContext);
|
||||
return (
|
||||
<ul class="menu menu-horizontal bg-base-200 rounded-box space-x-2">
|
||||
<li>
|
||||
<A href="/" end>
|
||||
Home
|
||||
</A>
|
||||
</li>
|
||||
{login_ctx?.token() != "" ? (
|
||||
<li>
|
||||
<A href="/new" end>
|
||||
New
|
||||
</A>
|
||||
</li>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export 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-around">
|
||||
<a class="btn btn-ghost normal-case text-xl">FrostByte</a>
|
||||
<Menu />
|
||||
<A
|
||||
href="#"
|
||||
class="btn btn-ghost normal-case text-sm"
|
||||
onClick={(b) => {
|
||||
b.preventDefault();
|
||||
if (login_ctx?.token() != "") {
|
||||
localStorage.setItem("token", "");
|
||||
localStorage.setItem("username", "");
|
||||
login_ctx?.setToken("");
|
||||
login_ctx?.setUsername("");
|
||||
return;
|
||||
}
|
||||
modal_ctx?.setLoginModalOpen(true);
|
||||
}}
|
||||
>
|
||||
{login_ctx?.token() != "" ? login_ctx?.username() : "Login"}
|
||||
</A>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// This is a modal
|
||||
export function Login() {
|
||||
const modal_ctx = useContext(ModalContext);
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
39
client-solid/src/Posts.tsx
Normal file
39
client-solid/src/Posts.tsx
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { createSignal } from "solid-js";
|
||||
import { getPosts } from "./api";
|
||||
import { Post } from "./api";
|
||||
|
||||
export function Posts() {
|
||||
const [posts, setPosts] = createSignal([] as Post[]);
|
||||
const [loading, setLoading] = createSignal(true);
|
||||
|
||||
getPosts().then((posts) => {
|
||||
setPosts(posts as any);
|
||||
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>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{posts().map((post) => {
|
||||
if (post.content == "") return; // Filtering out empty posts, remove this later
|
||||
return <PostSegment post={post}></PostSegment>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// This is the card container for a post
|
||||
export 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 break-words">{post.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
13
client-solid/src/Primary.tsx
Normal file
13
client-solid/src/Primary.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { Route, Routes } from "@solidjs/router";
|
||||
import { Posts } from "./Posts";
|
||||
import { NewPostInputArea } from "./Root";
|
||||
|
||||
// Primary is the section of the page that holds the main content
|
||||
export function Primary() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Posts />} />
|
||||
<Route path="/new" element={<NewPostInputArea />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
import { Accessor, createSignal, useContext } from "solid-js";
|
||||
import { createContext } from "solid-js";
|
||||
|
||||
import { Route, Routes, A } from "@solidjs/router";
|
||||
|
||||
import { createPost, getPosts } from "./api";
|
||||
import { Post, NewPost } from "./api";
|
||||
import { createPost } from "./api";
|
||||
import { NewPost } from "./api";
|
||||
import { Navbar } from "./Navbar";
|
||||
import { Primary } from "./Primary";
|
||||
import { Login } from "./Navbar";
|
||||
|
||||
// Representing the state of varoious modals.
|
||||
// So far we only have one modal, but we can add more later
|
||||
|
@ -55,45 +57,7 @@ function Root() {
|
|||
);
|
||||
}
|
||||
|
||||
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="#"
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
function Menu() {
|
||||
return (
|
||||
<ul class="menu menu-horizontal bg-base-200 rounded-box space-x-2 justify-end">
|
||||
<li>
|
||||
<A href="/" end>
|
||||
Home
|
||||
</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/new" end>
|
||||
New
|
||||
</A>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
function NewPostInputArea() {
|
||||
export function NewPostInputArea() {
|
||||
const [content, setContent] = createSignal("");
|
||||
const login_ctx = useContext(LoginContext);
|
||||
|
||||
|
@ -126,158 +90,4 @@ function NewPostInputArea() {
|
|||
);
|
||||
}
|
||||
|
||||
function Posts() {
|
||||
const [posts, setPosts] = createSignal([] as Post[]);
|
||||
const [loading, setLoading] = createSignal(true);
|
||||
|
||||
getPosts().then((posts) => {
|
||||
setPosts(posts as any);
|
||||
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>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{posts().map((post) => {
|
||||
if (post.content == "") return; // Filtering out empty posts, remove this later
|
||||
return <PostSegment post={post}></PostSegment>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 break-words">{post.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Primary() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Posts />} />
|
||||
<Route path="/new" element={<NewPostInputArea />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
||||
// This is a modal
|
||||
function Login() {
|
||||
const modal_ctx = useContext(ModalContext);
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
export default Root;
|
||||
|
|
Loading…
Reference in a new issue