Skip to content
Merged
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
@@ -0,0 +1,11 @@
import Foundation

/// Web parity: `apps/calendar-web/src/auth/providers/provider-copy.util.ts`.
public enum AccountChoiceCopy {
public static let localDeviceOnlyLabel = "On this device only"
public static let localDeviceOnlySubtitle =
"Events stay in this browser until you connect an account."
public static let connectCalendarAccountLabel = "Connect a calendar account"
public static let connectCalendarAccountHelper =
"Pick Google, Microsoft, or Apple. Or keep using this device only from the calendar list."
}
17 changes: 17 additions & 0 deletions apps/calendar-macos/CompassKitTests/AccountChoiceCopyTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import XCTest
@testable import CompassKit

final class AccountChoiceCopyTests: XCTestCase {
func testAccountChoiceCopyMatchesWeb() {
XCTAssertEqual(AccountChoiceCopy.localDeviceOnlyLabel, "On this device only")
XCTAssertEqual(
AccountChoiceCopy.localDeviceOnlySubtitle,
"Events stay in this browser until you connect an account.")
XCTAssertEqual(
AccountChoiceCopy.connectCalendarAccountLabel,
"Connect a calendar account")
XCTAssertEqual(
AccountChoiceCopy.connectCalendarAccountHelper,
"Pick Google, Microsoft, or Apple. Or keep using this device only from the calendar list.")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,21 @@ struct WelcomeModalView: View {
.foregroundStyle(theme.textMutedColor)
}
VStack(spacing: 12) {
primaryButton(title: "Explore without an account", hint: "S") { model.welcomeExplore() }
primaryButton(title: AccountChoiceCopy.localDeviceOnlyLabel, hint: "S") {
model.welcomeExplore()
}
Text(AccountChoiceCopy.localDeviceOnlySubtitle)
.font(.rubik(14))
.foregroundStyle(theme.textMutedColor)
VStack(spacing: 4) {
Text(AccountChoiceCopy.connectCalendarAccountLabel)
.font(.rubik(14, weight: .semibold))
.foregroundStyle(theme.textColor)
Text(AccountChoiceCopy.connectCalendarAccountHelper)
.font(.rubik(14))
.foregroundStyle(theme.textMutedColor)
}
.frame(maxWidth: .infinity)
Text("Ready to keep it? Start your free 7-day trial.")
.font(.rubik(14))
.foregroundStyle(theme.textMutedColor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ struct SettingsAccountsSectionView: View {

private var connectButtons: some View {
VStack(alignment: .leading, spacing: 6) {
Text(AccountChoiceCopy.connectCalendarAccountLabel)
.font(.rubik(13, weight: .semibold))
.foregroundStyle(theme.textColor)
Text(AccountChoiceCopy.connectCalendarAccountHelper)
.font(.rubik(11))
.foregroundStyle(theme.textMutedColor)
Button("Connect Google") {
Task { await syncStore.connect(provider: .google) }
}
Expand Down
2 changes: 1 addition & 1 deletion apps/calendar-web/src/auth/posthog/signup-funnel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export async function trackSignupStartedAtClick(
trackSignupStarted(source, anon_events_created);
}

/** Anonymous visitor chose local-only mode (e.g. sidebar "This browser"). */
/** Anonymous visitor chose local-only mode (e.g. sidebar "On this device only"). */
export function trackLocalModeSelected(source: LocalModeSource): void {
track("local_mode_selected", { source });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,13 @@ describe("ConnectProviderChooser", () => {
const user = userEvent.setup();
available = ["google", "microsoft", "apple"];

renderChooser(<ConnectProviderChooser idleLabel="Add account" />);
renderChooser(
<ConnectProviderChooser idleLabel="Connect a calendar account" />,
);

await user.click(screen.getByRole("button", { name: /Add account/ }));
await user.click(
screen.getByRole("button", { name: /Connect a calendar account/ }),
);

for (const name of ["Google", "Microsoft"] as const) {
const item = screen.getByRole("menuitem", { name });
Expand Down
2 changes: 1 addition & 1 deletion apps/calendar-web/src/auth/providers/connect.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export type UseConnectGoogleOptions = {
connection?: SyncConnectionSummary | null;
/**
* Always start a new-account OAuth round-trip (`{}`), even when some other
* account is `RECONNECT_REQUIRED`. Settings "Add account" must never bind
* account is `RECONNECT_REQUIRED`. Settings "Connect a calendar account" must never bind
* to a reconnect.
*/
newAccount?: boolean;
Expand Down
15 changes: 15 additions & 0 deletions apps/calendar-web/src/auth/providers/provider-copy.util.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { ArrowsClockwiseIcon } from "@phosphor-icons/react/dist/csr/ArrowsClockwise";
import {
bookingConnectPromptCopy,
CONNECT_CALENDAR_ACCOUNT_HELPER,
CONNECT_CALENDAR_ACCOUNT_LABEL,
CONNECT_CALENDAR_LABEL,
calendarProductName,
connectionProvider,
emptyCalendarsCopy,
LOCAL_DEVICE_ONLY_LABEL,
LOCAL_DEVICE_ONLY_SUBTITLE,
openingProviderCopy,
RECONNECT_BANNER_MESSAGE,
RECONNECT_CALENDAR_LABEL,
Expand Down Expand Up @@ -112,4 +116,15 @@ describe("provider copy", () => {
);
expect(reconnect?.label).toBe("Reconnect Microsoft Calendar");
});

it("keeps Tyler-approved account-choice copy byte-identical", () => {
expect(LOCAL_DEVICE_ONLY_LABEL).toBe("On this device only");
expect(LOCAL_DEVICE_ONLY_SUBTITLE).toBe(
"Events stay in this browser until you connect an account.",
);
expect(CONNECT_CALENDAR_ACCOUNT_LABEL).toBe("Connect a calendar account");
expect(CONNECT_CALENDAR_ACCOUNT_HELPER).toBe(
"Pick Google, Microsoft, or Apple. Or keep using this device only from the calendar list.",
);
});
});
12 changes: 12 additions & 0 deletions apps/calendar-web/src/auth/providers/provider-copy.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ export const CONNECT_CALENDAR_REASSURANCE =
export const CALENDAR_HOST_EXPLAINER =
"If you view your calendar in Apple Calendar, it may still be hosted by Google or Microsoft.";

/** Anonymous local-only calendar row and onboarding device-only path. */
export const LOCAL_DEVICE_ONLY_LABEL = "On this device only";

export const LOCAL_DEVICE_ONLY_SUBTITLE =
"Events stay in this browser until you connect an account.";

/** Settings and sign-in add-account chooser trigger. */
export const CONNECT_CALENDAR_ACCOUNT_LABEL = "Connect a calendar account";

export const CONNECT_CALENDAR_ACCOUNT_HELPER =
"Pick Google, Microsoft, or Apple. Or keep using this device only from the calendar list.";

/** Admin-consent required (Microsoft Graph `consentRequired`). */
export const CONSENT_REQUIRED_COPY =
"Your organization's admin has to approve Compass before this account can connect.";
Expand Down
12 changes: 12 additions & 0 deletions apps/calendar-web/src/components/AuthModal/AuthModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import {
import { type ProviderKind } from "@core/types/sync/identity.contracts";
import { trackSignupStep } from "@web/auth/posthog/signup-funnel";
import { consumeGoogleAuthNeedsConsentRetry } from "@web/auth/providers/authorization/provider-authorization.storage";
import {
CONNECT_CALENDAR_ACCOUNT_HELPER,
CONNECT_CALENDAR_ACCOUNT_LABEL,
} from "@web/auth/providers/provider-copy.util";
import { signInProviderForShortcutLetter } from "@web/auth/providers/sign-in-provider.util";
import { useSignInProviders } from "@web/auth/providers/useSignInProviders";
import { isEditableKeyboardTarget } from "@web/common/utils/form/form.util";
Expand Down Expand Up @@ -311,6 +315,14 @@ export const AuthModal: FC = () => {
{/* Google Sign In - at bottom */}
{showProviderSignIn ? (
<>
<div className="flex w-full flex-col gap-1">
<p className="font-semibold text-sm text-text">
{CONNECT_CALENDAR_ACCOUNT_LABEL}
</p>
<p className="text-text-muted text-xs">
{CONNECT_CALENDAR_ACCOUNT_HELPER}
</p>
</div>
<div className="flex items-center gap-3">
<div className="h-px flex-1 bg-border" />
<span className="text-sm text-text-muted">or</span>
Expand Down
11 changes: 8 additions & 3 deletions apps/calendar-web/src/components/Settings/AccountsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { type FC } from "react";
import { type Calendar } from "@core/types/calendar.contracts";
import { type SyncConnectionSummary } from "@core/types/user.types";
import { ConnectProviderChooser } from "@web/auth/providers/ConnectProviderChooser";
import { CALENDAR_HOST_EXPLAINER } from "@web/auth/providers/provider-copy.util";
import {
CONNECT_CALENDAR_ACCOUNT_HELPER,
CONNECT_CALENDAR_ACCOUNT_LABEL,
} from "@web/auth/providers/provider-copy.util";
import { useDisconnectGoogleAccount } from "@web/auth/providers/useDisconnectAccount";
import {
accountKey,
Expand Down Expand Up @@ -61,15 +64,17 @@ export const AccountsSection: FC<AccountsSectionProps> = ({

<OverlayPanelActions align="start">
<ConnectProviderChooser
idleLabel="Add account"
idleLabel={CONNECT_CALENDAR_ACCOUNT_LABEL}
newAccount
showShortcut={showShortcuts}
shortcut="A"
shortcutAttrs={settingsShortcutAttrs("add-account")}
variant="overlay-primary"
/>
</OverlayPanelActions>
<p className="text-text-muted text-xs">{CALENDAR_HOST_EXPLAINER}</p>
<p className="text-text-muted text-xs">
{CONNECT_CALENDAR_ACCOUNT_HELPER}
</p>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { useAccountHeaderStatus } from "./useAccountHeaderStatus";
* and five accounts render the same shape, so the two can't drift apart the
* way a separate single-account header did.
*
* Adding/disconnecting accounts lives in the command palette's "Add account" /
* Adding/disconnecting accounts lives in the command palette's "Connect a calendar account" /
* "Show accounts" items - not a permanent row or icon here, which stayed noisy
* for accounts with many subcalendars.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ describe("AnonymousCalendarRow", () => {
isActive: true,
};

it("renders the row with 'This browser' label and trial sync copy", () => {
it("renders the row with the local device label and trial sync copy", () => {
render(<AnonymousCalendarRow calendar={mockCalendar} />);

expect(screen.getByText("This browser")).toBeInTheDocument();
expect(screen.getByText("On this device only")).toBeInTheDocument();
expect(ANONYMOUS_CALENDAR_ROW_TRIAL_MESSAGE).toBe(
"Start your free trial to sync",
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import {
trackLocalModeSelected,
trackSignupStartedAtClick,
} from "@web/auth/posthog/signup-funnel";
import {
LOCAL_DEVICE_ONLY_LABEL,
LOCAL_DEVICE_ONLY_SUBTITLE,
} from "@web/auth/providers/provider-copy.util";
import { useAuthModal } from "@web/components/AuthModal/hooks/useAuthModal";
import {
Tooltip,
Expand Down Expand Up @@ -69,10 +73,13 @@ export const AnonymousCalendarRow: FC<AnonymousCalendarRowProps> = ({
borderColor: calendar.backgroundColor,
}}
/>
<span className="min-w-0 flex-1 truncate">This browser</span>
<span className="min-w-0 flex-1 truncate">
{LOCAL_DEVICE_ONLY_LABEL}
</span>
</button>
</TooltipTrigger>
<TooltipContent className="flex max-w-55 flex-col gap-1.5">
<span>{LOCAL_DEVICE_ONLY_SUBTITLE}</span>
<span>{ANONYMOUS_CALENDAR_ROW_TRIAL_MESSAGE}</span>
<button
className={TOOLTIP_ACTION_BUTTON_CLASSNAME}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,9 @@ describe("CalendarList", () => {
expect(screen.queryByText("ahab@pequod.com")).not.toBeInTheDocument();
});

it("shows a single row labeled 'This browser' for anonymous users, without repeating a heading", () => {
it("shows a single row labeled 'On this device only' for anonymous users, without repeating a heading", () => {
// Anonymous users get the local calendar rendered as AnonymousCalendarRow,
// which combines the account label ("This browser") and sign-up affordance
// which combines the account label ("On this device only") and sign-up affordance
// into a single row, without a separate heading. No visibility toggle.
const local = makeCalendar({
name: "Compass",
Expand All @@ -223,11 +223,11 @@ describe("CalendarList", () => {
mockUserEmail = undefined;
renderCalendarList([local], { authenticated: false });

expect(screen.getByText("This browser")).toBeInTheDocument();
expect(screen.getByText("On this device only")).toBeInTheDocument();
expect(screen.queryByText("primary")).not.toBeInTheDocument();
expect(screen.queryByText("Compass")).not.toBeInTheDocument();
expect(
screen.queryByRole("heading", { name: "This browser" }),
screen.queryByRole("heading", { name: "On this device only" }),
).not.toBeInTheDocument();
expect(
screen.getByRole("button", {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const goToChooseScreen = async (user: ReturnType<typeof userEvent.setup>) => {
await user.keyboard("{Enter}");
await user.keyboard("{Enter}");
expect(
screen.getByRole("button", { name: "Explore without an account" }),
screen.getByRole("button", { name: "On this device only" }),
).toBeTruthy();
};

Expand Down Expand Up @@ -166,7 +166,7 @@ describe("WelcomeModal", () => {
render(<WelcomeModal />);
await goToChooseScreen(user);
const explore = screen.getByRole("button", {
name: "Explore without an account",
name: "On this device only",
});
expect(explore.getAttribute("aria-busy")).toBe("false");

Expand Down Expand Up @@ -249,7 +249,7 @@ describe("WelcomeModal", () => {
for (const name of [
"Sign up",
"Continue with Google",
"Explore without an account",
"On this device only",
"Who is Compass for?",
"Back",
]) {
Expand Down Expand Up @@ -284,7 +284,7 @@ describe("WelcomeModal", () => {
await user.click(screen.getByRole("button", { name: "Next" }));

await user.click(
screen.getByRole("button", { name: "Explore without an account" }),
screen.getByRole("button", { name: "On this device only" }),
);
await waitFor(() => {
expect(
Expand Down Expand Up @@ -357,7 +357,7 @@ describe("WelcomeModal", () => {
"https://github.com/KeepSoftwareSimple/compass-calendar",
);
expect(
screen.getByRole("button", { name: "Explore without an account" }),
screen.getByRole("button", { name: "On this device only" }),
).toHaveFocus();

await user.keyboard("{Escape}");
Expand Down Expand Up @@ -510,7 +510,7 @@ describe("WelcomeModal", () => {
["Sign up", "U"],
["Log in", "i"],
["Back", "Esc"],
["Explore without an account", "S"],
["On this device only", "S"],
] as const) {
const hint = within(screen.getByRole("button", { name })).getByText(key);
expect(hint.className).not.toMatch(/opacity-0/);
Expand Down Expand Up @@ -643,7 +643,7 @@ describe("WelcomeModal", () => {
await goToChooseScreen(user);

const explore = screen.getByRole("button", {
name: "Explore without an account",
name: "On this device only",
});
expect(explore).toHaveFocus();

Expand Down Expand Up @@ -694,7 +694,7 @@ describe("WelcomeModal", () => {
await goToChooseScreen(user);

const explore = screen.getByRole("button", {
name: "Explore without an account",
name: "On this device only",
});
expect(explore).toHaveClass("w-full", "h-10", "c-button-primary");
expect(explore).toHaveClass("c-button-elevated");
Expand Down
21 changes: 19 additions & 2 deletions apps/calendar-web/src/components/WelcomeModal/WelcomeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import {
trackSignupStep,
} from "@web/auth/posthog/signup-funnel";
import { track } from "@web/auth/posthog/track";
import { CONNECT_CALENDAR_LABEL } from "@web/auth/providers/provider-copy.util";
import {
CONNECT_CALENDAR_ACCOUNT_HELPER,
CONNECT_CALENDAR_ACCOUNT_LABEL,
CONNECT_CALENDAR_LABEL,
LOCAL_DEVICE_ONLY_LABEL,
LOCAL_DEVICE_ONLY_SUBTITLE,
} from "@web/auth/providers/provider-copy.util";
import { signInProviderForShortcutLetter } from "@web/auth/providers/sign-in-provider.util";
import { useIsProviderAvailable } from "@web/auth/providers/useIsProviderAvailable";
import { useSignInProviders } from "@web/auth/providers/useSignInProviders";
Expand Down Expand Up @@ -397,9 +403,20 @@ export function WelcomeModal() {
className={PRIMARY_CTA_CLASS}
{...navigationBusyProps}
>
Explore without an account
{LOCAL_DEVICE_ONLY_LABEL}
<ShortcutHint className="ml-2">S</ShortcutHint>
</button>
<p className="text-center text-sm text-text-muted">
{LOCAL_DEVICE_ONLY_SUBTITLE}
</p>
<div className="flex w-full flex-col gap-1 text-center">
<p className="font-semibold text-sm text-text">
{CONNECT_CALENDAR_ACCOUNT_LABEL}
</p>
<p className="text-sm text-text-muted">
{CONNECT_CALENDAR_ACCOUNT_HELPER}
</p>
</div>
<p className="text-center text-sm text-text-muted">
Ready to keep it? Start your free 7-day trial.
</p>
Expand Down
Loading
Loading