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
29 changes: 29 additions & 0 deletions .changeset/sign-in-marks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
"seamless-templates": patch
---

feat(kit): mark the generated sign-in screen with Seamless Auth and Seamless Idea

Every generated application is shared with the handful of people it was made
for, and every one of them signs in. That screen is the largest owned surface the
company has, it grows with the fleet, it costs nothing, and it said nothing at
all.

`AuthFrame` now carries two lines under the form:

- **Secured by Seamless Auth**, to `seamlessauth.com`. Most of the people
reading it are about to use passwordless sign in for the first time, and
naming it is the only distribution the auth product gets from the fleet.
- **Made with Seamless Idea**, to `seamlessidea.com`.

Restraint is the whole design. They sit under the sign-in screens rather than
beside them, at footnote size in the muted ink role, centred, with no logo and no
lockup. A generated application has to feel like its owner's, and a banner across
the top would undo the thing that makes personal software worth having. Both
links open in a new tab so that nobody mid-sign-in loses the page, and they carry
`noopener` without `noreferrer`, because the referrer is the only way either
product ever sees that a generated application sent someone.

There is no switch to turn them off. The templates are copied into the owner's
project, so the lines can always be deleted; whether a paid tier removes them is
a decision for the CLI rather than a prop on the kit.
39 changes: 39 additions & 0 deletions shared/react-app/components/kit/AuthFrame.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import AuthFrame from "./AuthFrame";

function renderFrame() {
return render(
<AuthFrame
title="Kitchen table"
pitch="Everything the house is keeping track of, in one place."
points={["Post an entry", "See the month"]}
>
<button type="button">Send me a link</button>
</AuthFrame>,
);
}

