Updated some legal screen stuff

This commit is contained in:
Alois 2026-03-26 16:34:57 +00:00
commit ea72de0f08
6 changed files with 215 additions and 306 deletions

View file

@ -63,14 +63,6 @@ export default function Form() {
const { save } = useStorage(); const { save } = useStorage();
const navigate = useNavigate(); const navigate = useNavigate();
/**
* Opens the hidden file input when the upload tile is clicked.
* @returns Void.
*/
const handleUploadTileClick = React.useCallback((): void => {
uploadRef.current?.click();
}, []);
/** /**
* Handles uploaded .tu files and stores resolved credentials. * Handles uploaded .tu files and stores resolved credentials.
* @param event Change event from the hidden file input. * @param event Change event from the hidden file input.
@ -105,7 +97,7 @@ export default function Form() {
* @returns Promise that resolves after login processing. * @returns Promise that resolves after login processing.
*/ */
const handleCredentialsSubmit = React.useCallback( const handleCredentialsSubmit = React.useCallback(
async (event: React.FormEvent<HTMLFormElement>): Promise<void> => { async (event: React.SubmitEvent<HTMLFormElement>): Promise<void> => {
event.preventDefault(); event.preventDefault();
const formData = new FormData(event.currentTarget); const formData = new FormData(event.currentTarget);
@ -135,7 +127,7 @@ export default function Form() {
await save("user_id", user.data.user_id); await save("user_id", user.data.user_id);
await save("private_key", inputParse.data.private_key); await save("private_key", inputParse.data.private_key);
void navigate({ to: "/" }); navigate({ to: "/" });
} catch (error) { } catch (error) {
log(0, "Login", "red", error); log(0, "Login", "red", error);
toast("error", "Failed to fetch user data"); toast("error", "Failed to fetch user data");
@ -145,47 +137,37 @@ export default function Form() {
); );
return ( return (
<div className="flex gap-5"> <div className="flex gap-15">
<Card className="w-75 h-80"> <div
<CardHeader> onClick={() => uploadRef.current?.click()}
<CardTitle>Use .tu file</CardTitle> className="flex flex-col gap-3 cursor-pointer w-55 aspect-square bg-input/13 hover:bg-input/30 transition-all duration-300 ease-in-out border-3 items-center justify-center rounded-lg"
</CardHeader> >
<CardContent className="h-full flex items-center justify-center"> <Upload className="text-foreground" size={27} />
<div <p className="text-md">Upload .tu file</p>
onClick={handleUploadTileClick} </div>
className="cursor-pointer w-60 aspect-square mb-17 bg-input/13 hover:bg-input/30 transition-all duration-300 ease-in-out border-dotted border-input/75 border-3 flex items-center justify-center rounded-lg" <input
> accept=".tu"
<Upload className="text-input/75" size={34} /> onChange={handleFileInputChange}
</div> type="file"
<input ref={uploadRef}
onChange={handleFileInputChange} hidden
type="file" />
ref={uploadRef} <form
className="hidden" className="flex flex-col gap-5 aspect-square w-55"
/> onSubmit={handleCredentialsSubmit}
</CardContent> >
</Card> <div className="flex flex-col gap-2">
<Card className="w-75 h-auto"> <Label htmlFor="username">Username</Label>
<CardHeader> <Input required type="text" id="username" name="username" />
<CardTitle>Use credentials</CardTitle> </div>
</CardHeader> <div className="flex flex-col gap-2">
<CardContent> <Label htmlFor="private_key">Private Key</Label>
<form <Input required type="password" id="private_key" name="private_key" />
className="flex flex-col gap-5 h-full" </div>
onSubmit={handleCredentialsSubmit} <Button className="mt-auto" type="submit">
> Login
<div className="flex flex-col gap-2"> </Button>
<Label htmlFor="username">Username</Label> </form>
<Input type="text" id="username" name="username" />
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="private_key">Private Key</Label>
<Input type="password" id="private_key" name="private_key" />
</div>
<Button type="submit">Login</Button>
</form>
</CardContent>
</Card>
</div> </div>
); );
} }

View file

@ -1,4 +1,4 @@
import * as React from "react"; import { useState, useCallback, useEffect } from "react";
import { useStorage } from "@tensamin/storage/context"; import { useStorage } from "@tensamin/storage/context";
import { Button } from "@tensamin/ui/cmp/button"; import { Button } from "@tensamin/ui/cmp/button";
import { Checkbox } from "@tensamin/ui/cmp/checkbox"; import { Checkbox } from "@tensamin/ui/cmp/checkbox";
@ -11,112 +11,39 @@ import { log } from "@tensamin/shared/log";
import Link from "@tensamin/ui/link"; import Link from "@tensamin/ui/link";
import { Label } from "@tensamin/ui/cmp/label"; import { Label } from "@tensamin/ui/cmp/label";
type SaveFn = ReturnType<typeof useStorage>["save"]; // Prevents the user from using Tensamin without accepting the privacy policy and terms of service.
/**
* Persists accepted legal documents and marks the first onboarding step complete.
* @param save Storage save function.
* @param currentDocs Current legal documents fetched from the server.
* @param setPPandToSDone State setter for legal acceptance completion.
* @returns Promise that resolves when persistence is complete.
*/
async function persistAcceptedDocs(
save: SaveFn,
currentDocs: z.infer<typeof legalDocsSchema>,
setPPandToSDone: React.Dispatch<React.SetStateAction<boolean>>,
): Promise<void> {
await save("accepted_privacy_policy", true);
await save("accepted_terms_of_service", true);
await save("ppandtos_done", true);
await save("legal_docs", currentDocs);
setPPandToSDone(true);
}
/**
* Persists analytics preference toggles and marks analytics onboarding complete.
* @param save Storage save function.
* @param crashReports Whether crash reports are enabled.
* @param usageData Whether usage data is enabled.
* @param setCrashReports State setter for crash reports.
* @param setUsageData State setter for usage data.
* @param setDoneWithAnalytics State setter for analytics completion.
* @returns Promise that resolves when persistence is complete.
*/
async function persistAnalyticsPreferences(
save: SaveFn,
crashReports: boolean,
usageData: boolean,
setCrashReports: React.Dispatch<React.SetStateAction<boolean>>,
setUsageData: React.Dispatch<React.SetStateAction<boolean>>,
setDoneWithAnalytics: React.Dispatch<React.SetStateAction<boolean>>,
): Promise<void> {
await save("analytics_crash_reports", crashReports);
setCrashReports(crashReports);
await save("analytics_usage_data", usageData);
setUsageData(usageData);
await save("analytics_done", true);
setDoneWithAnalytics(true);
}
/**
* Gates the application behind legal and analytics consent checks.
* @param props Component props containing children to render after consent.
* @returns Legal onboarding or wrapped children JSX.
*/
export default function Screen(props: { children: React.ReactNode }) { export default function Screen(props: { children: React.ReactNode }) {
const { load, save } = useStorage(); const { load, save } = useStorage();
const [error, setError] = React.useState(""); const [error, setError] = useState("");
const [errorDescription, setErrorDescription] = React.useState(""); const [errorDescription, setErrorDescription] = useState("");
const [remoteDocs, setRemoteDocs] = React.useState< const [remoteDocs, setRemoteDocs] = useState<
z.infer<typeof legalDocsSchema> | undefined z.infer<typeof legalDocsSchema> | undefined
>(undefined); >(undefined);
const [loading, setLoading] = React.useState(true); const [loading, setLoading] = useState(true);
const [PPandToSDone, setPPandToSDone] = React.useState(false); const [acceptedPP, acceptPP] = useState(false);
const [acceptedPP, acceptPP] = React.useState(false); const [acceptedTOS, acceptTOS] = useState(false);
const [acceptedTOS, acceptTOS] = React.useState(false); const [hasContinued, setHasContinued] = useState(false);
const [doneWithAnalytics, setDoneWithAnalytics] = React.useState(false); const [userId, setUserId] = useState<number | undefined>(undefined);
const [crashReports, setCrashReports] = React.useState(false);
const [usageData, setUsageData] = React.useState(false);
/** // Saves
* Handles continue action for privacy policy and terms acceptance. const handleContinueLegal = useCallback((): void => {
* @returns Void.
*/
const handleContinueLegal = React.useCallback((): void => {
const currentDocs = remoteDocs; const currentDocs = remoteDocs;
if (!currentDocs) { if (!currentDocs) {
return; return;
} }
void persistAcceptedDocs(save, currentDocs, setPPandToSDone); save("accepted_privacy_policy", true);
save("accepted_terms_of_service", true);
save("legal_docs", currentDocs);
setHasContinued(true);
}, [remoteDocs, save]); }, [remoteDocs, save]);
/** useEffect(() => {
* Handles continue action for analytics preferences.
* @returns Void.
*/
const handleContinueAnalytics = React.useCallback((): void => {
const currentCrashReports = crashReports;
const currentUsageData = usageData;
void persistAnalyticsPreferences(
save,
currentCrashReports,
currentUsageData,
setCrashReports,
setUsageData,
setDoneWithAnalytics,
);
}, [crashReports, save, usageData]);
React.useEffect(() => {
let active = true; let active = true;
void load("user_id").then(async (id) => { void load("user_id").then(async (id) => {
@ -124,9 +51,9 @@ export default function Screen(props: { children: React.ReactNode }) {
return; return;
} }
setUserId(id);
if (id === 0) { if (id === 0) {
setPPandToSDone(true);
setDoneWithAnalytics(true);
setLoading(false); setLoading(false);
return; return;
} }
@ -170,41 +97,24 @@ export default function Screen(props: { children: React.ReactNode }) {
const localDocs = await load("legal_docs"); const localDocs = await load("legal_docs");
const [ const [loadedAcceptedPP, loadedAcceptedTOS] = await Promise.all([
loadedPPAndTOS,
loadedAcceptedPP,
loadedAcceptedTOS,
loadedAnalyticsDone,
loadedCrashReports,
loadedUsageData,
] = await Promise.all([
load("ppandtos_done"),
load("accepted_privacy_policy"), load("accepted_privacy_policy"),
load("accepted_terms_of_service"), load("accepted_terms_of_service"),
load("analytics_done"),
load("analytics_crash_reports"),
load("analytics_usage_data"),
]); ]);
if (!active) { if (!active) {
return; return;
} }
setPPandToSDone(loadedPPAndTOS);
acceptPP(loadedAcceptedPP); acceptPP(loadedAcceptedPP);
acceptTOS(loadedAcceptedTOS); acceptTOS(loadedAcceptedTOS);
setDoneWithAnalytics(loadedAnalyticsDone);
setCrashReports(loadedCrashReports);
setUsageData(loadedUsageData);
if (localDocs.pp.hash !== safeCurrent.data.pp.hash) { if (localDocs.pp.hash !== safeCurrent.data.pp.hash) {
acceptPP(false); acceptPP(false);
setPPandToSDone(false);
} }
if (localDocs.tos.hash !== safeCurrent.data.tos.hash) { if (localDocs.tos.hash !== safeCurrent.data.tos.hash) {
acceptTOS(false); acceptTOS(false);
setPPandToSDone(false);
} }
setLoading(false); setLoading(false);
@ -219,91 +129,70 @@ export default function Screen(props: { children: React.ReactNode }) {
return <ErrorScreen error={error} description={errorDescription} />; return <ErrorScreen error={error} description={errorDescription} />;
} }
if (loading) { if (loading || userId === undefined) {
return null; return null;
} }
if (PPandToSDone && doneWithAnalytics) { if ((acceptedPP && acceptedTOS && hasContinued) || userId === 0) {
return <>{props.children}</>; return <>{props.children}</>;
} }
return ( return (
<div className="w-full h-full flex items-center justify-center"> <div className="w-full h-full flex items-center justify-center">
<div className="h-full flex flex-col gap-15 p-10 md:p-40 w-full lg:w-2/3"> <div className="h-full flex flex-col gap-15 p-10 md:p-40 w-full lg:w-2/3">
{!PPandToSDone ? ( <h1 className="text-3xl md:text-4xl font-bold">
<> Privacy Policy & ToS
<h1 className="text-3xl md:text-4xl font-bold"> <p className="text-muted-foreground text-[20px] font-normal pt-3">
Privacy Policy & ToS {remoteDocs?.pp.version} / {remoteDocs?.tos.version}
<p className="text-muted-foreground text-[20px] font-normal pt-3"> </p>
{remoteDocs?.pp.version} / {remoteDocs?.tos.version} </h1>
</p> <div className="w-full h-full flex flex-col items-center justify-center gap-5">
</h1> <div className="justify-start items-start flex flex-col gap-2">
<div className="w-full h-full flex flex-col items-center justify-center gap-5"> <BigCheckbox
<div className="justify-start items-start flex flex-col gap-2"> checked={acceptedPP}
<BigCheckbox onChange={acceptPP}
checked={acceptedPP} label="I agree to the Privacy Policy"
onChange={acceptPP}
label="I agree to the Privacy Policy"
/>
<BigCheckbox
checked={acceptedTOS}
onChange={acceptTOS}
label="I agree to the Terms of Service"
/>
<div className="w-full border-t-2" />
<Link
label="Privacy Policy"
link={`https://legal.tensamin.net/pp/${remoteDocs?.pp.version}`}
/>
<Link
label="Terms of Service"
link={`https://legal.tensamin.net/tos/${remoteDocs?.tos.version}`}
/>
</div>
</div>
<ContinueButton
disabled={!acceptedPP || !acceptedTOS}
onClick={handleContinueLegal}
/> />
</> <BigCheckbox
) : ( checked={acceptedTOS}
<> onChange={acceptTOS}
<h1 className="text-3xl md:text-4xl font-bold">Analytics</h1> label="I agree to the Terms of Service"
<div className="w-full h-full flex justify-center items-center"> />
<div className="justify-start flex flex-col gap-2"> <div className="w-full border-t-2" />
<BigCheckbox <Link
checked={crashReports} label="Privacy Policy"
onChange={setCrashReports} link={`https://legal.tensamin.net/pp/${remoteDocs?.pp.version}`}
label="Send anonymous crash reports" />
/> <Link
<BigCheckbox label="Terms of Service"
checked={usageData} link={`https://legal.tensamin.net/tos/${remoteDocs?.tos.version}`}
onChange={setUsageData} />
label="Send anonymous usage data" </div>
/> </div>
</div> <ContinueButton
</div> disabled={!acceptedPP || !acceptedTOS}
<ContinueButton onClick={handleContinueAnalytics} /> onClick={handleContinueLegal}
</> />
)}
</div> </div>
</div> </div>
); );
} }
/** // Components
* Renders a large continue button used by legal and analytics steps. function ContinueButton({
* @param props Button props with click callback and disabled state. onClick,
* @returns Continue button JSX. disabled,
*/ }: {
function ContinueButton(props: { onClick: () => void; disabled?: boolean }) { onClick: () => void;
disabled?: boolean;
}) {
return ( return (
<div className="w-full flex justify-end"> <div className="w-full flex justify-end">
<Button <Button
size="lg" size="lg"
className="text-lg w-full md:w-auto" className="text-md w-full md:w-auto"
onClick={props.onClick} onClick={onClick}
disabled={props.disabled} disabled={disabled}
> >
Continue Continue
</Button> </Button>
@ -311,12 +200,11 @@ function ContinueButton(props: { onClick: () => void; disabled?: boolean }) {
); );
} }
/** export function BigCheckbox({
* Renders a larger checkbox row for onboarding preferences. label,
* @param props Checkbox label, current value, and change callback. checked,
* @returns Checkbox row JSX. onChange,
*/ }: {
export function BigCheckbox(props: {
label: string; label: string;
checked: boolean; checked: boolean;
onChange: (checked: boolean) => void; onChange: (checked: boolean) => void;
@ -324,11 +212,11 @@ export function BigCheckbox(props: {
return ( return (
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Checkbox <Checkbox
checked={props.checked} checked={checked}
onCheckedChange={props.onChange} onCheckedChange={onChange}
className="size-5.5 rounded-md flex items-center justify-center" className="size-5.5 rounded-md flex items-center justify-center"
/> />
<Label className="text-lg">{props.label}</Label> <Label className="text-lg">{label}</Label>
</div> </div>
); );
} }

View file

@ -1,10 +1,12 @@
import Socket from "@tensamin/ttp/context"; import Socket from "@tensamin/ttp/context";
import User from "@tensamin/user/context"; import User from "@tensamin/user/context";
import type { ReactNode } from "react"; import { useEffect, useState, type ReactNode } from "react";
import { useNavigate, useLocation } from "@tanstack/react-router";
import Sidebar from "@/components/sidebar"; import Sidebar from "@/components/sidebar";
import Conversation from "@/features/conversation/context"; import Conversation from "@/features/conversation/context";
import Navbar from "@/components/navbar"; import Navbar from "@/components/navbar";
import { useStorage } from "@tensamin/storage/context";
/** /**
* Executes Layout. * Executes Layout.
@ -12,8 +14,30 @@ import Navbar from "@/components/navbar";
* @returns unknown. * @returns unknown.
*/ */
export default function Layout(props: { children: ReactNode }) { export default function Layout(props: { children: ReactNode }) {
const [loggedIn, setLoggedIn] = useState<boolean | null>(null);
const { load } = useStorage();
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
console.log(location);
load("user_id").then((userId) => {
if (userId !== 0) {
setLoggedIn(true);
} else {
navigate({
to: "/login",
});
setLoggedIn(false);
}
});
}, [location]);
return ( return (
<Socket> <Socket blockConnection={!loggedIn}>
<User> <User>
<Conversation> <Conversation>
<div className="w-full h-full flex bg-sidebar"> <div className="w-full h-full flex bg-sidebar">

View file

@ -1,6 +1,4 @@
import Form from "@/components/screens/login/form"; import Form from "@/components/screens/login/form";
import { Button } from "@tensamin/ui/cmp/button";
import { Link } from "@tanstack/react-router";
/** /**
* Executes Page. * Executes Page.
@ -12,9 +10,12 @@ export default function Page() {
<div className="w-full h-full flex flex-col gap-10 items-center justify-center"> <div className="w-full h-full flex flex-col gap-10 items-center justify-center">
<Form /> <Form />
<div className="flex"> <div className="flex">
<Link to="/signup"> <a target="_blank" href="https://docs.tensamin.net/installation/#iota">
<Button variant="outline">Sign up</Button> <p className="text-sm underline text-foreground/75">
</Link> Don't have an account yet? Click here to get help setting up an
Iota.
</p>
</a>
</div> </div>
</div> </div>
); );

View file

@ -1,4 +1,13 @@
import * as React from "react"; import {
useState,
createContext,
type ReactNode,
useRef,
useEffect,
useContext,
useCallback,
useMemo,
} from "react";
import { useCrypto } from "@tensamin/crypto/context"; import { useCrypto } from "@tensamin/crypto/context";
import { log } from "@tensamin/shared/log"; import { log } from "@tensamin/shared/log";
import { useStorage } from "@tensamin/storage/context"; import { useStorage } from "@tensamin/storage/context";
@ -129,34 +138,35 @@ type ContextType = {
identified: () => boolean; identified: () => boolean;
}; };
const socketContext = React.createContext<ContextType | undefined>(undefined); const socketContext = createContext<ContextType | undefined>(undefined);
/** /**
* Provides socket transport state and authenticated send operations to children. * Provides socket transport state and authenticated send operations to children.
* @param props Component props with children. * @param props Component props with children.
* @returns Loading, error, or provider-wrapped JSX. * @returns Loading, error, or provider-wrapped JSX.
*/ */
export default function Provider(props: { children: React.ReactNode }) { export default function Provider(props: {
children: ReactNode;
blockConnection?: boolean;
}) {
const { load } = useStorage(); const { load } = useStorage();
const { decrypt, getSharedSecret } = useCrypto(); const { decrypt, getSharedSecret } = useCrypto();
const [readyState, setReadyState] = React.useState<number>( const [readyState, setReadyState] = useState<number>(READY_STATE.CLOSED);
READY_STATE.CLOSED, const [connected, setConnected] = useState<boolean>(false);
); const [identified, setIdentified] = useState<boolean>(false);
const [connected, setConnected] = React.useState<boolean>(false); const [identifying, setIdentifying] = useState<boolean>(false);
const [identified, setIdentified] = React.useState<boolean>(false);
const [identifying, setIdentifying] = React.useState<boolean>(false);
const [ownPing, setOwnPing] = React.useState<number>(0); const [ownPing, setOwnPing] = useState<number>(0);
const [iotaPing, setIotaPing] = React.useState<number>(0); const [iotaPing, setIotaPing] = useState<number>(0);
const [error, setError] = React.useState(""); const [error, setError] = useState("");
const [errorDescription, setErrorDescription] = React.useState(""); const [errorDescription, setErrorDescription] = useState("");
const clientRef = React.useRef<ReturnType< const clientRef = useRef<ReturnType<
typeof createTransportClient<Schemas> typeof createTransportClient<Schemas>
> | null>(null); > | null>(null);
const identificationStartedRef = React.useRef(false); const identificationStartedRef = useRef(false);
/** /**
* Sends typed protocol messages through the active transport client. * Sends typed protocol messages through the active transport client.
@ -165,7 +175,7 @@ export default function Provider(props: { children: React.ReactNode }) {
* @param options Optional request id and response mode. * @param options Optional request id and response mode.
* @returns A promise for either void (no response) or typed message payload. * @returns A promise for either void (no response) or typed message payload.
*/ */
const send = React.useCallback<BoundSendFn<Schemas>>( const send = useCallback<BoundSendFn<Schemas>>(
(( ((
type: string, type: string,
data?: Record<string, unknown>, data?: Record<string, unknown>,
@ -192,7 +202,7 @@ export default function Provider(props: { children: React.ReactNode }) {
[], [],
); );
React.useEffect(() => { useEffect(() => {
if (!connected || !identified) { if (!connected || !identified) {
return; return;
} }
@ -222,7 +232,7 @@ export default function Provider(props: { children: React.ReactNode }) {
}; };
}, [connected, identified, send]); }, [connected, identified, send]);
React.useEffect(() => { useEffect(() => {
let attempts = 0; let attempts = 0;
let reconnectTimer: ReturnType<typeof setTimeout> | null = null; let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
let reconnectResetTimer: ReturnType<typeof setTimeout> | null = null; let reconnectResetTimer: ReturnType<typeof setTimeout> | null = null;
@ -296,43 +306,45 @@ export default function Provider(props: { children: React.ReactNode }) {
}, RETRY_INTERVAL); }, RETRY_INTERVAL);
}; };
const transportClient = createTransportClient(schemas, { const transportClient = !props.blockConnection
url: TRANSPORT_URL, ? createTransportClient(schemas, {
onReadyStateChange: (state) => { url: TRANSPORT_URL,
setReadyState(state); onReadyStateChange: (state) => {
setReadyState(state);
if (state === READY_STATE.OPEN) { if (state === READY_STATE.OPEN) {
clearReconnectTimer(); clearReconnectTimer();
scheduleReconnectReset(); scheduleReconnectReset();
identificationStartedRef.current = false; identificationStartedRef.current = false;
setConnected(true); setConnected(true);
setIdentified(false); setIdentified(false);
setError(""); setError("");
setErrorDescription(""); setErrorDescription("");
return; return;
} }
clearReconnectResetTimer(); clearReconnectResetTimer();
identificationStartedRef.current = false; identificationStartedRef.current = false;
setConnected(false); setConnected(false);
setIdentified(false); setIdentified(false);
}, },
onClose: ({ error: closeError, intentional }) => { onClose: ({ error: closeError, intentional }) => {
clearReconnectResetTimer(); clearReconnectResetTimer();
setConnected(false); setConnected(false);
setIdentified(false); setIdentified(false);
setIdentifying(false); setIdentifying(false);
if (disposed || intentional) { if (disposed || intentional) {
return; return;
} }
log(0, "Socket", "red", "Disconnected", closeError, { log(0, "Socket", "red", "Disconnected", closeError, {
stopSending: isStopSendingError(closeError), stopSending: isStopSendingError(closeError),
}); });
scheduleReconnect(closeError); scheduleReconnect(closeError);
}, },
}); })
: null;
clientRef.current = transportClient; clientRef.current = transportClient;
@ -346,7 +358,7 @@ export default function Provider(props: { children: React.ReactNode }) {
} }
try { try {
await transportClient.connect(TRANSPORT_URL); await transportClient?.connect(TRANSPORT_URL);
} catch (connectError) { } catch (connectError) {
if (disposed) { if (disposed) {
return; return;
@ -368,7 +380,7 @@ export default function Provider(props: { children: React.ReactNode }) {
clientRef.current = null; clientRef.current = null;
} }
void transportClient.close("context-dispose"); void transportClient?.close("context-dispose");
setReadyState(READY_STATE.CLOSED); setReadyState(READY_STATE.CLOSED);
setConnected(false); setConnected(false);
setIdentified(false); setIdentified(false);
@ -377,7 +389,7 @@ export default function Provider(props: { children: React.ReactNode }) {
}; };
}, []); }, []);
React.useEffect(() => { useEffect(() => {
if (!connected) { if (!connected) {
setIdentifying(false); setIdentifying(false);
setIdentified(false); setIdentified(false);
@ -489,7 +501,7 @@ export default function Provider(props: { children: React.ReactNode }) {
}; };
}, [connected, decrypt, getSharedSecret, load, send]); }, [connected, decrypt, getSharedSecret, load, send]);
const progress = React.useMemo(() => { const progress = useMemo(() => {
if (readyState === READY_STATE.CONNECTING) return 30; if (readyState === READY_STATE.CONNECTING) return 30;
if (!connected) return 45; if (!connected) return 45;
if (identifying) return 75; if (identifying) return 75;
@ -497,7 +509,7 @@ export default function Provider(props: { children: React.ReactNode }) {
return 100; return 100;
}, [connected, identified, identifying, readyState]); }, [connected, identified, identifying, readyState]);
const loadingTitle = React.useMemo(() => { const loadingTitle = useMemo(() => {
if (readyState === READY_STATE.CONNECTING || !connected) { if (readyState === READY_STATE.CONNECTING || !connected) {
return "Connecting to Tensamin"; return "Connecting to Tensamin";
} }
@ -509,7 +521,7 @@ export default function Provider(props: { children: React.ReactNode }) {
return "Loading"; return "Loading";
}, [connected, identified, identifying, readyState]); }, [connected, identified, identifying, readyState]);
const loadingDescription = React.useMemo(() => { const loadingDescription = useMemo(() => {
if (readyState === READY_STATE.CONNECTING || !connected) { if (readyState === READY_STATE.CONNECTING || !connected) {
return "Establishing transport channel"; return "Establishing transport channel";
} }
@ -521,7 +533,7 @@ export default function Provider(props: { children: React.ReactNode }) {
return undefined; return undefined;
}, [connected, identified, identifying, readyState]); }, [connected, identified, identifying, readyState]);
const contextValue = React.useMemo<ContextType>( const contextValue = useMemo<ContextType>(
() => ({ () => ({
send, send,
readyState: () => readyState, readyState: () => readyState,
@ -559,7 +571,7 @@ export default function Provider(props: { children: React.ReactNode }) {
* @returns Socket context API for transport operations and connection state. * @returns Socket context API for transport operations and connection state.
*/ */
export function useSocket(): ContextType { export function useSocket(): ContextType {
const context = React.useContext(socketContext); const context = useContext(socketContext);
if (!context) { if (!context) {
throw new Error("useSocket must be used within a SocketProvider"); throw new Error("useSocket must be used within a SocketProvider");
} }

View file

@ -1 +1,3 @@
- Calculate message height and pass to virtualizer (-> packages/chat/src/components/input.tsx) - Calculate message height and pass to virtualizer (-> packages/chat/src/components/input.tsx)
- Review legal screen stuff
- Fix that location change doesn't trigger loggedIn so the user is stuck on a connecting page