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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface ChooseSignupMethodProps {
isLoading: boolean;
validation: ReturnType<typeof useUsernameValidation>;
onSubmit: (authenticationMode?: AuthOption, password?: string) => void;
onStorageAccessRequest: () => Promise<void>;
authOptions: AuthOption[];
isOpen?: boolean;
}
Expand All @@ -18,6 +19,7 @@ export function ChooseSignupMethodForm({
isLoading,
validation,
onSubmit,
onStorageAccessRequest,
authOptions,
isOpen = true,
}: ChooseSignupMethodProps) {
Expand Down Expand Up @@ -123,7 +125,10 @@ export function ChooseSignupMethodForm({
setSelectedAuth(option);
} else {
setSelectedAuth(option);
onSubmit(option);
void (async () => {
await onStorageAccessRequest();
onSubmit(option);
})();
}
}
break;
Expand All @@ -134,7 +139,15 @@ export function ChooseSignupMethodForm({
break;
}
},
[isOpen, showPasswordInput, isLoading, options, highlightedIndex, onSubmit],
[
isOpen,
showPasswordInput,
isLoading,
options,
highlightedIndex,
onSubmit,
onStorageAccessRequest,
],
);

useEffect(() => {
Expand Down Expand Up @@ -168,12 +181,18 @@ export function ChooseSignupMethodForm({
setSelectedAuth(option);
} else {
setSelectedAuth(option);
onSubmit(option);
void (async () => {
await onStorageAccessRequest();
onSubmit(option);
})();
}
};

const handlePasswordSubmit = (password: string) => {
onSubmit("password", password);
void (async () => {
await onStorageAccessRequest();
onSubmit("password", password);
})();
};

const handlePasswordCancel = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const Default: Story = {
onUsernameChange: () => {},
onUsernameFocus: () => {},
onUsernameClear: () => {},
onStorageAccessRequest: async () => {},
setChangeWallet: () => {},
onSubmit: () => {},
},
Expand Down Expand Up @@ -59,6 +60,7 @@ export const WithLightMode: Story = {
onUsernameChange: () => {},
onUsernameFocus: () => {},
onUsernameClear: () => {},
onStorageAccessRequest: async () => {},
setChangeWallet: () => {},
onSubmit: () => {},
},
Expand All @@ -85,6 +87,7 @@ export const WithTheme: Story = {
onUsernameChange: () => {},
onUsernameFocus: () => {},
onUsernameClear: () => {},
onStorageAccessRequest: async () => {},
setChangeWallet: () => {},
onSubmit: () => {},
},
Expand All @@ -110,6 +113,7 @@ export const WithTimeoutError: Story = {
onUsernameChange: () => {},
onUsernameFocus: () => {},
onUsernameClear: () => {},
onStorageAccessRequest: async () => {},
setChangeWallet: () => {},
onSubmit: () => {},
},
Expand All @@ -134,6 +138,7 @@ export const WithValidationError: Story = {
onUsernameChange: () => {},
onUsernameFocus: () => {},
onUsernameClear: () => {},
onStorageAccessRequest: async () => {},
setChangeWallet: () => {},
onSubmit: () => {},
},
Expand All @@ -159,6 +164,7 @@ export const WithGenericError: Story = {
onUsernameChange: () => {},
onUsernameFocus: () => {},
onUsernameClear: () => {},
onStorageAccessRequest: async () => {},
setChangeWallet: () => {},
onSubmit: () => {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const mockUseCreateController = vi.fn();
const mockUseUsernameValidation = vi.fn();
const mockUseControllerTheme = vi.fn();
const mockUseWallets = vi.fn().mockReturnValue({ wallets: [] });
const mockRequestStorageAccess = vi.fn().mockResolvedValue(true);
const mockIsIframe = vi.fn();

// Mock the ResizeObserver
const ResizeObserverMock = vi.fn(() => ({
Expand Down Expand Up @@ -76,13 +78,26 @@ vi.mock("./useCreateController", () => ({
vi.mock("@/hooks/debounce", () => ({
useDebounce: <T,>(value: T) => ({ debouncedValue: value }),
}));
vi.mock("@/utils/connection/storage-access", () => ({
requestStorageAccess: () => mockRequestStorageAccess(),
}));
vi.mock("@cartridge/ui/utils", async () => {
const actual = await vi.importActual<typeof import("@cartridge/ui/utils")>(
"@cartridge/ui/utils",
);
return {
...actual,
isIframe: () => mockIsIframe(),
};
});
describe("CreateController", () => {
const defaultProps = {
isSlot: false,
error: undefined,
};
beforeEach(() => {
vi.clearAllMocks();
mockIsIframe.mockReturnValue(false);
// Set default mock returns
mockUseCreateController.mockReturnValue({
isLoading: false,
Expand Down Expand Up @@ -185,6 +200,51 @@ describe("CreateController", () => {
});
});

it("requests storage access on submit in iframe", async () => {
mockIsIframe.mockReturnValue(true);
const handleSubmit = vi.fn().mockResolvedValue(undefined);
const setAuthenticationStep = vi.fn();
mockUseCreateController.mockReturnValue({
isLoading: false,
error: undefined,
setError: vi.fn(),
handleSubmit,
authenticationStep: AuthenticationStep.FillForm,
setAuthenticationStep,
waitingForConfirmation: false,
changeWallet: false,
setChangeWallet: vi.fn(),
overlay: null,
setOverlay: vi.fn(),
signupOptions: ["webauthn"],
authMethod: undefined,
setAuthMethod: vi.fn(),
});
renderComponent();
const input = screen.getByPlaceholderText("Username");
fireEvent.change(input, { target: { value: "validuser" } });

// Ensure dropdown is closed by blurring input
fireEvent.blur(input);

// Wait for validation to be applied
await waitFor(() => {
const submitButton = screen.getByTestId("submit-button");
expect(submitButton).not.toBeDisabled();
});

// Submit form
const submitButton = screen.getByTestId("submit-button");
const form = submitButton.closest("form");
if (form) {
fireEvent.submit(form);
}

await waitFor(() => {
expect(mockRequestStorageAccess).toHaveBeenCalled();
});
});

it("shows loading state during submission", async () => {
mockUseCreateController.mockReturnValue({
isLoading: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { usePostHog } from "@/components/provider/posthog";
import { useControllerTheme } from "@/hooks/connection";
import { useDebounce } from "@/hooks/debounce";
import { allUseSameAuth } from "@/utils/controller";
import { requestStorageAccess } from "@/utils/connection/storage-access";
import { AuthOption, AuthOptions } from "@cartridge/controller";
import {
CartridgeLogo,
Expand All @@ -31,6 +32,7 @@ import {
} from "@/hooks/viewport";
import { useDevice } from "@/hooks/device";
import { AccountSearchResult } from "@/hooks/account";
import { isIframe } from "@cartridge/ui/utils";

interface CreateControllerViewProps {
theme: VerifiableControllerTheme;
Expand All @@ -45,6 +47,7 @@ interface CreateControllerViewProps {
onUsernameFocus: () => void;
onUsernameClear: () => void;
onSubmit: (authenticationMode?: AuthOption) => void;
onStorageAccessRequest: () => Promise<void>;
onKeyDown: (e: React.KeyboardEvent) => void;
isSlot?: boolean;
authenticationStep: AuthenticationStep;
Expand Down Expand Up @@ -81,6 +84,7 @@ function CreateControllerForm({
onUsernameChange,
onUsernameFocus,
onUsernameClear,
onStorageAccessRequest,
onKeyDown,
onSubmit,
waitingForConfirmation,
Expand Down Expand Up @@ -195,13 +199,15 @@ function CreateControllerForm({
height: layoutHeight,
}}
ref={layoutRef}
onSubmit={(e) => {
onSubmit={async (e) => {
e.preventDefault();
// Don't submit if dropdown is open
if (isDropdownOpen) {
return;
}

await onStorageAccessRequest();

if (keyboardIsOpen) {
// If keyboard is open, mark for pending submit after it closes
setPendingSubmitAfterKeyboardClose(true);
Expand Down Expand Up @@ -295,6 +301,7 @@ export function CreateControllerView({
onUsernameFocus,
onUsernameClear,
onSubmit,
onStorageAccessRequest,
onKeyDown,
authenticationStep,
setAuthenticationStep,
Expand Down Expand Up @@ -342,6 +349,7 @@ export function CreateControllerView({
onUsernameChange={onUsernameChange}
onUsernameFocus={onUsernameFocus}
onUsernameClear={onUsernameClear}
onStorageAccessRequest={onStorageAccessRequest}
onSubmit={onSubmit}
onKeyDown={onKeyDown}
waitingForConfirmation={waitingForConfirmation}
Expand All @@ -359,6 +367,7 @@ export function CreateControllerView({
isLoading={isLoading}
validation={validation}
onSubmit={onSubmit}
onStorageAccessRequest={onStorageAccessRequest}
authOptions={authOptions}
isOpen={authenticationStep === AuthenticationStep.ChooseMethod}
/>
Expand Down Expand Up @@ -495,6 +504,21 @@ export function CreateController({
],
);

const handleStorageAccessRequest = useCallback(async () => {
if (!isIframe()) {
return;
}

try {
await requestStorageAccess();
} catch (error) {
console.error(
"[CreateController] Storage access request failed:",
error,
);
}
}, []);

useEffect(() => {
if (
pendingSubmitRef.current &&
Expand Down Expand Up @@ -587,8 +611,6 @@ export function CreateController({
canSubmit,
authenticationStep,
isDropdownOpen,
setAuthMethod,
handleFormSubmit,
]);

// Reset authMethod and pendingSubmit when sheet is closed
Expand All @@ -612,6 +634,7 @@ export function CreateController({
onUsernameFocus={handleUsernameFocus}
onUsernameClear={handleUsernameClear}
onSubmit={handleFormSubmit}
onStorageAccessRequest={handleStorageAccessRequest}
onKeyDown={handleKeyDown}
authenticationStep={authenticationStep}
setAuthenticationStep={setAuthenticationStep}
Expand Down
Loading