Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion desktop/src/features/onboarding/ui/CommunityOnboardingFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
parseEmojiAvatarDataUrl,
ProfileAvatarEditor,
} from "@/features/profile/ui/ProfileAvatarEditor";
import { updateProfile } from "@/shared/api/tauriProfiles";
import { getProfile, updateProfile } from "@/shared/api/tauriProfiles";
import { getIdentity, importIdentity } from "@/shared/api/tauriIdentity";
import { listPersonas } from "@/shared/api/tauriPersonas";
import { relayClient } from "@/shared/api/relayClient";
Expand Down Expand Up @@ -151,6 +151,7 @@ export function CommunityOnboardingFlow({
[],
);
const [isPending, setIsPending] = React.useState(false);
const checkedProfileTransactionRef = React.useRef<string | null>(null);
const [starterChannelFailureCount, setStarterChannelFailureCount] =
React.useState(0);
const [deniedPubkey, setDeniedPubkey] = React.useState("");
Expand Down Expand Up @@ -274,6 +275,22 @@ export function CommunityOnboardingFlow({
}, [isPending, update]);

const isProfileStage = transaction?.stage === "profile";
React.useEffect(() => {
if (!isProfileStage || !transaction) return;
if (checkedProfileTransactionRef.current === transaction.id) return;

checkedProfileTransactionRef.current = transaction.id;
void getProfile()
.then((profile) => {
if (profile.hasProfileEvent) {
update({ stage: "team-intro", error: undefined }, transaction.id);
}
})
.catch(() => {
// Discovery is best-effort. Staying on the profile step preserves the
// existing path when the relay cannot answer the lookup.
});
}, [isProfileStage, transaction, update]);
const isTeamStage =
transaction?.stage === "team-intro" ||
transaction?.stage === "finalizing" ||
Expand Down
9 changes: 9 additions & 0 deletions desktop/src/testing/e2eBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ type E2eConfig = {
channelWindowDelayMs?: number;
profileReadDelayMs?: number;
profileReadError?: string;
/** Override whether get_profile reports a real kind:0 event. */
profileHasEvent?: boolean;
profileUpdateError?: string;
profileUpdateErrors?: string[];
searchProfiles?: MockSearchProfileSeed[];
Expand Down Expand Up @@ -5088,6 +5090,13 @@ async function handleGetChannels(config: E2eConfig | undefined) {

async function handleGetProfile(config: E2eConfig | undefined) {
const identity = getIdentity(config);
const forcedHasProfileEvent = config?.mock?.profileHasEvent;
if (forcedHasProfileEvent !== undefined) {
return {
...cloneProfile(ensureMockProfile(config)),
has_profile_event: forcedHasProfileEvent,
};
}
if (!identity) {
const profileReadDelayMs = config?.mock?.profileReadDelayMs ?? 0;
if (profileReadDelayMs > 0) {
Expand Down
60 changes: 60 additions & 0 deletions desktop/tests/e2e/onboarding.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,66 @@ test("first-community direct join reaches profile", async ({ page }) => {
.toEqual({ communityCount: 1, transactionMatchesOnlyCommunity: true });
});

test("community onboarding reuses an existing relay profile", async ({
page,
}) => {
await seedActiveIdentity(page, BLANK_TYLER_IDENTITY);
await page.addInitScript(
({ pubkey, transactionStorageKey }) => {
window.localStorage.setItem(
`buzz-machine-onboarding-complete.v2:${pubkey}`,
"true",
);
const timestamp = new Date().toISOString();
window.localStorage.setItem(
transactionStorageKey,
JSON.stringify({
id: "txn-existing-profile",
source: "add-community",
stage: "profile",
relayUrl: "wss://onboarding.communities.buzz.xyz",
communityName: "Onboarding",
communityId: "e2e-default-community",
createdAt: timestamp,
updatedAt: timestamp,
}),
);
},
{
pubkey: BLANK_TYLER_IDENTITY.pubkey,
transactionStorageKey: COMMUNITY_ONBOARDING_TRANSACTION_STORAGE_KEY,
},
);
await installMockBridge(
page,
{ profileHasEvent: true },
{
relayWsUrl: "wss://onboarding.communities.buzz.xyz",
skipOnboardingSeed: true,
},
);
await page.goto("/");

await expect
.poll(() =>
page.evaluate(
() =>
(
window as Window & { __BUZZ_E2E_COMMANDS__?: string[] }
).__BUZZ_E2E_COMMANDS__?.filter(
(command) => command === "get_profile",
).length ?? 0,
),
)
.toBeGreaterThan(0);
await expect(
page.getByRole("heading", { name: "Meet your starter team" }),
).toBeVisible();
await expect(
page.getByRole("heading", { name: "Build your profile" }),
).toHaveCount(0);
});

test("first-community direct join cancel returns to request access", async ({
page,
}) => {
Expand Down
2 changes: 2 additions & 0 deletions desktop/tests/helpers/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ type MockBridgeOptions = {
channelWindowDelayMs?: number;
profileReadDelayMs?: number;
profileReadError?: string;
/** Override whether get_profile reports a real kind:0 event. */
profileHasEvent?: boolean;
profileUpdateError?: string;
profileUpdateErrors?: string[];
searchProfiles?: MockSearchProfileSeed[];
Expand Down
Loading