Added support for CORS and integrated login handler
This commit is contained in:
parent
27db90cd58
commit
fdcd08dc50
4 changed files with 125 additions and 43 deletions
50
src/App.tsx
50
src/App.tsx
|
@ -1,8 +1,8 @@
|
|||
import React, { createContext, useContext, useState } from "react";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import Chat from "./Chat/Chat";
|
||||
import "./App.css";
|
||||
import { LangType, Message } from "./Chat/messageTypes";
|
||||
import { MessageContainer } from "./Chat/MessageContainer";
|
||||
import { MessageDisplay } from "./Chat/MessageDisplay";
|
||||
import strings from "./Intl/strings.json";
|
||||
import { LangContext, LoginContext, LoginType } from "./context";
|
||||
import { contentTypes, domain, endpoints, port } from "./consts";
|
||||
|
@ -11,19 +11,31 @@ import { Login } from "./Login/Login";
|
|||
const Wrapper = (): React.ReactElement => {
|
||||
const [lang, setLang] = useState<LangType>("en_US");
|
||||
const [login, setLogin] = useState<LoginType | undefined>(undefined);
|
||||
useEffect(() => {
|
||||
document.title = login
|
||||
? `IRC logged in as ${login.username}`
|
||||
: "IRC Chat";
|
||||
}, [login]);
|
||||
return (
|
||||
<LangContext.Provider value={lang}>
|
||||
<h1>{strings[lang].homepage.title}</h1>
|
||||
<p>{strings[lang].homepage.description}</p>
|
||||
<LoginContext.Provider value={login}>
|
||||
{/* callbacks for altering the Lang/Login contexts */}
|
||||
<Login
|
||||
setLogin={(value) => {
|
||||
setLogin(value);
|
||||
}}
|
||||
></Login>
|
||||
<App
|
||||
changeLang={(value: string) => {
|
||||
setLang(value as LangType);
|
||||
}}
|
||||
/>
|
||||
{login ? (
|
||||
<App
|
||||
changeLang={(value: string) => {
|
||||
setLang(value as LangType);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</LoginContext.Provider>
|
||||
</LangContext.Provider>
|
||||
);
|
||||
|
@ -33,6 +45,7 @@ const setNameOnServer = async (name: string) => {
|
|||
`http://${domain}:${port}${endpoints.user}`,
|
||||
{
|
||||
method: "POST",
|
||||
mode: "cors",
|
||||
headers: contentTypes.json,
|
||||
body: JSON.stringify({
|
||||
userName: name,
|
||||
|
@ -77,23 +90,20 @@ const App = ({
|
|||
}
|
||||
});
|
||||
};
|
||||
if (!username) {
|
||||
var newName = prompt(home.userNamePrompt) as string;
|
||||
while (!validateName(newName)) {
|
||||
console.log(newName);
|
||||
// if (!username) {
|
||||
// var newName = prompt(home.userNamePrompt) as string;
|
||||
// while (!validateName(newName)) {
|
||||
// console.log(newName);
|
||||
|
||||
prompt("Username invalid! Please enter again.") as string;
|
||||
}
|
||||
setUsername(newName);
|
||||
}
|
||||
// prompt("Username invalid! Please enter again.") as string;
|
||||
// }
|
||||
// setUsername(newName);
|
||||
// }
|
||||
if (!login) {
|
||||
return <></>;
|
||||
} else
|
||||
return (
|
||||
<div className="App">
|
||||
<h1>{home.title}</h1>
|
||||
<pre>{home.description}</pre>
|
||||
<h3>Your name is: {username}</h3>
|
||||
<button
|
||||
onClick={(ev) => {
|
||||
const selection = prompt(home.newLangPrompt);
|
||||
|
@ -130,9 +140,9 @@ const App = ({
|
|||
Change Username
|
||||
</button>
|
||||
{messages.map((message) => {
|
||||
return <MessageContainer {...message} />;
|
||||
return <MessageDisplay {...message} />;
|
||||
})}
|
||||
{<Chat user={username as string} />}
|
||||
{<Chat user={login.username as string} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@ import React, {
|
|||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { MessageContainer } from "./MessageContainer";
|
||||
import { MessageDisplay } from "./MessageDisplay";
|
||||
import { Client } from "@stomp/stompjs";
|
||||
import { Message, MessageType } from "./messageTypes";
|
||||
import "./Chat.css";
|
||||
|
@ -29,7 +29,7 @@ const Chat = ({ user }: { user: string }): React.ReactElement => {
|
|||
console.log(messageBody);
|
||||
setMessages((message) => {
|
||||
return message.concat([
|
||||
<MessageContainer
|
||||
<MessageDisplay
|
||||
key={`${messageBody.type}@${messageBody.timeMillis}`}
|
||||
{...messageBody}
|
||||
/>,
|
||||
|
@ -99,8 +99,23 @@ const Chat = ({ user }: { user: string }): React.ReactElement => {
|
|||
sendData();
|
||||
}
|
||||
});
|
||||
useEffect(() => {
|
||||
try {
|
||||
const elem = document.querySelector(".chat-inner-wrapper");
|
||||
if (elem) {
|
||||
elem.scrollTop = elem.scrollHeight;
|
||||
} else {
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("error encountered");
|
||||
}
|
||||
return () => {};
|
||||
}, [messages]);
|
||||
return (
|
||||
<div className="chat">
|
||||
<fieldset className="chat">
|
||||
<legend>
|
||||
Logged in as <b>{user}</b>
|
||||
</legend>
|
||||
<div className="chat-inner-wrapper">{messages}</div>
|
||||
<span className="entry-box">
|
||||
<input id="data-entry"></input>
|
||||
|
@ -108,7 +123,7 @@ const Chat = ({ user }: { user: string }): React.ReactElement => {
|
|||
{chatPage.sendButtonPrompt}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
);
|
||||
};
|
||||
export default Chat;
|
||||
|
|
|
@ -2,7 +2,8 @@ import React, { useContext } from "react";
|
|||
import { Message, MessageType } from "./messageTypes";
|
||||
import { LangContext } from "../context";
|
||||
import strings from "../Intl/strings.json";
|
||||
export const MessageContainer = ({
|
||||
import "./MessageDisplay.css";
|
||||
export const MessageDisplay = ({
|
||||
type,
|
||||
fromUserId,
|
||||
toUserId,
|
||||
|
@ -21,19 +22,23 @@ export const MessageContainer = ({
|
|||
* This caused the return statement to fail, and the message fails to render, despite it being correctly committed to the db.
|
||||
* Funny clown moment 🤡
|
||||
*/
|
||||
|
||||
const timeString = `${
|
||||
dateTime.getHours() > 12
|
||||
? dateTime.getHours() - 12
|
||||
: dateTime.getHours()
|
||||
}:${dateTime.getMinutes()} ${dateTime.getHours() > 12 ? "PM" : "AM"}`;
|
||||
switch (type) {
|
||||
case MessageType.HELLO as MessageType:
|
||||
return (
|
||||
<p>
|
||||
[{dateTime.toLocaleString()}]{" "}
|
||||
<p className="msg">
|
||||
[{timeString}]{" "}
|
||||
{msgPage.joinMessage.replace("$userName", fromUserId)}
|
||||
</p>
|
||||
);
|
||||
case MessageType.MESSAGE as MessageType:
|
||||
return (
|
||||
<p>
|
||||
[{dateTime.toLocaleString()}]{" "}
|
||||
<p className="msg">
|
||||
[{timeString}]{" "}
|
||||
{msgPage.serverMessage
|
||||
.replace("$userName", fromUserId)
|
||||
.replace("$content", content)}
|
||||
|
@ -46,9 +51,9 @@ export const MessageContainer = ({
|
|||
default:
|
||||
console.error("Illegal MessageType reported!");
|
||||
return (
|
||||
<p>
|
||||
[{dateTime.toLocaleString()}] **THIS MESSAGE CANNOT BE
|
||||
CORRECTLY SHOWN BECAUSE THE CLIENT ENCOUNTERED AN ERROR**
|
||||
<p className="msg-err">
|
||||
[{timeString}] **THIS MESSAGE CANNOT BE CORRECTLY SHOWN
|
||||
BECAUSE THE CLIENT ENCOUNTERED AN ERROR**
|
||||
</p>
|
||||
);
|
||||
}
|
|
@ -2,6 +2,7 @@ import { useState } from "react";
|
|||
import { contentTypes, domain, endpoints, port } from "../consts";
|
||||
import { LoginType } from "../context";
|
||||
import { User } from "../Chat/userTypes";
|
||||
import "./Login.css";
|
||||
const encrypt = (rawPasswordString: string) => {
|
||||
// TODO Encryption method stub
|
||||
return rawPasswordString;
|
||||
|
@ -9,9 +10,10 @@ const encrypt = (rawPasswordString: string) => {
|
|||
export const Login = ({
|
||||
setLogin,
|
||||
}: {
|
||||
setLogin: (newLogin: LoginType) => void;
|
||||
setLogin: (newLogin: LoginType | undefined) => void;
|
||||
}): React.ReactElement => {
|
||||
const [valid, setValid] = useState(true);
|
||||
const [valid, setValid] = useState<boolean | undefined>(true);
|
||||
const [validText, setValidText] = useState<string | undefined>();
|
||||
const registrationHandler = () => {
|
||||
const uname = (document.getElementById("username") as HTMLInputElement)
|
||||
.value;
|
||||
|
@ -20,6 +22,7 @@ export const Login = ({
|
|||
);
|
||||
fetch(`http://${domain}:${port}${endpoints.user}`, {
|
||||
method: "POST",
|
||||
mode: "cors",
|
||||
headers: contentTypes.json,
|
||||
body: JSON.stringify({
|
||||
userName: uname,
|
||||
|
@ -28,8 +31,12 @@ export const Login = ({
|
|||
}),
|
||||
}).then((response) => {
|
||||
if (response.status === 400) {
|
||||
// 400 Bad request
|
||||
console.log("Username is taken or invalid!");
|
||||
} else {
|
||||
setValid(false);
|
||||
setValidText("Username is taken or invalid!");
|
||||
} else if (response.status === 200) {
|
||||
// 200 OK
|
||||
const futureDate = new Date();
|
||||
futureDate.setHours(futureDate.getHours() + 2);
|
||||
setLogin({
|
||||
|
@ -37,6 +44,7 @@ export const Login = ({
|
|||
lastSeen: Date.now(),
|
||||
validUntil: futureDate.getUTCMilliseconds(),
|
||||
});
|
||||
document.title = `IRC User ${uname}`;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -48,16 +56,28 @@ export const Login = ({
|
|||
(document.getElementById("passwd") as HTMLInputElement).value
|
||||
);
|
||||
// async invocation of Fetch API
|
||||
fetch(`http://${domain}:${port}${endpoints.user}?user=${uname}`, {
|
||||
fetch(`http://${domain}:${port}${endpoints.user}?name=${uname}`, {
|
||||
method: "GET",
|
||||
mode: "cors",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
if (res.status === 404) {
|
||||
console.log("404 not found encountered");
|
||||
throw new Error("Username does not exist");
|
||||
} else if (res.status === 200) {
|
||||
console.log("200 OK");
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then((userObject) => {
|
||||
if (!userObject) {
|
||||
return;
|
||||
}
|
||||
const user = userObject as User;
|
||||
const validLogin = passwd === user.passwordHash;
|
||||
if (!validLogin) {
|
||||
// login invalid
|
||||
setValid(false); // triggers page re-render -- should refresh the page
|
||||
throw new Error("Password incorrect!");
|
||||
} else {
|
||||
// login valid
|
||||
const validUntilDate: Date = new Date();
|
||||
|
@ -67,22 +87,54 @@ export const Login = ({
|
|||
lastSeen: user.lastSeen,
|
||||
validUntil: validUntilDate.getUTCMilliseconds(),
|
||||
});
|
||||
document.title = `IRC User ${uname}`;
|
||||
}
|
||||
})
|
||||
.catch((reason: Error) => {
|
||||
setValid(false);
|
||||
setValidText(reason.message);
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<div className="login">
|
||||
<fieldset>
|
||||
<legend>Login window</legend>
|
||||
<p className="uname-error-text">
|
||||
{valid ? "Error in your username or password" : ""}
|
||||
{valid && valid !== undefined ? "" : validText}
|
||||
</p>
|
||||
<label htmlFor="username">Username: </label>
|
||||
<br />
|
||||
<input id="username" type="text"></input>
|
||||
<br />
|
||||
<label htmlFor="passwd">Password: </label>
|
||||
<br />
|
||||
<input id="passwd" type="password"></input>
|
||||
<button type="submit">Login</button>
|
||||
<button type="submit">Register</button>
|
||||
<br />
|
||||
<button
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
loginHandler();
|
||||
}}
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
registrationHandler();
|
||||
}}
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
setLogin(undefined);
|
||||
setValid(false);
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue