Working login modal pointed to the correct api endpoint
This commit is contained in:
		
							parent
							
								
									508cf528af
								
							
						
					
					
						commit
						c2103071bc
					
				
					 3 changed files with 174 additions and 27 deletions
				
			
		|  | @ -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> | ||||
|  |  | |||
|  | @ -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"> | ||||
|         <div class="flex flex-col items-center my-2"> | ||||
|           <Navbar /> | ||||
|           <div class="flex flex-col items-center md:w-96 space-y-2"> | ||||
|             <Primary /> | ||||
|       <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> | ||||
|         </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" /> | ||||
|     </div> | ||||
|     <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> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Imbus
						Imbus