diff --git a/visions/ui/package-lock.json b/visions/ui/package-lock.json index 807f246..337109c 100644 --- a/visions/ui/package-lock.json +++ b/visions/ui/package-lock.json @@ -20,6 +20,7 @@ "classnames": "^2.5.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-redux": "^9.2.0", "react-router": "^6.28.0", "react-router-dom": "^6.28.0", "react-scripts": "5.0.1", @@ -3747,6 +3748,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" + }, "node_modules/@types/ws": { "version": "8.5.13", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", @@ -12920,6 +12926,28 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -15257,6 +15285,14 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/visions/ui/package.json b/visions/ui/package.json index 65707a6..f95789d 100644 --- a/visions/ui/package.json +++ b/visions/ui/package.json @@ -15,6 +15,7 @@ "classnames": "^2.5.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-redux": "^9.2.0", "react-router": "^6.28.0", "react-router-dom": "^6.28.0", "react-scripts": "5.0.1", diff --git a/visions/ui/src/App.tsx b/visions/ui/src/App.tsx index 59220e2..a607bbb 100644 --- a/visions/ui/src/App.tsx +++ b/visions/ui/src/App.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { PropsWithChildren, useEffect, useState } from 'react'; import './App.css'; import { Client } from './client'; import { createBrowserRouter, RouterProvider } from 'react-router-dom'; @@ -10,6 +10,7 @@ import { Admin } from './views/Admin/Admin'; import Candela from './plugins/Candela'; import { Authentication } from './views/Authentication/Authentication'; import { StateProvider } from './components/StateProvider'; +import { AuthProvider } from './providers/AuthProvider/AuthProvider'; const TEST_CHARSHEET_UUID = "12df9c09-1f2f-4147-8eda-a97bd2a7a803"; @@ -27,6 +28,20 @@ const CandelaCharsheet = ({ client }: { client: Client }) => { return sheet ? :
} +interface AuthedViewProps { + client: Client; +} + +const AuthedView = ({ client, children }: PropsWithChildren) => { + return ( + + console.log(password)} onAuth={(username, password) => console.log(username, password)}> + {children} + + + ); +} + const App = ({ client }: AppProps) => { console.log("rendering app"); const [websocketUrl, setWebsocketUrl] = useState(undefined); @@ -39,7 +54,7 @@ const App = ({ client }: AppProps) => { createBrowserRouter([ { path: "/", - element: + element: }, { diff --git a/visions/ui/src/components/StateProvider.tsx b/visions/ui/src/components/StateProvider.tsx index 990b0ba..79b504a 100644 --- a/visions/ui/src/components/StateProvider.tsx +++ b/visions/ui/src/components/StateProvider.tsx @@ -3,19 +3,14 @@ import { Status, Tabletop } from "visions-types"; import { Client } from "../client"; import { assertNever } from "../plugins/Candela"; -type AuthState = { type: "NoAdmin" } | { type: "Unauthed" } | { type: "Authed", username: string }; - type TabletopState = { - auth: AuthState; tabletop: Tabletop; } -type StateAction = { type: "SetAuthState", state: AuthState } - | { type: "HandleMessage" }; +type Action = {}; const initialState = (): TabletopState => ( { - auth: { type: "Unauthed" }, tabletop: { backgroundColor: { red: 0, green: 0, blue: 0 }, backgroundImage: undefined } } ); @@ -28,37 +23,10 @@ export const StateProvider = ({ client, children }: PropsWithChildren { - console.log("useCallback"); - client.status().then((status: Status) => { - console.log("status: ", status); - if (status.admin_enabled) { - dispatch({ type: "SetAuthState", state: { type: "Unauthed" } }); - } else { - dispatch({ type: "SetAuthState", state: { type: "NoAdmin" } }); - } - }) - }, - [client] - ); - return {children} ; } -const stateReducer = (state: TabletopState, action: StateAction): TabletopState => { - switch (action.type) { - case "SetAuthState": { - return { ...state, auth: action.state }; - } - case "HandleMessage": { - return state; - } - default: { - assertNever(action); - return state; - } - } -} +const stateReducer = (state: TabletopState, _action: Action): TabletopState => state; diff --git a/visions/ui/src/providers/AuthProvider/AuthProvider.tsx b/visions/ui/src/providers/AuthProvider/AuthProvider.tsx new file mode 100644 index 0000000..e32743d --- /dev/null +++ b/visions/ui/src/providers/AuthProvider/AuthProvider.tsx @@ -0,0 +1,35 @@ +import React, { createContext, PropsWithChildren, useEffect, useReducer } from "react"; +import { Status } from "visions-types"; +import { Client } from "../../client"; + +type AuthState = { type: "NoAdmin" } | { type: "Unauthed" } | { type: "Authed", username: string }; + +type Action = { type: "SetAuthState", state: AuthState }; + +export const AuthContext = createContext({ type: "NoAdmin" }); + +interface AuthProviderProps { + client: Client; +} + +export const AuthProvider = ({ client, children }: PropsWithChildren) => { + const [authState, dispatch] = useReducer(stateReducer, { type: "NoAdmin" }); + + useEffect(() => { + client.status().then((status: Status) => { + if (status.admin_enabled) { + dispatch({ type: "SetAuthState", state: { type: "Unauthed" } }); + } else { + dispatch({ type: "SetAuthState", state: { type: "NoAdmin" } }); + } + }) + }, + [client] + ); + + return ( + {children} + ); +} + +const stateReducer = (_state: AuthState, action: Action) => action.state; diff --git a/visions/ui/src/views/Authentication/Authentication.tsx b/visions/ui/src/views/Authentication/Authentication.tsx index 743ca1b..39496ab 100644 --- a/visions/ui/src/views/Authentication/Authentication.tsx +++ b/visions/ui/src/views/Authentication/Authentication.tsx @@ -1,17 +1,22 @@ -import React, { PropsWithChildren, useContext } from 'react'; +import React, { PropsWithChildren, useContext, useState } from 'react'; import { AppContext } from '../../components/StateProvider'; import { assertNever } from '../../plugins/Candela'; +import { AuthContext } from '../../providers/AuthProvider/AuthProvider'; import './Authentication.css'; interface AuthenticationProps { + onAdminPassword: (password: string) => void; + onAuth: (username: string, password: string) => void; } -export const Authentication = ({ children }: PropsWithChildren) => { +export const Authentication = ({ onAdminPassword, onAuth, children }: PropsWithChildren) => { // No admin password set: prompt for the admin password // Password set, nobody logged in: prompt for login // User logged in: show the children - let { auth } = useContext(AppContext); + let [userField, setUserField] = useState(""); + let [pwField, setPwField] = useState(""); + let auth = useContext(AuthContext); switch (auth.type) { case "NoAdmin": { @@ -19,8 +24,8 @@ export const Authentication = ({ children }: PropsWithChildren

Welcome to your new Visions VTT Instance

Set your admin password:

- - + setPwField(evt.target.value)} /> + onAdminPassword(pwField)} /> ; } @@ -29,9 +34,9 @@ export const Authentication = ({ children }: PropsWithChildren

Welcome to Visions VTT

- - - + setUserField(evt.target.value)} /> + setPwField(evt.target.value)} /> + onAuth(userField, pwField)} />
;