fix: Login modal rendered twice

Also added ability to login by username or by role
This commit is contained in:
Davide Passoni 2025-03-20 10:49:07 +01:00
parent 0ef5de51c4
commit 791b1fc4ab
3 changed files with 195 additions and 90 deletions

View File

@ -1,10 +1,16 @@
import React, { useEffect, useState } from "react";
import { ModalEvent } from "../../../events";
import { FaXmark } from "react-icons/fa6";
import { getApp, OlympusApp } from "../../../olympusapp";
import { getApp } from "../../../olympusapp";
import { OlympusState } from "../../../constants/constants";
export function Modal(props: { open: boolean; children?: JSX.Element | JSX.Element[]; className?: string; size?: "sm" | "md" | "lg" | "full" }) {
export function Modal(props: {
open: boolean;
children?: JSX.Element | JSX.Element[];
className?: string;
size?: "sm" | "md" | "lg" | "full";
disableClose?: boolean;
}) {
const [splash, setSplash] = useState(Math.ceil(Math.random() * 7));
useEffect(() => {
@ -15,21 +21,47 @@ export function Modal(props: { open: boolean; children?: JSX.Element | JSX.Eleme
<>
{props.open && (
<>
<div className={`fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95`}></div>
<div
className={`fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95`}
></div>
<div
className={`
fixed left-[50%] top-[50%] z-40 inline-flex translate-x-[-50%] translate-y-[-50%]
overflow-y-auto scroll-smooth
bg-olympus-800
max-md:rounded-none
max-md:border-none rounded-xl border-[1px] border-solid border-gray-700 drop-shadow-md
${props.size === "lg" ? "h-[600px] w-[1100px] max-md:h-full max-md:w-full" : ""}
${props.size === "md" ? "h-[600px] w-[950px] max-md:h-full max-md:w-full" : ""}
${props.size === "sm" ? "h-[500px] w-[800px] max-md:h-full max-md:w-full" : ""}
fixed left-[50%] top-[50%] z-40 inline-flex translate-x-[-50%]
translate-y-[-50%] overflow-y-auto scroll-smooth rounded-xl
border-[1px] border-solid border-gray-700 bg-olympus-800
drop-shadow-md
max-md:rounded-none max-md:border-none
${
props.size === "lg"
? `
h-[600px] w-[1100px]
max-md:h-full max-md:w-full
`
: ""
}
${
props.size === "md"
? `
h-[600px] w-[950px]
max-md:h-full max-md:w-full
`
: ""
}
${
props.size === "sm"
? `
h-[500px] w-[800px]
max-md:h-full max-md:w-full
`
: ""
}
${props.size === "full" ? "h-full w-full" : ""}
`}
>
<img src={`images/splash/${splash}.jpg`} className={`contents-center w-full object-cover opacity-[4%]`}></img>
<img
src={`images/splash/${splash}.jpg`}
className={`contents-center w-full object-cover opacity-[4%]`}
></img>
<div className="fixed left-0 top-0 h-full w-full">
<div
className={`
@ -50,17 +82,19 @@ export function Modal(props: { open: boolean; children?: JSX.Element | JSX.Eleme
`}
>
{props.children}
<div
className={`
absolute right-5 top-5 cursor-pointer text-xl text-white
`}
>
<FaXmark
onClick={() => {
getApp().setState(OlympusState.IDLE);
}}
/>{" "}
</div>
{!props.disableClose && (
<div
className={`
absolute right-5 top-5 cursor-pointer text-xl text-white
`}
>
<FaXmark
onClick={() => {
getApp().setState(OlympusState.IDLE);
}}
/>{" "}
</div>
)}
</div>
</div>
</div>

View File

@ -3,12 +3,12 @@ import { Modal } from "./components/modal";
import { Card } from "./components/card";
import { ErrorCallout } from "../components/olcallout";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowRight, faCheckCircle, faExternalLink } from "@fortawesome/free-solid-svg-icons";
import { faArrowLeft, faArrowRight, faCheckCircle, faExternalLink } from "@fortawesome/free-solid-svg-icons";
import { getApp, VERSION } from "../../olympusapp";
import { sha256 } from "js-sha256";
import { LoginSubState, NO_SUBSTATE, OlympusState } from "../../constants/constants";
import { OlDropdown, OlDropdownItem } from "../components/oldropdown";
import { AppStateChangedEvent, EnabledCommandModesChangedEvent, MissionDataChangedEvent, WrongCredentialsEvent } from "../../events";
import { AppStateChangedEvent, EnabledCommandModesChangedEvent, WrongCredentialsEvent } from "../../events";
export function LoginModal(props: { open: boolean }) {
const [subState, setSubState] = useState(NO_SUBSTATE);
@ -18,6 +18,7 @@ export function LoginModal(props: { open: boolean }) {
const [loginError, setLoginError] = useState(false);
const [commandModes, setCommandModes] = useState(null as null | string[]);
const [activeCommandMode, setActiveCommandMode] = useState(null as null | string);
const [loginByRole, setLoginByRole] = useState(true);
useEffect(() => {
AppStateChangedEvent.on((state, subState) => {
@ -29,6 +30,11 @@ export function LoginModal(props: { open: boolean }) {
});
}, []);
const updateUsername = useCallback(() => {
loginByRole ? setUsername("Game master") : setUsername("");
}, [loginByRole]);
useEffect(updateUsername, [loginByRole]);
const usernameCallback = useCallback(() => getApp()?.getServerManager().setUsername(username), [username]);
useEffect(usernameCallback, [username]);
@ -80,7 +86,7 @@ export function LoginModal(props: { open: boolean }) {
useEffect(subStateCallback, [subState]);
return (
<Modal open={props.open}>
<Modal open={props.open} size="md" disableClose={true}>
<div
className={`
flex w-full flex-row gap-6
@ -95,25 +101,6 @@ export function LoginModal(props: { open: boolean }) {
>
{!checkingPassword ? (
<>
<div className="flex flex-col items-start">
<div
className={`
pt-1 text-xs text-gray-800
dark:text-gray-400
`}
>
Connect to
</div>
<div
className={`
flex items-center justify-center gap-2 text-gray-800 text-md
font-bold
dark:text-gray-200
`}
>
{window.location.toString()}
</div>
</div>
<div
className={`
flex w-[100%] flex-row content-center items-center gap-2
@ -147,33 +134,96 @@ export function LoginModal(props: { open: boolean }) {
<>
{subState === LoginSubState.CREDENTIALS && (
<>
<div className={`flex flex-col items-start gap-2`}>
<label
<div
className={`
peer box-content flex h-10 w-80 cursor-pointer
justify-between rounded-lg border-4 border-gray-600
border-transparent bg-olympus-600
hover:bg-olympus-400
peer-focus:outline-none peer-focus:ring-2
peer-focus:ring-blue-800
`}
onClick={() => setLoginByRole(!loginByRole)}
>
<div
data-login-by-role={loginByRole}
className={`
text-gray-800 text-md
dark:text-white
relative
before:absolute before:h-10 before:w-40
before:rounded-md before:bg-blue-500
before:transition-transform before:content-['']
before:data-[login-by-role='false']:translate-x-40
`}
></div>
<div
className={`
z-40 my-auto w-[50%] text-center
${loginByRole ? "text-white" : `
text-gray-400 transition-colors
`}
`}
>
Username
</label>
<input
type="text"
autoComplete="username"
onChange={(ev) => setUsername(ev.currentTarget.value)}
Login by role
</div>
<div
className={`
block w-full max-w-80 rounded-lg border
border-gray-300 bg-gray-50 p-2.5 text-sm
text-gray-900
dark:border-gray-600 dark:bg-gray-700
dark:text-white dark:placeholder-gray-400
dark:focus:border-blue-500 dark:focus:ring-blue-500
focus:border-blue-500 focus:ring-blue-500
z-40 my-auto w-[50%] px-2 text-center
${!loginByRole ? "text-white" : `
text-gray-400 transition-colors
`}
`}
placeholder="Enter display name"
value={username}
required
/>
>
Login by name
</div>
</div>
<div className={`flex flex-col items-start gap-2`}>
{loginByRole ? (
<>
<label
className={`
text-gray-800 text-md
dark:text-white
`}
>
Role
</label>
<OlDropdown label={username} className={`w-full`}>
<OlDropdownItem onClick={() => {setUsername("Game master")}}>Game master</OlDropdownItem>
<OlDropdownItem onClick={() => {setUsername("Blue commander")}}>Blue commander</OlDropdownItem>
<OlDropdownItem onClick={() => {setUsername("Red commander")}}>Red commander</OlDropdownItem>
</OlDropdown>
</>
) : (
<>
<label
className={`
text-gray-800 text-md
dark:text-white
`}
>
Username
</label>
<input
type="text"
autoComplete="username"
onChange={(ev) => setUsername(ev.currentTarget.value)}
className={`
block w-full rounded-lg border border-gray-300
bg-gray-50 p-2.5 text-sm text-gray-900
dark:border-gray-600 dark:bg-gray-700
dark:text-white dark:placeholder-gray-400
dark:focus:border-blue-500
dark:focus:ring-blue-500
focus:border-blue-500 focus:ring-blue-500
`}
placeholder="Enter username"
value={username}
required
/>
</>
)}
<label
className={`
text-gray-800 text-md
@ -186,9 +236,8 @@ export function LoginModal(props: { open: boolean }) {
type="password"
onChange={(ev) => setPassword(ev.currentTarget.value)}
className={`
block w-full max-w-80 rounded-lg border
border-gray-300 bg-gray-50 p-2.5 text-sm
text-gray-900
block w-full rounded-lg border border-gray-300
bg-gray-50 p-2.5 text-sm text-gray-900
dark:border-gray-600 dark:bg-gray-700
dark:text-white dark:placeholder-gray-400
dark:focus:border-blue-500 dark:focus:ring-blue-500
@ -198,6 +247,7 @@ export function LoginModal(props: { open: boolean }) {
required
/>
</div>
<div className="flex">
<button
type="button"
@ -234,9 +284,10 @@ export function LoginModal(props: { open: boolean }) {
>
Choose your role
</label>
<OlDropdown label={activeCommandMode ?? ""} className={`
w-48
`}>
<OlDropdown
label={activeCommandMode ?? ""}
className={`w-48`}
>
{commandModes?.map((commandMode) => {
return <OlDropdownItem onClick={() => setActiveCommandMode(commandMode)}>{commandMode}</OlDropdownItem>;
})}
@ -266,21 +317,41 @@ export function LoginModal(props: { open: boolean }) {
) : (
<>
<ErrorCallout
title="Server could not be reached or password is incorrect"
description="The Olympus Server at this address could not be reached or the password is incorrect. Check your password. If correct, check the address is correct, restart the Olympus server or reinstall Olympus. Ensure the ports set are not already used."
title="Server could not be reached or username/password is incorrect"
description="The Olympus Server at this address could not be reached or the password is incorrect. Check your username and password. If correct, check the address is correct, restart the Olympus server or reinstall Olympus. Ensure the ports set are not already used."
></ErrorCallout>
<div className={`text-sm font-medium text-gray-200`}>
Still having issues? See our{" "}
<a
href=""
<div className="flex gap-4">
<button
type="button"
onClick={() => {
getApp().setState(OlympusState.LOGIN, LoginSubState.CREDENTIALS);
setLoginError(false);
}}
className={`
text-blue-300 underline
hover:no-underline
flex content-center items-center gap-2 rounded-sm
bg-blue-700 px-5 py-2.5 text-sm font-medium text-white
dark:bg-blue-600 dark:hover:bg-blue-700
dark:focus:ring-blue-800
focus:outline-none focus:ring-4 focus:ring-blue-300
hover:bg-blue-800
`}
>
troubleshooting guide here
</a>
.
<FontAwesomeIcon className={`my-auto`} icon={faArrowLeft} />
Back
</button>
<div className={`my-auto text-sm font-medium text-gray-200`}>
Still having issues? See our{" "}
<a
href=""
className={`
text-blue-300 underline
hover:no-underline
`}
>
troubleshooting guide here
</a>
.
</div>
</div>
</>
)}
@ -309,6 +380,7 @@ export function LoginModal(props: { open: boolean }) {
</div>
)}
</div>
<div
className={`
flex flex-grow flex-row content-end justify-center gap-3
@ -325,9 +397,9 @@ export function LoginModal(props: { open: boolean }) {
object-cover
`}
></img>
<div
className={`mt-2 flex content-center items-center gap-2 font-bold`}
>
<div className={`
mt-2 flex content-center items-center gap-2 font-bold
`}>
YouTube Video Guide
<FontAwesomeIcon className={`my-auto text-xs text-gray-400`} icon={faExternalLink} />
</div>
@ -348,9 +420,9 @@ export function LoginModal(props: { open: boolean }) {
object-cover
`}
></img>
<div
className={`mt-2 flex content-center items-center gap-2 font-bold`}
>
<div className={`
mt-2 flex content-center items-center gap-2 font-bold
`}>
Wiki Guide
<FontAwesomeIcon className={`my-auto text-xs text-gray-400`} icon={faExternalLink} />
</div>

View File

@ -73,7 +73,6 @@ export function UI() {
<ProtectionPromptModal open={appState === OlympusState.UNIT_CONTROL && appSubState == UnitControlSubState.PROTECTION} />
<KeybindModal open={appState === OlympusState.OPTIONS && appSubState === OptionsSubstate.KEYBIND} />
<ImportExportModal open={appState === OlympusState.IMPORT_EXPORT} />
<LoginModal open={appState === OlympusState.LOGIN} />
<WarningModal open={appState === OlympusState.WARNING} />
<TrainingModal open={appState === OlympusState.TRAINING} />
<AdminModal open={appState === OlympusState.ADMIN} />