Improve the user profile, create a Modal, and provide a way to create a user

This commit is contained in:
Savanni D'Gerinel 2025-01-26 21:30:05 -05:00
parent dcd5514433
commit 94a821d657
15 changed files with 153 additions and 28 deletions

View File

@ -64,7 +64,13 @@ pub fn routes(core: Core) -> Router {
let Json(req) = req; let Json(req) = req;
wrap_handler(|| create_user(core, headers, req)) wrap_handler(|| create_user(core, headers, req))
} }
}), })
.layer(
CorsLayer::new()
.allow_methods([Method::PUT])
.allow_headers([AUTHORIZATION, CONTENT_TYPE])
.allow_origin(Any),
),
) )
.route( .route(
"/api/v1/users", "/api/v1/users",

View File

@ -3,11 +3,11 @@ import './App.css'
import { Client } from './client' import { Client } from './client'
import { createBrowserRouter, RouterProvider } from 'react-router-dom' import { createBrowserRouter, RouterProvider } from 'react-router-dom'
import { DesignPage } from './views/Design/Design' import { DesignPage } from './views/Design/Design'
import { Admin } from './views/Admin/Admin'
import Candela from './plugins/Candela' import Candela from './plugins/Candela'
import { Authentication } from './views/Authentication/Authentication' import { Authentication } from './views/Authentication/Authentication'
import { StateContext, StateProvider } from './providers/StateProvider/StateProvider' import { StateContext, StateProvider } from './providers/StateProvider/StateProvider'
import { MainView } from './views' import { MainView } from './views'
import { AdminView } from './views/Admin/Admin'
const TEST_CHARSHEET_UUID = "12df9c09-1f2f-4147-8eda-a97bd2a7a803" const TEST_CHARSHEET_UUID = "12df9c09-1f2f-4147-8eda-a97bd2a7a803"
@ -62,7 +62,7 @@ const App = ({ client }: AppProps) => {
}, },
{ {
path: "/admin", path: "/admin",
element: <Admin client={client} /> element: <AdminView client={client} />
}, },
{ {
path: "/candela", path: "/candela",

View File

@ -63,6 +63,17 @@ export class Client {
return fetch(url).then((response) => response.json()); return fetch(url).then((response) => response.json());
} }
async createUser(sessionId: string, username: string) {
const url = new URL(this.base);
url.pathname = '/api/v1/user';
return fetch(url, {
method: 'PUT',
headers: [['Authorization', `Bearer: ${sessionId}`],
['Content-Type', 'application/json']],
body: JSON.stringify({ username }),
}).then((response) => response.json())
}
async setPassword(password_1: string, password_2: string) { async setPassword(password_1: string, password_2: string) {
const url = new URL(this.base); const url = new URL(this.base);
url.pathname = `/api/v1/user/password`; url.pathname = `/api/v1/user/password`;

View File

@ -0,0 +1,24 @@
.modal {
z-index: 1;
position: absolute;
width: 100%;
height: 100%;
background-color: rgba(128, 128, 128, 0.8);
display: flex;
justify-content: center;
align-items: center;
}
.modal_window {
border: 1px solid black;
border-radius: 5px;
padding: 1em;
}
.modal_title {
background-color: skyblue;
}
.modal_cta {
margin-top: var(--margin-s);
}

View File

@ -0,0 +1,33 @@
import { PropsWithChildren } from "react";
import "./Modal.css";
export type Action = {
name: string
action: () => void
}
export interface ModalProps {
title: string
onCancel: Action
onPrimary: Action
onSecondary?: Action
}
export const Modal = ({ title, children, onCancel, onPrimary, onSecondary }: PropsWithChildren<ModalProps>) => (
<div className="modal">
<div className="modal_window">
<h1 className="modal_title"> {title} </h1>
<div className="modal_body">
{children}
</div>
<footer className="modal_cta">
<button onClick={() => onCancel.action()}>{onCancel.name}</button>
{onSecondary ? <button onClick={() => onSecondary.action()}>{onSecondary.name}</button> : <></>}
<button onClick={() => onPrimary.action()}>{onPrimary.name}</button>
</footer>
</div>
</div>
)

View File

@ -2,11 +2,3 @@
margin: var(--margin-s); margin: var(--margin-s);
} }
.profile_columns {
display: flex;
justify-content: space-between;
}
.profile_columns > div {
width: 45%;
}

View File

@ -4,15 +4,12 @@ import './Profile.css';
interface ProfileProps { interface ProfileProps {
profile: UserOverview, profile: UserOverview,
users: UserOverview[],
games: GameOverview[], games: GameOverview[],
} }
export const ProfileElement = ({ profile, users, games }: ProfileProps) => { export const ProfileElement = ({ profile, games }: ProfileProps) => {
const adminNote = profile.isAdmin ? <div> <i>Note: this user is an admin</i> </div> : <></>; const adminNote = profile.isAdmin ? <div> <i>Note: this user is an admin</i> </div> : <></>;
const userList = profile.isAdmin && <UserManagementElement users={users} />;
return (<div className="profile profile_columns"> return (<div className="profile profile_columns">
<CardElement name={profile.name}> <CardElement name={profile.name}>
<div>Games: {games.map((game) => { <div>Games: {games.map((game) => {
@ -20,7 +17,5 @@ export const ProfileElement = ({ profile, users, games }: ProfileProps) => {
}) }</div> }) }</div>
{adminNote} {adminNote}
</CardElement> </CardElement>
{userList}
</div>) </div>)
} }

View File

@ -1,12 +1,15 @@
import { PropsWithChildren, useState } from "react"
import { AccountState, UserOverview } from "visions-types" import { AccountState, UserOverview } from "visions-types"
import { CardElement} from ".." import { CardElement} from ".."
interface UserManagementProps { interface UserManagementProps {
users: UserOverview[] users: UserOverview[]
onShowCreateUser: () => void
} }
export const UserManagementElement = ({ users }: UserManagementProps) => { export const UserManagementElement = ({ users, onShowCreateUser }: UserManagementProps) => {
return ( return (
<>
<CardElement name="Users"> <CardElement name="Users">
<table> <table>
<thead> <thead>
@ -18,8 +21,9 @@ export const UserManagementElement = ({ users }: UserManagementProps) => {
</tr>)} </tr>)}
</tbody> </tbody>
</table> </table>
<button>Create User</button> <button onClick={() => onShowCreateUser()}>Create User</button>
</CardElement> </CardElement>
</>
) )
} }

