Skip to content

Commit ea0175b

Browse files
authored
Merge pull request #77 from element-hq/langleyd/dialog-flows
Dialog flows and Setup views
2 parents 72b817b + da36d8a commit ea0175b

File tree

80 files changed

+4930
-2700
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+4930
-2700
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"lodash-es": "^4.17.21",
3030
"react": "^19.0.0",
3131
"react-dom": "^19.0.0",
32+
"react-focus-lock": "^2.13.7",
3233
"react-virtuoso": "^4.12.8",
3334
"sanitize-html": "^2.12.1",
3435
"uniffi-bindgen-react-native": "git+https://github.com/jhugman/uniffi-bindgen-react-native#5c01f3f7025d069aac1dd1fd51ca72bb76fdb243"

src/App.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const App: React.FC = () => {
3535
return <OidcCallback />;
3636
}
3737

38-
const { clientState, loginViewModel, encryptionViewModel } =
38+
const { clientState, loginFlowViewModel, encryptionFlowViewModel } =
3939
useViewModel(clientViewModel);
4040
console.log("App rendering with clientState:", clientState);
4141

@@ -50,8 +50,8 @@ const App: React.FC = () => {
5050
</LoadingScreen>
5151
);
5252
} else if (clientState === ClientState.SettingUpEncryption) {
53-
component = encryptionViewModel ? (
54-
<Encryption encryptionViewModel={encryptionViewModel} />
53+
component = encryptionFlowViewModel ? (
54+
<Encryption encryptionFlowViewModel={encryptionFlowViewModel} />
5555
) : null;
5656
} else if (clientState === ClientState.Syncing) {
5757
component = (
@@ -77,8 +77,8 @@ const App: React.FC = () => {
7777
clientState === ClientState.LoggedOut ||
7878
clientState === ClientState.LoggingIn
7979
) {
80-
component = loginViewModel ? (
81-
<Login loginViewModel={loginViewModel} />
80+
component = loginFlowViewModel ? (
81+
<Login loginFlowViewModel={loginFlowViewModel} />
8282
) : null;
8383
}
8484

src/ConfirmIdentityScreen.module.css

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/ConfirmIdentityScreen.tsx

Lines changed: 0 additions & 66 deletions
This file was deleted.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2026 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import { Button } from "@vector-im/compound-web";
9+
import { useViewModel } from "@element-hq/web-shared-components";
10+
import type React from "react";
11+
import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key";
12+
import type {
13+
ConfirmIdentityStepViewModel,
14+
ConfirmIdentityStepViewSnapshot,
15+
} from "./ConfirmIdentityStepViewModel";
16+
import { IdentityConfirmationAction } from "./ConfirmIdentityStepViewModel";
17+
import type { ScreenProps } from "./screenRegistry.types";
18+
import { SetupScreenLayout, SetupScreenHeader } from "../SetupScreen";
19+
20+
/**
21+
* Screen for confirming user identity.
22+
* Shows available options: recovery key, interactive verification, or reset.
23+
*/
24+
export const ConfirmIdentityScreen: React.FC<
25+
ScreenProps<ConfirmIdentityStepViewModel>
26+
> = ({ viewModel }) => {
27+
const { availableActions } = useViewModel(
28+
viewModel,
29+
) as ConfirmIdentityStepViewSnapshot;
30+
31+
return (
32+
<SetupScreenLayout>
33+
<SetupScreenHeader
34+
Icon={KeyIcon}
35+
title="Confirm your identity"
36+
subtitle="To access your encrypted messages, you need to verify your identity."
37+
/>
38+
39+
<div
40+
style={{
41+
display: "flex",
42+
flexDirection: "column",
43+
gap: "var(--cpd-space-4x)",
44+
}}
45+
>
46+
<Button kind="primary" size="lg" disabled={true}>
47+
Use another device (coming soon)
48+
</Button>
49+
50+
{availableActions.includes(
51+
IdentityConfirmationAction.Recovery,
52+
) && (
53+
<Button
54+
kind="primary"
55+
size="lg"
56+
onClick={() => viewModel.useRecoveryKey()}
57+
>
58+
Use recovery key
59+
</Button>
60+
)}
61+
62+
<Button
63+
kind="secondary"
64+
size="lg"
65+
onClick={() => viewModel.cannotConfirm()}
66+
>
67+
Can't confirm
68+
</Button>
69+
</div>
70+
</SetupScreenLayout>
71+
);
72+
};
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2026 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import { FlowStepViewModel } from "./FlowStepViewModel";
9+
10+
/**
11+
* Available actions on the Confirm Identity screen
12+
*/
13+
export enum IdentityConfirmationAction {
14+
/** Use recovery key to verify */
15+
Recovery = "recovery",
16+
/** Use another device for interactive verification */
17+
InteractiveVerification = "interactive_verification",
18+
}
19+
20+
/**
21+
* Result from the confirm identity step
22+
*/
23+
export type ConfirmIdentityResult =
24+
| { outcome: "useRecoveryKey" }
25+
| { outcome: "resetIdentity" }
26+
| { outcome: "interactiveVerification" };
27+
28+
/**
29+
* Props for ConfirmIdentityStepViewModel
30+
*/
31+
export interface ConfirmIdentityStepViewModelProps {
32+
/** Available confirmation actions */
33+
availableActions: IdentityConfirmationAction[];
34+
}
35+
36+
/**
37+
* Observable state for confirm identity screen
38+
*/
39+
export interface ConfirmIdentityStepViewSnapshot {
40+
/** Available confirmation actions */
41+
availableActions: IdentityConfirmationAction[];
42+
}
43+
44+
/**
45+
* Actions available on the confirm identity screen
46+
*/
47+
export interface ConfirmIdentityStepViewActions {
48+
useRecoveryKey(): void;
49+
useInteractiveVerification(): void;
50+
cannotConfirm(): void;
51+
}
52+
53+
/**
54+
* ViewModel for the confirm identity step.
55+
* Entry point for the encryption flow - user chooses how to verify.
56+
*/
57+
export class ConfirmIdentityStepViewModel
58+
extends FlowStepViewModel<
59+
ConfirmIdentityStepViewSnapshot,
60+
ConfirmIdentityStepViewModelProps,
61+
ConfirmIdentityResult
62+
>
63+
implements ConfirmIdentityStepViewActions
64+
{
65+
public readonly screenType = "confirm-identity";
66+
67+
public constructor(props: ConfirmIdentityStepViewModelProps) {
68+
super(props, {
69+
availableActions: props.availableActions,
70+
});
71+
}
72+
73+
public useRecoveryKey(): void {
74+
this.complete({ outcome: "useRecoveryKey" });
75+
}
76+
77+
public useInteractiveVerification(): void {
78+
this.complete({ outcome: "interactiveVerification" });
79+
}
80+
81+
public cannotConfirm(): void {
82+
this.complete({ outcome: "resetIdentity" });
83+
}
84+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2026 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import { InlineSpinner } from "@vector-im/compound-web";
9+
import { useViewModel } from "@element-hq/web-shared-components";
10+
import type React from "react";
11+
import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key";
12+
import type {
13+
EnablingRecoveryStepViewModel,
14+
EnablingRecoveryStepViewSnapshot,
15+
} from "./EnablingRecoveryStepViewModel";
16+
import type { ScreenProps } from "./screenRegistry.types";
17+
import { SetupScreenLayout, SetupScreenHeader } from "../SetupScreen";
18+
19+
/**
20+
* Screen showing progress while enabling recovery.
21+
* Displays progress messages during backup creation.
22+
*/
23+
export const EnablingRecoveryScreen: React.FC<
24+
ScreenProps<EnablingRecoveryStepViewModel>
25+
> = ({ viewModel }) => {
26+
const { progressMessage, error } = useViewModel(
27+
viewModel,
28+
) as EnablingRecoveryStepViewSnapshot;
29+
30+
return (
31+
<SetupScreenLayout>
32+
<SetupScreenHeader Icon={KeyIcon} title="Setting up recovery" />
33+
34+
<div
35+
style={{
36+
display: "flex",
37+
flexDirection: "column",
38+
alignItems: "center",
39+
gap: "var(--cpd-space-4x)",
40+
}}
41+
>
42+
{error ? (
43+
<div
44+
style={{
45+
padding: "var(--cpd-space-3x)",
46+
backgroundColor:
47+
"var(--cpd-color-bg-critical-subtle)",
48+
borderRadius: "var(--cpd-radius-pill-effect)",
49+
color: "var(--cpd-color-text-critical-primary)",
50+
textAlign: "center",
51+
}}
52+
>
53+
{error}
54+
</div>
55+
) : (
56+
<>
57+
<InlineSpinner />
58+
<p
59+
style={{
60+
margin: 0,
61+
color: "var(--cpd-color-text-secondary)",
62+
textAlign: "center",
63+
}}
64+
>
65+
{progressMessage}
66+
</p>
67+
</>
68+
)}
69+
</div>
70+
</SetupScreenLayout>
71+
);
72+
};

0 commit comments

Comments
 (0)