Updated some legal screen stuff
This commit is contained in:
parent
246b36abc9
commit
ea72de0f08
6 changed files with 215 additions and 306 deletions
|
|
@ -63,14 +63,6 @@ export default function Form() {
|
|||
const { save } = useStorage();
|
||||
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.
|
||||
* @param event Change event from the hidden file input.
|
||||
|
|
@ -105,7 +97,7 @@ export default function Form() {
|
|||
* @returns Promise that resolves after login processing.
|
||||
*/
|
||||
const handleCredentialsSubmit = React.useCallback(
|
||||
async (event: React.FormEvent<HTMLFormElement>): Promise<void> => {
|
||||
async (event: React.SubmitEvent<HTMLFormElement>): Promise<void> => {
|
||||
event.preventDefault();
|
||||
|
||||
const formData = new FormData(event.currentTarget);
|
||||
|
|
@ -135,7 +127,7 @@ export default function Form() {
|
|||
await save("user_id", user.data.user_id);
|
||||
await save("private_key", inputParse.data.private_key);
|
||||
|
||||
void navigate({ to: "/" });
|
||||
navigate({ to: "/" });
|
||||
} catch (error) {
|
||||
log(0, "Login", "red", error);
|
||||
toast("error", "Failed to fetch user data");
|
||||
|
|
@ -145,47 +137,37 @@ export default function Form() {
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="flex gap-5">
|
||||
<Card className="w-75 h-80">
|
||||
<CardHeader>
|
||||
<CardTitle>Use .tu file</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="h-full flex items-center justify-center">
|
||||
<div
|
||||
onClick={handleUploadTileClick}
|
||||
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"
|
||||
>
|
||||
<Upload className="text-input/75" size={34} />
|
||||
</div>
|
||||
<input
|
||||
onChange={handleFileInputChange}
|
||||
type="file"
|
||||
ref={uploadRef}
|
||||
className="hidden"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="w-75 h-auto">
|
||||
<CardHeader>
|
||||
<CardTitle>Use credentials</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form
|
||||
className="flex flex-col gap-5 h-full"
|
||||
onSubmit={handleCredentialsSubmit}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="username">Username</Label>
|
||||
<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 className="flex gap-15">
|
||||
<div
|
||||
onClick={() => uploadRef.current?.click()}
|
||||
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"
|
||||
>
|
||||
<Upload className="text-foreground" size={27} />
|
||||
<p className="text-md">Upload .tu file</p>
|
||||
</div>
|
||||
<input
|
||||
accept=".tu"
|
||||
onChange={handleFileInputChange}
|
||||
type="file"
|
||||
ref={uploadRef}
|
||||
hidden
|
||||
/>
|
||||
<form
|
||||
className="flex flex-col gap-5 aspect-square w-55"
|
||||
onSubmit={handleCredentialsSubmit}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="username">Username</Label>
|
||||
<Input required type="text" id="username" name="username" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="private_key">Private Key</Label>
|
||||
<Input required type="password" id="private_key" name="private_key" />
|
||||
</div>
|
||||
<Button className="mt-auto" type="submit">
|
||||
Login
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import * as React from "react";
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { useStorage } from "@tensamin/storage/context";
|
||||
import { Button } from "@tensamin/ui/cmp/button";
|
||||
import { Checkbox } from "@tensamin/ui/cmp/checkbox";
|
||||
|
|
@ -11,112 +11,39 @@ import { log } from "@tensamin/shared/log";
|
|||
import Link from "@tensamin/ui/link";
|
||||
import { Label } from "@tensamin/ui/cmp/label";
|
||||
|
||||
type SaveFn = ReturnType<typeof useStorage>["save"];
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// Prevents the user from using Tensamin without accepting the privacy policy and terms of service.
|
||||
export default function Screen(props: { children: React.ReactNode }) {
|
||||
const { load, save } = useStorage();
|
||||
|
||||
const [error, setError] = React.useState("");
|
||||
const [errorDescription, setErrorDescription] = React.useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [errorDescription, setErrorDescription] = useState("");
|
||||
|
||||
const [remoteDocs, setRemoteDocs] = React.useState<
|
||||
const [remoteDocs, setRemoteDocs] = useState<
|
||||
z.infer<typeof legalDocsSchema> | undefined
|
||||
>(undefined);
|
||||
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [PPandToSDone, setPPandToSDone] = React.useState(false);
|
||||
const [acceptedPP, acceptPP] = React.useState(false);
|
||||
const [acceptedTOS, acceptTOS] = React.useState(false);
|
||||
const [acceptedPP, acceptPP] = useState(false);
|
||||
const [acceptedTOS, acceptTOS] = useState(false);
|
||||
const [hasContinued, setHasContinued] = useState(false);
|
||||
|
||||
const [doneWithAnalytics, setDoneWithAnalytics] = React.useState(false);
|
||||
const [crashReports, setCrashReports] = React.useState(false);
|
||||
const [usageData, setUsageData] = React.useState(false);
|
||||
const [userId, setUserId] = useState<number | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* Handles continue action for privacy policy and terms acceptance.
|
||||
* @returns Void.
|
||||
*/
|
||||
const handleContinueLegal = React.useCallback((): void => {
|
||||
// Saves
|
||||
const handleContinueLegal = useCallback((): void => {
|
||||
const currentDocs = remoteDocs;
|
||||
if (!currentDocs) {
|
||||
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]);
|
||||
|
||||
/**
|
||||
* 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(() => {
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
|
||||
void load("user_id").then(async (id) => {
|
||||
|
|
@ -124,9 +51,9 @@ export default function Screen(props: { children: React.ReactNode }) {
|
|||
return;
|
||||
}
|
||||
|
||||
setUserId(id);
|
||||
|
||||
if (id === 0) {
|
||||
setPPandToSDone(true);
|
||||
setDoneWithAnalytics(true);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
|
@ -170,41 +97,24 @@ export default function Screen(props: { children: React.ReactNode }) {
|
|||
|
||||
const localDocs = await load("legal_docs");
|
||||
|
||||
const [
|
||||
loadedPPAndTOS,
|
||||
loadedAcceptedPP,
|
||||
loadedAcceptedTOS,
|
||||
loadedAnalyticsDone,
|
||||
loadedCrashReports,
|
||||
loadedUsageData,
|
||||
] = await Promise.all([
|
||||
load("ppandtos_done"),
|
||||
const [loadedAcceptedPP, loadedAcceptedTOS] = await Promise.all([
|
||||
load("accepted_privacy_policy"),
|
||||
load("accepted_terms_of_service"),
|
||||
load("analytics_done"),
|
||||
load("analytics_crash_reports"),
|
||||
load("analytics_usage_data"),
|
||||
]);
|
||||
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
|
||||
setPPandToSDone(loadedPPAndTOS);
|
||||
acceptPP(loadedAcceptedPP);
|
||||
acceptTOS(loadedAcceptedTOS);
|
||||
setDoneWithAnalytics(loadedAnalyticsDone);
|
||||
setCrashReports(loadedCrashReports);
|
||||
setUsageData(loadedUsageData);
|
||||
|
||||
if (localDocs.pp.hash !== safeCurrent.data.pp.hash) {
|
||||
acceptPP(false);
|
||||
setPPandToSDone(false);
|
||||
}
|
||||
|
||||
if (localDocs.tos.hash !== safeCurrent.data.tos.hash) {
|
||||
acceptTOS(false);
|
||||
setPPandToSDone(false);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
|
|
@ -219,91 +129,70 @@ export default function Screen(props: { children: React.ReactNode }) {
|
|||
return <ErrorScreen error={error} description={errorDescription} />;
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
if (loading || userId === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (PPandToSDone && doneWithAnalytics) {
|
||||
if ((acceptedPP && acceptedTOS && hasContinued) || userId === 0) {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<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">
|
||||
{!PPandToSDone ? (
|
||||
<>
|
||||
<h1 className="text-3xl md:text-4xl font-bold">
|
||||
Privacy Policy & ToS
|
||||
<p className="text-muted-foreground text-[20px] font-normal pt-3">
|
||||
{remoteDocs?.pp.version} / {remoteDocs?.tos.version}
|
||||
</p>
|
||||
</h1>
|
||||
<div className="w-full h-full flex flex-col items-center justify-center gap-5">
|
||||
<div className="justify-start items-start flex flex-col gap-2">
|
||||
<BigCheckbox
|
||||
checked={acceptedPP}
|
||||
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}
|
||||
<h1 className="text-3xl md:text-4xl font-bold">
|
||||
Privacy Policy & ToS
|
||||
<p className="text-muted-foreground text-[20px] font-normal pt-3">
|
||||
{remoteDocs?.pp.version} / {remoteDocs?.tos.version}
|
||||
</p>
|
||||
</h1>
|
||||
<div className="w-full h-full flex flex-col items-center justify-center gap-5">
|
||||
<div className="justify-start items-start flex flex-col gap-2">
|
||||
<BigCheckbox
|
||||
checked={acceptedPP}
|
||||
onChange={acceptPP}
|
||||
label="I agree to the Privacy Policy"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h1 className="text-3xl md:text-4xl font-bold">Analytics</h1>
|
||||
<div className="w-full h-full flex justify-center items-center">
|
||||
<div className="justify-start flex flex-col gap-2">
|
||||
<BigCheckbox
|
||||
checked={crashReports}
|
||||
onChange={setCrashReports}
|
||||
label="Send anonymous crash reports"
|
||||
/>
|
||||
<BigCheckbox
|
||||
checked={usageData}
|
||||
onChange={setUsageData}
|
||||
label="Send anonymous usage data"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ContinueButton onClick={handleContinueAnalytics} />
|
||||
</>
|
||||
)}
|
||||
<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}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a large continue button used by legal and analytics steps.
|
||||
* @param props Button props with click callback and disabled state.
|
||||
* @returns Continue button JSX.
|
||||
*/
|
||||
function ContinueButton(props: { onClick: () => void; disabled?: boolean }) {
|
||||
// Components
|
||||
function ContinueButton({
|
||||
onClick,
|
||||
disabled,
|
||||
}: {
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="w-full flex justify-end">
|
||||
<Button
|
||||
size="lg"
|
||||
className="text-lg w-full md:w-auto"
|
||||
onClick={props.onClick}
|
||||
disabled={props.disabled}
|
||||
className="text-md w-full md:w-auto"
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
|
|
@ -311,12 +200,11 @@ function ContinueButton(props: { onClick: () => void; disabled?: boolean }) {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a larger checkbox row for onboarding preferences.
|
||||
* @param props Checkbox label, current value, and change callback.
|
||||
* @returns Checkbox row JSX.
|
||||
*/
|
||||
export function BigCheckbox(props: {
|
||||
export function BigCheckbox({
|
||||
label,
|
||||
checked,
|
||||
onChange,
|
||||
}: {
|
||||
label: string;
|
||||
checked: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
|
|
@ -324,11 +212,11 @@ export function BigCheckbox(props: {
|
|||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
checked={props.checked}
|
||||
onCheckedChange={props.onChange}
|
||||
checked={checked}
|
||||
onCheckedChange={onChange}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import Socket from "@tensamin/ttp/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 Conversation from "@/features/conversation/context";
|
||||
import Navbar from "@/components/navbar";
|
||||
import { useStorage } from "@tensamin/storage/context";
|
||||
|
||||
/**
|
||||
* Executes Layout.
|
||||
|
|
@ -12,8 +14,30 @@ import Navbar from "@/components/navbar";
|
|||
* @returns unknown.
|
||||
*/
|
||||
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 (
|
||||
<Socket>
|
||||
<Socket blockConnection={!loggedIn}>
|
||||
<User>
|
||||
<Conversation>
|
||||
<div className="w-full h-full flex bg-sidebar">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import Form from "@/components/screens/login/form";
|
||||
import { Button } from "@tensamin/ui/cmp/button";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
/**
|
||||
* 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">
|
||||
<Form />
|
||||
<div className="flex">
|
||||
<Link to="/signup">
|
||||
<Button variant="outline">Sign up</Button>
|
||||
</Link>
|
||||
<a target="_blank" href="https://docs.tensamin.net/installation/#iota">
|
||||
<p className="text-sm underline text-foreground/75">
|
||||
Don't have an account yet? Click here to get help setting up an
|
||||
Iota.
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 { log } from "@tensamin/shared/log";
|
||||
import { useStorage } from "@tensamin/storage/context";
|
||||
|
|
@ -129,34 +138,35 @@ type ContextType = {
|
|||
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.
|
||||
* @param props Component props with children.
|
||||
* @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 { decrypt, getSharedSecret } = useCrypto();
|
||||
|
||||
const [readyState, setReadyState] = React.useState<number>(
|
||||
READY_STATE.CLOSED,
|
||||
);
|
||||
const [connected, setConnected] = React.useState<boolean>(false);
|
||||
const [identified, setIdentified] = React.useState<boolean>(false);
|
||||
const [identifying, setIdentifying] = React.useState<boolean>(false);
|
||||
const [readyState, setReadyState] = useState<number>(READY_STATE.CLOSED);
|
||||
const [connected, setConnected] = useState<boolean>(false);
|
||||
const [identified, setIdentified] = useState<boolean>(false);
|
||||
const [identifying, setIdentifying] = useState<boolean>(false);
|
||||
|
||||
const [ownPing, setOwnPing] = React.useState<number>(0);
|
||||
const [iotaPing, setIotaPing] = React.useState<number>(0);
|
||||
const [ownPing, setOwnPing] = useState<number>(0);
|
||||
const [iotaPing, setIotaPing] = useState<number>(0);
|
||||
|
||||
const [error, setError] = React.useState("");
|
||||
const [errorDescription, setErrorDescription] = React.useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [errorDescription, setErrorDescription] = useState("");
|
||||
|
||||
const clientRef = React.useRef<ReturnType<
|
||||
const clientRef = useRef<ReturnType<
|
||||
typeof createTransportClient<Schemas>
|
||||
> | null>(null);
|
||||
const identificationStartedRef = React.useRef(false);
|
||||
const identificationStartedRef = useRef(false);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @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,
|
||||
data?: Record<string, unknown>,
|
||||
|
|
@ -192,7 +202,7 @@ export default function Provider(props: { children: React.ReactNode }) {
|
|||
[],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (!connected || !identified) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -222,7 +232,7 @@ export default function Provider(props: { children: React.ReactNode }) {
|
|||
};
|
||||
}, [connected, identified, send]);
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
let attempts = 0;
|
||||
let reconnectTimer: 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);
|
||||
};
|
||||
|
||||
const transportClient = createTransportClient(schemas, {
|
||||
url: TRANSPORT_URL,
|
||||
onReadyStateChange: (state) => {
|
||||
setReadyState(state);
|
||||
const transportClient = !props.blockConnection
|
||||
? createTransportClient(schemas, {
|
||||
url: TRANSPORT_URL,
|
||||
onReadyStateChange: (state) => {
|
||||
setReadyState(state);
|
||||
|
||||
if (state === READY_STATE.OPEN) {
|
||||
clearReconnectTimer();
|
||||
scheduleReconnectReset();
|
||||
identificationStartedRef.current = false;
|
||||
setConnected(true);
|
||||
setIdentified(false);
|
||||
setError("");
|
||||
setErrorDescription("");
|
||||
return;
|
||||
}
|
||||
if (state === READY_STATE.OPEN) {
|
||||
clearReconnectTimer();
|
||||
scheduleReconnectReset();
|
||||
identificationStartedRef.current = false;
|
||||
setConnected(true);
|
||||
setIdentified(false);
|
||||
setError("");
|
||||
setErrorDescription("");
|
||||
return;
|
||||
}
|
||||
|
||||
clearReconnectResetTimer();
|
||||
identificationStartedRef.current = false;
|
||||
setConnected(false);
|
||||
setIdentified(false);
|
||||
},
|
||||
onClose: ({ error: closeError, intentional }) => {
|
||||
clearReconnectResetTimer();
|
||||
setConnected(false);
|
||||
setIdentified(false);
|
||||
setIdentifying(false);
|
||||
clearReconnectResetTimer();
|
||||
identificationStartedRef.current = false;
|
||||
setConnected(false);
|
||||
setIdentified(false);
|
||||
},
|
||||
onClose: ({ error: closeError, intentional }) => {
|
||||
clearReconnectResetTimer();
|
||||
setConnected(false);
|
||||
setIdentified(false);
|
||||
setIdentifying(false);
|
||||
|
||||
if (disposed || intentional) {
|
||||
return;
|
||||
}
|
||||
if (disposed || intentional) {
|
||||
return;
|
||||
}
|
||||
|
||||
log(0, "Socket", "red", "Disconnected", closeError, {
|
||||
stopSending: isStopSendingError(closeError),
|
||||
});
|
||||
scheduleReconnect(closeError);
|
||||
},
|
||||
});
|
||||
log(0, "Socket", "red", "Disconnected", closeError, {
|
||||
stopSending: isStopSendingError(closeError),
|
||||
});
|
||||
scheduleReconnect(closeError);
|
||||
},
|
||||
})
|
||||
: null;
|
||||
|
||||
clientRef.current = transportClient;
|
||||
|
||||
|
|
@ -346,7 +358,7 @@ export default function Provider(props: { children: React.ReactNode }) {
|
|||
}
|
||||
|
||||
try {
|
||||
await transportClient.connect(TRANSPORT_URL);
|
||||
await transportClient?.connect(TRANSPORT_URL);
|
||||
} catch (connectError) {
|
||||
if (disposed) {
|
||||
return;
|
||||
|
|
@ -368,7 +380,7 @@ export default function Provider(props: { children: React.ReactNode }) {
|
|||
clientRef.current = null;
|
||||
}
|
||||
|
||||
void transportClient.close("context-dispose");
|
||||
void transportClient?.close("context-dispose");
|
||||
setReadyState(READY_STATE.CLOSED);
|
||||
setConnected(false);
|
||||
setIdentified(false);
|
||||
|
|
@ -377,7 +389,7 @@ export default function Provider(props: { children: React.ReactNode }) {
|
|||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (!connected) {
|
||||
setIdentifying(false);
|
||||
setIdentified(false);
|
||||
|
|
@ -489,7 +501,7 @@ export default function Provider(props: { children: React.ReactNode }) {
|
|||
};
|
||||
}, [connected, decrypt, getSharedSecret, load, send]);
|
||||
|
||||
const progress = React.useMemo(() => {
|
||||
const progress = useMemo(() => {
|
||||
if (readyState === READY_STATE.CONNECTING) return 30;
|
||||
if (!connected) return 45;
|
||||
if (identifying) return 75;
|
||||
|
|
@ -497,7 +509,7 @@ export default function Provider(props: { children: React.ReactNode }) {
|
|||
return 100;
|
||||
}, [connected, identified, identifying, readyState]);
|
||||
|
||||
const loadingTitle = React.useMemo(() => {
|
||||
const loadingTitle = useMemo(() => {
|
||||
if (readyState === READY_STATE.CONNECTING || !connected) {
|
||||
return "Connecting to Tensamin";
|
||||
}
|
||||
|
|
@ -509,7 +521,7 @@ export default function Provider(props: { children: React.ReactNode }) {
|
|||
return "Loading";
|
||||
}, [connected, identified, identifying, readyState]);
|
||||
|
||||
const loadingDescription = React.useMemo(() => {
|
||||
const loadingDescription = useMemo(() => {
|
||||
if (readyState === READY_STATE.CONNECTING || !connected) {
|
||||
return "Establishing transport channel";
|
||||
}
|
||||
|
|
@ -521,7 +533,7 @@ export default function Provider(props: { children: React.ReactNode }) {
|
|||
return undefined;
|
||||
}, [connected, identified, identifying, readyState]);
|
||||
|
||||
const contextValue = React.useMemo<ContextType>(
|
||||
const contextValue = useMemo<ContextType>(
|
||||
() => ({
|
||||
send,
|
||||
readyState: () => readyState,
|
||||
|
|
@ -559,7 +571,7 @@ export default function Provider(props: { children: React.ReactNode }) {
|
|||
* @returns Socket context API for transport operations and connection state.
|
||||
*/
|
||||
export function useSocket(): ContextType {
|
||||
const context = React.useContext(socketContext);
|
||||
const context = useContext(socketContext);
|
||||
if (!context) {
|
||||
throw new Error("useSocket must be used within a SocketProvider");
|
||||
}
|
||||
|
|
|
|||
2
todo.md
2
todo.md
|
|
@ -1 +1,3 @@
|
|||
- 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
|
||||
|
|
|
|||
Loading…
Reference in a new issue