describe("AuthFrame", () => {
it("carries both marks, each linked out", () => {
renderFrame();

expect(
screen.getByRole("link", { name: "Secured by Seamless Auth" }),
).toHaveAttribute("href", "https://seamlessauth.com");
expect(
screen.getByRole("link", { name: "Made with Seamless Idea" }),
).toHaveAttribute("href", "https://seamlessidea.com");
});

it("keeps the marks behind the sign-in screens", () => {
renderFrame();

const form = screen.getByRole("button", { name: "Send me a link" });
const mark = screen.getByRole("link", { name: "Secured by Seamless Auth" });

expect(
form.compareDocumentPosition(mark) & Node.DOCUMENT_POSITION_FOLLOWING,
).toBeTruthy();
});
});
37 changes: 33 additions & 4 deletions shared/react-app/components/kit/AuthFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import type { AuthFrameProps } from "./types";
* The signed-out surface: a pitch on one half, the sign-in screens on the other.
*
* `children` is the auth package's own screens. They are rendered once and
* untouched; nothing here adds an input, a button or a link of its own, because
* the form is the point of the page and a second call to action beside it is a
* way of losing people.
* untouched; nothing here adds an input or a button of its own, because the form
* is the point of the page and a second call to action beside it is a way of
* losing people. The two marks under the form are the one exception, and their
* weight is what keeps them from being one.
*
* This page is outside the application shell, so it mounts the backdrop itself.
*/
Expand Down Expand Up @@ -46,7 +47,35 @@ export default function AuthFrame({
</div>

<div className="above-vista flex w-full flex-col justify-center bg-surface px-6 py-16 sm:px-10 lg:w-1/2">
<div className="mx-auto w-full max-w-md">{children}</div>
<div className="mx-auto w-full max-w-md">
{children}

{/* Under the form and at footnote weight, because the application has
to read as the owner's rather than as a demo of ours. It is the
mark a printer leaves on an invitation, not a banner.

`noopener` without `noreferrer`: the referrer is the only way
either product sees that a generated application sent someone. */}
<p className="mt-12 flex flex-wrap justify-center gap-x-3 gap-y-1 text-center text-xs text-ink-muted">
<a
className="transition-colors hover:text-ink"
href="https://seamlessauth.com"
target="_blank"
rel="noopener"
>
Secured by Seamless Auth
</a>
<span aria-hidden>&middot;</span>
<a
className="transition-colors hover:text-ink"
href="https://seamlessidea.com"
target="_blank"
rel="noopener"
>
Made with Seamless Idea
</a>
</p>
</div>
</div>
</div>
);
Expand Down
39 changes: 39 additions & 0 deletions templates/web/react-oauth/src/components/kit/AuthFrame.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import AuthFrame from "./AuthFrame";

function renderFrame() {
return render(
<AuthFrame
title="Kitchen table"
pitch="Everything the house is keeping track of, in one place."
points={["Post an entry", "See the month"]}
>
<button type="button">Send me a link</button>
</AuthFrame>,
);
}

describe("AuthFrame", () => {
it("carries both marks, each linked out", () => {
renderFrame();

expect(
screen.getByRole("link", { name: "Secured by Seamless Auth" }),
).toHaveAttribute("href", "https://seamlessauth.com");
expect(
screen.getByRole("link", { name: "Made with Seamless Idea" }),
).toHaveAttribute("href", "https://seamlessidea.com");
});

it("keeps the marks behind the sign-in screens", () => {
renderFrame();

const form = screen.getByRole("button", { name: "Send me a link" });
const mark = screen.getByRole("link", { name: "Secured by Seamless Auth" });

expect(
form.compareDocumentPosition(mark) & Node.DOCUMENT_POSITION_FOLLOWING,
).toBeTruthy();
});
});
37 changes: 33 additions & 4 deletions templates/web/react-oauth/src/components/kit/AuthFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import type { AuthFrameProps } from "./types";
* The signed-out surface: a pitch on one half, the sign-in screens on the other.
*
* `children` is the auth package's own screens. They are rendered once and
* untouched; nothing here adds an input, a button or a link of its own, because
* the form is the point of the page and a second call to action beside it is a
* way of losing people.
* untouched; nothing here adds an input or a button of its own, because the form
* is the point of the page and a second call to action beside it is a way of
* losing people. The two marks under the form are the one exception, and their
* weight is what keeps them from being one.
*
* This page is outside the application shell, so it mounts the backdrop itself.
*/
Expand Down Expand Up @@ -46,7 +47,35 @@ export default function AuthFrame({
</div>

<div className="above-vista flex w-full flex-col justify-center bg-surface px-6 py-16 sm:px-10 lg:w-1/2">
<div className="mx-auto w-full max-w-md">{children}</div>
<div className="mx-auto w-full max-w-md">
{children}

{/* Under the form and at footnote weight, because the application has
to read as the owner's rather than as a demo of ours. It is the
mark a printer leaves on an invitation, not a banner.

`noopener` without `noreferrer`: the referrer is the only way
either product sees that a generated application sent someone. */}
<p className="mt-12 flex flex-wrap justify-center gap-x-3 gap-y-1 text-center text-xs text-ink-muted">
<a
className="transition-colors hover:text-ink"
href="https://seamlessauth.com"
target="_blank"
rel="noopener"
>
Secured by Seamless Auth
</a>
<span aria-hidden>&middot;</span>
<a
className="transition-colors hover:text-ink"
href="https://seamlessidea.com"
target="_blank"
rel="noopener"
>
Made with Seamless Idea
</a>
</p>
</div>
</div>
</div>
);
Expand Down
39 changes: 39 additions & 0 deletions templates/web/react-vite/src/components/kit/AuthFrame.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import AuthFrame from "./AuthFrame";

function renderFrame() {
return render(
<AuthFrame
title="Kitchen table"
pitch="Everything the house is keeping track of, in one place."
points={["Post an entry", "See the month"]}
>
<button type="button">Send me a link</button>
</AuthFrame>,
);
}

describe("AuthFrame", () => {
it("carries both marks, each linked out", () => {
renderFrame();

expect(
screen.getByRole("link", { name: "Secured by Seamless Auth" }),
).toHaveAttribute("href", "https://seamlessauth.com");
expect(
screen.getByRole("link", { name: "Made with Seamless Idea" }),
).toHaveAttribute("href", "https://seamlessidea.com");
});

it("keeps the marks behind the sign-in screens", () => {
renderFrame();

const form = screen.getByRole("button", { name: "Send me a link" });
const mark = screen.getByRole("link", { name: "Secured by Seamless Auth" });

expect(
form.compareDocumentPosition(mark) & Node.DOCUMENT_POSITION_FOLLOWING,
).toBeTruthy();
});
});
37 changes: 33 additions & 4 deletions templates/web/react-vite/src/components/kit/AuthFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import type { AuthFrameProps } from "./types";
* The signed-out surface: a pitch on one half, the sign-in screens on the other.
*
* `children` is the auth package's own screens. They are rendered once and
* untouched; nothing here adds an input, a button or a link of its own, because
* the form is the point of the page and a second call to action beside it is a
* way of losing people.
* untouched; nothing here adds an input or a button of its own, because the form
* is the point of the page and a second call to action beside it is a way of
* losing people. The two marks under the form are the one exception, and their
* weight is what keeps them from being one.
*
* This page is outside the application shell, so it mounts the backdrop itself.
*/
Expand Down Expand Up @@ -46,7 +47,35 @@ export default function AuthFrame({
</div>

<div className="above-vista flex w-full flex-col justify-center bg-surface px-6 py-16 sm:px-10 lg:w-1/2">
<div className="mx-auto w-full max-w-md">{children}</div>
<div className="mx-auto w-full max-w-md">
{children}

{/* Under the form and at footnote weight, because the application has
to read as the owner's rather than as a demo of ours. It is the
mark a printer leaves on an invitation, not a banner.

`noopener` without `noreferrer`: the referrer is the only way
either product sees that a generated application sent someone. */}
<p className="mt-12 flex flex-wrap justify-center gap-x-3 gap-y-1 text-center text-xs text-ink-muted">
<a
className="transition-colors hover:text-ink"
href="https://seamlessauth.com"
target="_blank"
rel="noopener"
>
Secured by Seamless Auth
</a>
<span aria-hidden>&middot;</span>
<a
className="transition-colors hover:text-ink"
href="https://seamlessidea.com"
target="_blank"
rel="noopener"
>
Made with Seamless Idea
</a>
</p>
</div>
</div>
</div>
);
Expand Down
Loading