Improve the user profile, create a Modal, and provide a way to create a user
This commit is contained in:
parent
dcd5514433
commit
94a821d657
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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`;
|
||||||
|
24
visions/ui/src/components/Modal/Modal.css
Normal file
24
visions/ui/src/components/Modal/Modal.css
Normal 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);
|
||||||
|
}
|
33
visions/ui/src/components/Modal/Modal.tsx
Normal file
33
visions/ui/src/components/Modal/Modal.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -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%;
|
|
||||||
}
|
|
||||||
|
@ -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>)
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 }
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(() => {
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
9
visions/ui/src/views/Profile/Profile.css
Normal file
9
visions/ui/src/views/Profile/Profile.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.profile-view_columns {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-view_columns > div {
|
||||||
|
width: 45%;
|
||||||
|
}
|
||||||
|
|
45
visions/ui/src/views/Profile/Profile.tsx
Normal file
45
visions/ui/src/views/Profile/Profile.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
import { MainView } from './Main/Main'
|
import { MainView } from './Main/Main'
|
||||||
|
import { ProfileView } from './Profile/Profile'
|
||||||
|
|
||||||
export { MainView }
|
export { MainView, ProfileView }
|
||||||
|
Loading…
Reference in New Issue
Block a user