Better login modal, post layout fixing
This commit is contained in:
parent
e6bf6bc8a6
commit
8d23f17f76
9 changed files with 269 additions and 101 deletions
17
client-solid/src/Icons.tsx
Normal file
17
client-solid/src/Icons.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
export function Arrow() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="w-6 h-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M17.25 8.25L21 12m0 0l-3.75 3.75M21 12H3" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
67
client-solid/src/LoginModal.tsx
Normal file
67
client-solid/src/LoginModal.tsx
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import { onCleanup, useContext } from "solid-js";
|
||||||
|
import { ModalContext } from "./Root";
|
||||||
|
import { LoginForm } from "./RegLogin/Login";
|
||||||
|
import { RegisterForm } from "./RegLogin/Register";
|
||||||
|
|
||||||
|
export function LoginModal() {
|
||||||
|
const modal_ctx = useContext(ModalContext);
|
||||||
|
|
||||||
|
if (!modal_ctx) {
|
||||||
|
// Handle the case where ModalContext is not available
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
modal_ctx.setLoginModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Close the modal when the component is unmounted
|
||||||
|
onCleanup(() => {
|
||||||
|
modal_ctx.setLoginModalOpen(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{modal_ctx.loginModalOpen() && (
|
||||||
|
<dialog class="modal modal-open">
|
||||||
|
<div class="modal-box">
|
||||||
|
<form method="dialog">
|
||||||
|
<button
|
||||||
|
onclick={closeModal}
|
||||||
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<h3 class="font-bold text-lg mb-2">Welcome!</h3>
|
||||||
|
<div class="tabs tabs-lifted">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="login_tabs"
|
||||||
|
class="tab"
|
||||||
|
aria-label="Login"
|
||||||
|
checked
|
||||||
|
/>
|
||||||
|
<div class="tab-content bg-base-100 border-base-300 rounded-box p-10">
|
||||||
|
<LoginForm />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="login_tabs"
|
||||||
|
class="tab"
|
||||||
|
aria-label="Register"
|
||||||
|
/>
|
||||||
|
<div class="tab-content bg-base-100 border-base-300 rounded-box p-10">
|
||||||
|
<RegisterForm />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button onclick={closeModal}>close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -2,26 +2,23 @@ import { useContext } from "solid-js";
|
||||||
import { A } from "@solidjs/router";
|
import { A } from "@solidjs/router";
|
||||||
import { LoginContext } from "./Root";
|
import { LoginContext } from "./Root";
|
||||||
import { ModalContext } from "./Root";
|
import { ModalContext } from "./Root";
|
||||||
import { LoginForm } from "./Login";
|
|
||||||
|
function MenuItem(props: { href: string; children: any }) {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
<A class="justify-center" href={props.href} end>
|
||||||
|
{props.children}
|
||||||
|
</A>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function Menu() {
|
function Menu() {
|
||||||
let login_ctx = useContext(LoginContext);
|
let login_ctx = useContext(LoginContext);
|
||||||
return (
|
return (
|
||||||
<ul class="menu menu-horizontal bg-base-100 rounded-box space-x-2">
|
<ul class="menu md:menu-horizontal rounded-box">
|
||||||
<li>
|
<MenuItem href="/">Home</MenuItem>
|
||||||
<A href="/" end>
|
{login_ctx?.token() != "" && <MenuItem href="/new">New Post</MenuItem>}
|
||||||
Home
|
|
||||||
</A>
|
|
||||||
</li>
|
|
||||||
{login_ctx?.token() != "" ? (
|
|
||||||
<li>
|
|
||||||
<A href="/new" end>
|
|
||||||
New
|
|
||||||
</A>
|
|
||||||
</li>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -30,61 +27,42 @@ export function Navbar() {
|
||||||
let modal_ctx = useContext(ModalContext);
|
let modal_ctx = useContext(ModalContext);
|
||||||
let login_ctx = useContext(LoginContext);
|
let login_ctx = useContext(LoginContext);
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
localStorage.setItem("token", "");
|
||||||
|
localStorage.setItem("username", "");
|
||||||
|
login_ctx?.setToken("");
|
||||||
|
login_ctx?.setUsername("");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Capitalize the first letter of the username
|
||||||
|
const username =
|
||||||
|
login_ctx && login_ctx.username()
|
||||||
|
? login_ctx.username().charAt(0).toUpperCase() +
|
||||||
|
login_ctx.username().slice(1)
|
||||||
|
: "Guest";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="navbar text-neutral-content max-w-3xl max-w">
|
<div class="navbar text-neutral-content max-w-3xl max-w rounded-box my-4">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<A href={"/"} class="btn btn-ghost normal-case text-xl">
|
<A href={"/"} class="btn btn-ghost normal-case text-xl">
|
||||||
FrostByte
|
FrostByte
|
||||||
</A>
|
</A>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden md:flex">
|
<div class="flex">
|
||||||
<Menu />
|
<Menu />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 justify-end hidden md:flex">
|
<div class="flex-1 justify-end flex">
|
||||||
<A
|
<A
|
||||||
href="#"
|
href="#"
|
||||||
class="btn btn-ghost normal-case text-sm"
|
class="btn btn-ghost normal-case text-sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (login_ctx?.token() != "") {
|
if (login_ctx?.token() != "") logout();
|
||||||
localStorage.setItem("token", "");
|
else modal_ctx?.setLoginModalOpen(true);
|
||||||
localStorage.setItem("username", "");
|
|
||||||
login_ctx?.setToken("");
|
|
||||||
login_ctx?.setUsername("");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
modal_ctx?.setLoginModalOpen(true);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{login_ctx?.token() != "" ? login_ctx?.username() : "Login"}
|
{login_ctx?.token() != "" ? username : "Login"}
|
||||||
</A>
|
</A>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { createSignal } from "solid-js";
|
||||||
import { getPosts } from "./api";
|
import { getPosts } from "./api";
|
||||||
import { Post } from "./api";
|
import { Post } from "./api";
|
||||||
import { useNavigate } from "@solidjs/router";
|
import { useNavigate } from "@solidjs/router";
|
||||||
|
import { Arrow } from "./Icons";
|
||||||
|
|
||||||
export function Posts() {
|
export function Posts() {
|
||||||
const [posts, setPosts] = createSignal([] as Post[]);
|
const [posts, setPosts] = createSignal([] as Post[]);
|
||||||
|
@ -13,17 +14,15 @@ export function Posts() {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col space-y-2 w-full md:w-96">
|
<>
|
||||||
{loading() ? (
|
{loading() && (
|
||||||
<span class="loading loading-spinner loading-lg self-center"></span>
|
<span class="loading loading-spinner loading-lg self-center"></span>
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
)}
|
||||||
{posts().map((post) => {
|
{posts().map((post) => {
|
||||||
if (post.content == "") return; // Filtering out empty posts, remove this later
|
if (post.content == "") return; // Filtering out empty posts, remove this later
|
||||||
return <PostSegment post={post}></PostSegment>;
|
return <PostSegment post={post}></PostSegment>;
|
||||||
})}
|
})}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,13 +30,17 @@ export function Posts() {
|
||||||
export function PostSegment({ post }: { post: Post }) {
|
export function PostSegment({ post }: { post: Post }) {
|
||||||
const nav = useNavigate();
|
const nav = useNavigate();
|
||||||
return (
|
return (
|
||||||
<div
|
<div class="card border-b-2 flex-grow border-b-base-300 bg-base-200 hover:bg-base-300 compact text-base-content w-full">
|
||||||
onClick={() => nav("/post/" + post?.id)}
|
|
||||||
class="card bg-base-200 shadow-lg compact text-base-content w-full"
|
|
||||||
>
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="text-base-content break-words">{post?.content}</p>
|
<p class="text-base-content break-words">{post?.content}</p>
|
||||||
|
<div class="card-actions justify-end">
|
||||||
|
<button onClick={() => nav("/post/" + post?.id)} class="btn btn-xs">
|
||||||
|
<Arrow />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { createSignal, useContext } from "solid-js";
|
import { createSignal, useContext } from "solid-js";
|
||||||
import { LoginContext, ModalContext } from "./Root";
|
import { LoginContext, ModalContext } from "../Root";
|
||||||
|
|
||||||
export function LoginForm() {
|
export function LoginForm() {
|
||||||
const modal_ctx = useContext(ModalContext);
|
const modal_ctx = useContext(ModalContext);
|
||||||
|
@ -17,49 +17,49 @@ export function LoginForm() {
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loginPress(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
setWaiting(true);
|
||||||
|
submitLogin(username(), password()).then((token) => {
|
||||||
|
if (token != "") {
|
||||||
|
setWaiting(false);
|
||||||
|
setError(false);
|
||||||
|
login_ctx?.setUsername(username());
|
||||||
|
login_ctx?.setToken(token);
|
||||||
|
modal_ctx?.setLoginModalOpen(false);
|
||||||
|
setUsername("");
|
||||||
|
setPassword("");
|
||||||
|
} else {
|
||||||
|
loginFailed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form class="form-control">
|
<form class="form-control space-y-2">
|
||||||
<label class="label">
|
|
||||||
<span class="label-text">Username</span>
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="username"
|
placeholder="Username"
|
||||||
value={username()}
|
value={username()}
|
||||||
class="input input-bordered"
|
class="input input-bordered"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setUsername(e.target.value);
|
setUsername(e.target.value);
|
||||||
}} />
|
}}
|
||||||
<label class="label">
|
/>
|
||||||
<span class="label-text">Password</span>
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="password"
|
placeholder="Password"
|
||||||
value={password()}
|
value={password()}
|
||||||
class="input input-bordered"
|
class="input input-bordered"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setPassword(e.target.value);
|
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class={"btn btn-primary" + (error() ? " btn-error" : "")}
|
||||||
|
onClick={ loginPress }
|
||||||
>
|
>
|
||||||
{waiting() ? "Logging in..." : "Login"}
|
{waiting() ? "Logging in..." : "Login"}
|
||||||
</button>
|
</button>
|
||||||
|
@ -73,6 +73,7 @@ export async function submitLogin(
|
||||||
username: string,
|
username: string,
|
||||||
password: string
|
password: string
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
if(username == "" || password == "") return "";
|
||||||
const response = await fetch("/api/login", {
|
const response = await fetch("/api/login", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
@ -89,4 +90,3 @@ export async function submitLogin(
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
103
client-solid/src/RegLogin/Register.tsx
Normal file
103
client-solid/src/RegLogin/Register.tsx
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import { createSignal, useContext } from "solid-js";
|
||||||
|
import { LoginContext, ModalContext } from "../Root";
|
||||||
|
|
||||||
|
export function RegisterForm() {
|
||||||
|
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() {
|
||||||
|
setError(true);
|
||||||
|
setWaiting(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
setError(false);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function regPress(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
setWaiting(true);
|
||||||
|
submitRegistration(username(), password(), captcha()).then((token) => {
|
||||||
|
if (token != "") {
|
||||||
|
setWaiting(false);
|
||||||
|
setError(false);
|
||||||
|
login_ctx?.setUsername(username());
|
||||||
|
login_ctx?.setToken(token);
|
||||||
|
modal_ctx?.setLoginModalOpen(false);
|
||||||
|
setUsername("");
|
||||||
|
setPassword("");
|
||||||
|
} else {
|
||||||
|
loginFailed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form class="form-control space-y-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Username"
|
||||||
|
value={username()}
|
||||||
|
class="input input-bordered"
|
||||||
|
onChange={(e) => {
|
||||||
|
setUsername(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
placeholder="Password"
|
||||||
|
value={password()}
|
||||||
|
class="input input-bordered"
|
||||||
|
onChange={(e) => {
|
||||||
|
setPassword(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Captcha"
|
||||||
|
value={password()}
|
||||||
|
class="input input-bordered"
|
||||||
|
onChange={(e) => {
|
||||||
|
setCaptcha(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class={"btn btn-primary" + (error() ? " btn-error" : "")}
|
||||||
|
onClick={regPress}
|
||||||
|
>
|
||||||
|
{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 submitRegistration(
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
captcha: string
|
||||||
|
): Promise<string> {
|
||||||
|
const response = await fetch("/api/register", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ username, password, captcha }),
|
||||||
|
});
|
||||||
|
|
||||||
|
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 "";
|
||||||
|
}
|
|
@ -5,7 +5,8 @@ import { createPost } from "./api";
|
||||||
import { NewPost } from "./api";
|
import { NewPost } from "./api";
|
||||||
import { Navbar } from "./Navbar";
|
import { Navbar } from "./Navbar";
|
||||||
import { Primary } from "./Primary";
|
import { Primary } from "./Primary";
|
||||||
import { Login } from "./Navbar";
|
// import { Login } from "./Navbar";
|
||||||
|
import { LoginModal } from "./LoginModal";
|
||||||
import { useNavigate } from "@solidjs/router";
|
import { useNavigate } from "@solidjs/router";
|
||||||
|
|
||||||
// Representing the state of varoious modals.
|
// Representing the state of varoious modals.
|
||||||
|
@ -44,9 +45,9 @@ function Root() {
|
||||||
<LoginContext.Provider
|
<LoginContext.Provider
|
||||||
value={{ token, setToken, username, setUsername }}
|
value={{ token, setToken, username, setUsername }}
|
||||||
>
|
>
|
||||||
<div class="flex flex-col items-center my-2">
|
<div class="flex flex-col items-center">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<Login />
|
<LoginModal />
|
||||||
<div class="flex flex-col items-center md:w-96 space-y-2">
|
<div class="flex flex-col items-center md:w-96 space-y-2">
|
||||||
<Primary />
|
<Primary />
|
||||||
</div>
|
</div>
|
||||||
|
@ -66,10 +67,12 @@ export function NewPostInputArea() {
|
||||||
|
|
||||||
const sendPost = () => {
|
const sendPost = () => {
|
||||||
setWaiting(true);
|
setWaiting(true);
|
||||||
|
|
||||||
const response = createPost({
|
const response = createPost({
|
||||||
content: content(),
|
content: content(),
|
||||||
token: login_ctx?.token(),
|
token: login_ctx?.token(),
|
||||||
} as NewPost);
|
} as NewPost);
|
||||||
|
|
||||||
if (response) {
|
if (response) {
|
||||||
response.then(() => {
|
response.then(() => {
|
||||||
setWaiting(false);
|
setWaiting(false);
|
||||||
|
|
|
@ -9,11 +9,9 @@ export function SinglePost() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<div>Some loading message</div>}>
|
<Suspense fallback={<div>Some loading message</div>}>
|
||||||
<div>
|
|
||||||
<Show when={post()}>
|
<Show when={post()}>
|
||||||
<PostSegment post={post()!}></PostSegment>
|
<PostSegment post={post()!}></PostSegment>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ export async function getPost(id: string): Promise<Post> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createPost(post: NewPost): Promise<void> {
|
export async function createPost(post: NewPost): Promise<void> {
|
||||||
// await fetch(`${API_URL}`, {
|
|
||||||
await fetch("/api/posts", {
|
await fetch("/api/posts", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
Loading…
Reference in a new issue