The button was rendered inside the Log In tab's form only, so a visitor on Create Account saw no social option at all. Reported from a local run. The mistake came from following the passkey button too closely. A passkey belongs only on Log In, and correctly so: you cannot register an account with one, since registration requires an account to register it against. Google is the opposite case. Creating an account is precisely what a new customer reaches for it to do, so leaving it off the sign-up tab hid the feature from the people it helps most — and hid it on the tab the modal opens on by default. The label differs by tab and nothing else does. One endpoint serves both: it signs in a known identity, links a verified address, or creates an account, and the customer neither knows nor cares which will happen. So the wording matches what they came to that tab to do rather than what the server ends up doing. Both spellings are given in Google's identity guidelines alongside the mark. The two consent checkboxes above it are deliberately not carried across. Google takes the customer off this site entirely, and a tick that survived that round trip would be a consent recorded from a form nobody submitted. They are asked again, with the same wording and through the same endpoints, on the step they land on afterwards — which is what #342 built that step for. The end-to-end test asserts absence, like the one beside it, because local and QA have no credentials and absence is the behaviour that actually runs there. Verified: frontend tsc, lint and build clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
80 lines
2.9 KiB
TypeScript
80 lines
2.9 KiB
TypeScript
import Button from 'antd/es/button';
|
|
|
|
/**
|
|
* Google's own mark, inlined as SVG (#345).
|
|
*
|
|
* Their identity guidelines specify the four colours and the geometry, and a
|
|
* hand-drawn approximation of somebody else's trademark is a compliance problem
|
|
* rather than a style choice. These are the published values.
|
|
*
|
|
* Inlined rather than fetched, for the reason every other asset in this app is:
|
|
* a second origin is a second thing that can be down, blocked, or slow, and
|
|
* this one sits on the sign-in path.
|
|
*/
|
|
function GoogleMark() {
|
|
return (
|
|
<svg width="18" height="18" viewBox="0 0 18 18" aria-hidden="true" focusable="false">
|
|
<path
|
|
fill="#4285F4"
|
|
d="M17.64 9.2c0-.64-.06-1.25-.16-1.84H9v3.48h4.84a4.14 4.14 0 0 1-1.8 2.72v2.26h2.92c1.7-1.57 2.68-3.88 2.68-6.62z"
|
|
/>
|
|
<path
|
|
fill="#34A853"
|
|
d="M9 18c2.43 0 4.47-.8 5.96-2.18l-2.92-2.26c-.8.54-1.84.86-3.04.86-2.34 0-4.32-1.58-5.03-3.7H.96v2.34A9 9 0 0 0 9 18z"
|
|
/>
|
|
<path
|
|
fill="#FBBC05"
|
|
d="M3.97 10.72a5.4 5.4 0 0 1 0-3.44V4.94H.96a9 9 0 0 0 0 8.12l3.01-2.34z"
|
|
/>
|
|
<path
|
|
fill="#EA4335"
|
|
d="M9 3.58c1.32 0 2.5.45 3.44 1.35l2.58-2.59C13.46.9 11.43 0 9 0A9 9 0 0 0 .96 4.94l3.01 2.34C4.68 5.16 6.66 3.58 9 3.58z"
|
|
/>
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
type Props = Readonly<{
|
|
/** Where to send the customer back to. Validated again on the server. */
|
|
returnTo: string;
|
|
/**
|
|
* Which tab this sits on, which changes only the wording.
|
|
*
|
|
* One endpoint serves both: it signs in a known identity, links a verified
|
|
* address, or creates an account. The customer does not know or care which
|
|
* of those will happen, so the label matches what they came to the tab to
|
|
* do rather than what the server ends up doing.
|
|
*
|
|
* Both spellings are in Google identity guidelines alongside the mark.
|
|
*/
|
|
intent?: 'sign-in' | 'sign-up';
|
|
}>;
|
|
|
|
/**
|
|
* Signing in with Google (#345).
|
|
*
|
|
* A navigation rather than a fetch, which is what makes this different from
|
|
* every other control on the auth form. The flow leaves this application
|
|
* entirely, so there is no promise to await and no error to catch here — the
|
|
* server's callback decides what happens and redirects accordingly.
|
|
*
|
|
* `returnTo` is sent as a query parameter and **validated on the server**, not
|
|
* here. It has to be, since anyone can type the URL, and doing it in one place
|
|
* beats doing it in two languages. See `google/returnTo.ts`.
|
|
*/
|
|
export default function GoogleSignInButton({ returnTo, intent = 'sign-in' }: Props) {
|
|
return (
|
|
<Button
|
|
block
|
|
icon={<GoogleMark />}
|
|
onClick={() => {
|
|
// assign rather than the router: this is a full page departure to
|
|
// another origin, and react-router would try to match it as a route.
|
|
window.location.assign(`/api/auth/google/start?returnTo=${encodeURIComponent(returnTo)}`);
|
|
}}
|
|
>
|
|
{intent === 'sign-up' ? 'Sign up with Google' : 'Sign in with Google'}
|
|
</Button>
|
|
);
|
|
}
|