View File

@ -1,9 +1,10 @@
import { CardElement } from './Card/Card' import { CardElement } from './Card/Card'
import { GameOverviewElement } from './GameOverview/GameOverview' import { GameOverviewElement } from './GameOverview/GameOverview'
import { Modal, ModalProps } from './Modal/Modal'
import { ProfileElement } from './Profile/Profile' import { ProfileElement } from './Profile/Profile'
import { SimpleGuage } from './Guages/SimpleGuage' import { SimpleGuage } from './Guages/SimpleGuage'
import { ThumbnailElement } from './Thumbnail/Thumbnail' import { ThumbnailElement } from './Thumbnail/Thumbnail'
import { TabletopElement } from './Tabletop/Tabletop' import { TabletopElement } from './Tabletop/Tabletop'
import { UserManagementElement } from './UserManagement/UserManagement' import { UserManagementElement } from './UserManagement/UserManagement'
export { CardElement, GameOverviewElement, UserManagementElement, ProfileElement, ThumbnailElement, TabletopElement, SimpleGuage } export { CardElement, GameOverviewElement, Modal, type ModalProps, UserManagementElement, ProfileElement, ThumbnailElement, TabletopElement, SimpleGuage }

View File

@ -97,12 +97,15 @@ class StateManager {
break; break;
} }
} }
/*
if (sessionId) {
window.localStorage.setItem("sessionId", sessionId);
this.dispatch({ type: "SetAuthState", content: { type: "Authed", sessionId } });
} }
*/
async createUser(username: string) {
if (!this.client || !this.dispatch) return;
const sessionId = window.localStorage.getItem("sessionId");
if (sessionId) {
let createUserResponse = await this.client.createUser(sessionId, username);
}
} }
} }

View File

@ -45,7 +45,7 @@ interface AdminProps {
client: Client, client: Client,
} }
export const Admin = ({ client }: AdminProps) => { export const AdminView = ({ client }: AdminProps) => {
const [users, setUsers] = useState<Array<User>>([]); const [users, setUsers] = useState<Array<User>>([]);
useEffect(() => { useEffect(() => {

View File

@ -3,6 +3,7 @@ import { UserOverview } from 'visions-types';
import { Client } from '../../client'; import { Client } from '../../client';
import { ProfileElement } from '../../components'; import { ProfileElement } from '../../components';
import { getSessionId, StateContext, StateProvider } from '../../providers/StateProvider/StateProvider'; import { getSessionId, StateContext, StateProvider } from '../../providers/StateProvider/StateProvider';
import { ProfileView } from '../Profile/Profile';
interface MainProps { interface MainProps {
client: Client client: Client
@ -26,7 +27,7 @@ export const MainView = ({ client }: MainProps) => {
return ( return (
<div> <div>
{profile && <ProfileElement profile={profile} users={users} games={[]} />} {profile && <ProfileView profile={profile} users={users} games={[]} />}
</div> </div>
) )
} }

View File

@ -0,0 +1,9 @@
.profile-view_columns {
display: flex;
justify-content: space-between;
}
.profile-view_columns > div {
width: 45%;
}

View File

@ -0,0 +1,45 @@
import { useContext, useState } from "react"
import { GameOverview, UserOverview } from "visions-types"
import { Modal, ModalProps, ProfileElement, UserManagementElement } from "../../components"
import { StateContext } from "../../providers/StateProvider/StateProvider"
import "./Profile.css"
interface ProfileProps {
profile: UserOverview,
users: UserOverview[],
games: GameOverview[],
}
interface CreateUserModalProps {
onCancel: () => void
onCreateUser: (name: string) => void
}
const CreateUserModal = ({ onCancel, onCreateUser }: CreateUserModalProps) => {
const [userName, setUserName] = useState("");
return <Modal title="Create User" onCancel={{ name: "Cancel", action: onCancel }}
onPrimary={{ name: "Create User", action: () => onCreateUser(userName) }}>
<input type="text" placeholder="username" onChange={(evt) => {
setUserName(evt.target.value);
}} />
</Modal>
}
export const ProfileView = ({ profile, users, games, }: ProfileProps) => {
const [_state, manager] = useContext(StateContext)
const [showUser, setShowUser] = useState(false)
const userList = profile.isAdmin && <UserManagementElement users={users} onShowCreateUser={() => setShowUser(true)} />
return (
<div className="profile-view">
{showUser && <CreateUserModal onCancel={() => setShowUser(false)} onCreateUser={(username) => manager.createUser(username)} />}
<div className="profile-view_columns">
<ProfileElement profile={profile} games={games} />
{userList}
</div>
</div>
)
}

View File

@ -1,3 +1,4 @@
import { MainView } from './Main/Main' import { MainView } from './Main/Main'
import { ProfileView } from './Profile/Profile'
export { MainView } export { MainView, ProfileView }