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)} />
;