Restructure
This commit is contained in:
parent
da48f005a3
commit
22a3ca1769
16 changed files with 44 additions and 34 deletions
75
client-solid/src/Components/Login.tsx
Normal file
75
client-solid/src/Components/Login.tsx
Normal file
|
@ -0,0 +1,75 @@
|
|||
import { JSXElement, Show, createSignal, useContext } from "solid-js";
|
||||
|
||||
import { LoginContext, ModalContext } from "../Context/GlobalState";
|
||||
import { AuthResponse, submitLogin } from "../Util/api";
|
||||
|
||||
export function LoginForm(): JSXElement {
|
||||
const modal_ctx = useContext(ModalContext)!;
|
||||
const login_ctx = useContext(LoginContext)!;
|
||||
const [username, setUsername] = createSignal<string>("");
|
||||
const [password, setPassword] = createSignal<string>("");
|
||||
const [waiting, setWaiting] = createSignal(false);
|
||||
const [error, setError] = createSignal(false);
|
||||
|
||||
async function loginFailed(): Promise<void> {
|
||||
setError(true);
|
||||
setWaiting(false);
|
||||
setTimeout(() => {
|
||||
setError(false);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
const clearFields = (): void => {
|
||||
setUsername("");
|
||||
setPassword("");
|
||||
};
|
||||
|
||||
const success = (response: AuthResponse): void => {
|
||||
setWaiting(false);
|
||||
setError(false);
|
||||
clearFields();
|
||||
login_ctx.logIn(response.username, response.token);
|
||||
modal_ctx.setOpen(false);
|
||||
};
|
||||
|
||||
async function loginPress(e: Event): Promise<void> {
|
||||
e.preventDefault();
|
||||
setWaiting(true);
|
||||
const data = await submitLogin(username(), password());
|
||||
if (data) success(data as AuthResponse);
|
||||
else loginFailed();
|
||||
}
|
||||
|
||||
return (
|
||||
<form class="form-control space-y-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={username()}
|
||||
class="input input-bordered"
|
||||
onChange={(e): void => {
|
||||
setUsername(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={password()}
|
||||
class="input input-bordered"
|
||||
onChange={(e): void => {
|
||||
setPassword(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<button
|
||||
classList={{ "btn btn-primary": !error(), "btn btn-error": error() }}
|
||||
onClick={loginPress}
|
||||
>
|
||||
<Show when={waiting()} fallback="Login">
|
||||
Logging in...
|
||||
</Show>
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
23
client-solid/src/Components/LoginButton.tsx
Normal file
23
client-solid/src/Components/LoginButton.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { JSXElement, Show, useContext } from "solid-js";
|
||||
|
||||
import { LoginContext, ModalContext } from "../Context/GlobalState";
|
||||
import { UserCircle } from "../Util/Icons";
|
||||
|
||||
export function LoginButton(): JSXElement {
|
||||
const modal_ctx = useContext(ModalContext)!;
|
||||
const login_ctx = useContext(LoginContext)!;
|
||||
|
||||
const clickHandler = (): void => {
|
||||
if (login_ctx.loggedIn()) login_ctx.logOut();
|
||||
else modal_ctx.setOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="btn btn-ghost text-sm capitalize" onClick={clickHandler}>
|
||||
<Show when={login_ctx.loggedIn()} fallback="Login">
|
||||
{login_ctx.username()}
|
||||
</Show>
|
||||
<UserCircle />
|
||||
</div>
|
||||
);
|
||||
}
|
36
client-solid/src/Components/Menu.tsx
Normal file
36
client-solid/src/Components/Menu.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { A } from "@solidjs/router";
|
||||
import { JSXElement, Show, useContext } from "solid-js";
|
||||
|
||||
import { LoginContext } from "../Context/GlobalState";
|
||||
import { Home, Plus } from "../Util/Icons";
|
||||
|
||||
// Represents a single list item in the menu bar
|
||||
export function MenuItem(props: {
|
||||
href: string;
|
||||
children: JSXElement;
|
||||
}): JSXElement {
|
||||
return (
|
||||
<li>
|
||||
<A class="justify-center" href={props.href} end>
|
||||
{props.children}
|
||||
</A>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
// Represents the menu bar at the top of the page
|
||||
export function Menu(): JSXElement {
|
||||
const login_ctx = useContext(LoginContext)!;
|
||||
return (
|
||||
<Show when={login_ctx.loggedIn()}>
|
||||
<ul class="menu space-y-2 rounded-box md:menu-horizontal md:space-x-2 md:space-y-0">
|
||||
<MenuItem href="/">
|
||||
<Home />
|
||||
</MenuItem>
|
||||
<MenuItem href="/new">
|
||||
<Plus />
|
||||
</MenuItem>
|
||||
</ul>
|
||||
</Show>
|
||||
);
|
||||
}
|
64
client-solid/src/Components/NewPost.tsx
Normal file
64
client-solid/src/Components/NewPost.tsx
Normal file
|
@ -0,0 +1,64 @@
|
|||
import { useNavigate } from "@solidjs/router";
|
||||
import { JSXElement, Show, createSignal, onMount, useContext } from "solid-js";
|
||||
|
||||
import { LoginContext } from "../Context/GlobalState";
|
||||
import { NewPost, createPost } from "../Util/api";
|
||||
|
||||
export function NewPostInputArea(): JSXElement {
|
||||
const [content, setContent] = createSignal("");
|
||||
const [waiting, setWaiting] = createSignal(false);
|
||||
|
||||
// We assumte this context is always available
|
||||
const login_ctx = useContext(LoginContext)!;
|
||||
|
||||
const nav = useNavigate();
|
||||
|
||||
const sendPost = (): void => {
|
||||
setWaiting(true);
|
||||
|
||||
const response = createPost({
|
||||
content: content(),
|
||||
token: login_ctx.token(),
|
||||
} as NewPost);
|
||||
|
||||
if (response) {
|
||||
response.then(() => {
|
||||
setWaiting(false);
|
||||
setContent("");
|
||||
nav("/");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Bail out if not logged in
|
||||
onMount(() => {
|
||||
if (!login_ctx.loggedIn()) nav("/");
|
||||
});
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={!waiting()}
|
||||
fallback={<span class="loading loading-spinner loading-lg self-center" />}
|
||||
>
|
||||
<div class="flex w-full flex-col space-y-2">
|
||||
<textarea
|
||||
class="textarea textarea-bordered h-32"
|
||||
placeholder="Speak your mind..."
|
||||
maxLength={500}
|
||||
onInput={(input): void => {
|
||||
setContent(input.target.value);
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
class={
|
||||
"btn btn-primary btn-sm self-end" +
|
||||
(content() == "" ? " btn-disabled" : "")
|
||||
}
|
||||
onClick={sendPost}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
);
|
||||
}
|
43
client-solid/src/Components/Posts.tsx
Normal file
43
client-solid/src/Components/Posts.tsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { useNavigate } from "@solidjs/router";
|
||||
import { For, JSXElement, Show, createSignal } from "solid-js";
|
||||
|
||||
import { Arrow, loadSpinner } from "../Util/Icons";
|
||||
import { Post, getPosts } from "../Util/api";
|
||||
|
||||
export function Posts(): JSXElement {
|
||||
const [posts, setPosts] = createSignal([] as Post[]);
|
||||
const [loading, setLoading] = createSignal(true);
|
||||
|
||||
getPosts().then((posts) => {
|
||||
setPosts(posts);
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
return (
|
||||
<Show when={!loading()} fallback={loadSpinner()}>
|
||||
<For each={posts()}>
|
||||
{(post): JSXElement => <PostSegment post={post} />}
|
||||
</For>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
|
||||
// This is the card container for a post
|
||||
export function PostSegment(props: { post: Post }): JSXElement {
|
||||
const nav = useNavigate();
|
||||
return (
|
||||
<div class="card compact w-full flex-grow border-b-2 border-b-base-300 bg-base-200 text-base-content transition-all hover:bg-base-300">
|
||||
<div class="card-body">
|
||||
<p class="break-words text-base-content">{props.post?.content}</p>
|
||||
<div class="card-actions justify-end">
|
||||
<button
|
||||
onClick={(): void => nav("/post/" + props.post?.id)}
|
||||
class="btn btn-xs"
|
||||
>
|
||||
<Arrow />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
87
client-solid/src/Components/Register.tsx
Normal file
87
client-solid/src/Components/Register.tsx
Normal file
|
@ -0,0 +1,87 @@
|
|||
import { JSXElement, Show, createSignal, useContext } from "solid-js";
|
||||
|
||||
import { LoginContext, ModalContext } from "../Context/GlobalState";
|
||||
import { AuthResponse, submitRegistration } from "../Util/api";
|
||||
|
||||
export function RegisterForm(): JSXElement {
|
||||
const modal_ctx = useContext(ModalContext)!;
|
||||
const login_ctx = useContext(LoginContext)!;
|
||||
const [username, setUsername] = createSignal<string>("");
|
||||
const [password, setPassword] = createSignal<string>("");
|
||||
const [captcha, setCaptcha] = createSignal<string>("");
|
||||
const [waiting, setWaiting] = createSignal(false);
|
||||
const [error, setError] = createSignal(false);
|
||||
|
||||
async function loginFailed(): Promise<void> {
|
||||
setError(true);
|
||||
setWaiting(false);
|
||||
setTimeout(() => {
|
||||
setError(false);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
const clearFields = (): void => {
|
||||
setUsername("");
|
||||
setPassword("");
|
||||
setCaptcha("");
|
||||
};
|
||||
|
||||
const success = (response: AuthResponse): void => {
|
||||
setWaiting(false);
|
||||
setError(false);
|
||||
clearFields();
|
||||
login_ctx.logIn(response.username, response.token);
|
||||
modal_ctx.setOpen(false);
|
||||
};
|
||||
|
||||
async function regPress(e: Event): Promise<void> {
|
||||
e.preventDefault();
|
||||
setWaiting(true);
|
||||
const data = await submitRegistration(username(), password(), captcha());
|
||||
if (data) success(data as AuthResponse);
|
||||
else loginFailed();
|
||||
}
|
||||
|
||||
return (
|
||||
<form class="form-control space-y-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={username()}
|
||||
class="input input-bordered"
|
||||
onChange={(e): void => {
|
||||
setUsername(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={password()}
|
||||
class="input input-bordered"
|
||||
onChange={(e): void => {
|
||||
setPassword(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Captcha"
|
||||
value={captcha()}
|
||||
class="input input-bordered"
|
||||
onChange={(e): void => {
|
||||
setCaptcha(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<button
|
||||
classList={{ "btn btn-primary": !error(), "btn btn-error": error() }}
|
||||
onClick={regPress}
|
||||
>
|
||||
<Show when={waiting()} fallback="Register">
|
||||
Registering...
|
||||
</Show>
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
19
client-solid/src/Components/SinglePost.tsx
Normal file
19
client-solid/src/Components/SinglePost.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { useParams } from "@solidjs/router";
|
||||
import { JSXElement, Show, Suspense, createResource } from "solid-js";
|
||||
|
||||
import { loadSpinner } from "../Util/Icons";
|
||||
import { getPost } from "../Util/api";
|
||||
import { PostSegment } from "./Posts";
|
||||
|
||||
export function SinglePost(): JSXElement {
|
||||
const params = useParams();
|
||||
const [post] = createResource(params.postid, getPost);
|
||||
|
||||
return (
|
||||
<Suspense fallback={loadSpinner()}>
|
||||
<Show when={post()}>
|
||||
<PostSegment post={post()!} />
|
||||
</Show>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue