···11+import { useState } from "react";
22+import { useUIState } from "src/useUIState";
33+import { useLeafletPublicationData } from "components/PageSWRDataProvider";
44+import { BlockProps, BlockLayout } from "./Block";
55+import { EmailInput } from "components/Subscribe/EmailSubscribe";
66+import { ButtonPrimary } from "components/Buttons";
77+88+// SignupBlock is a static, non-interactive preview of the publication subscribe
99+// form, styled to match SubscribePanel (components/Subscribe/SubscribeButton.tsx).
1010+// It is shown in the editor — the real, working form is rendered on the
1111+// published page (see PostContent.tsx's signup case). No user data is fetched.
1212+export const SignupBlock = (
1313+ props: BlockProps & {
1414+ areYouSure?: boolean;
1515+ setAreYouSure?: (value: boolean) => void;
1616+ },
1717+) => {
1818+ let isSelected = useUIState((s) =>
1919+ s.selectedBlocks.find((b) => b.value === props.entityID),
2020+ );
2121+ let { normalizedPublication } = useLeafletPublicationData();
2222+ let [email, setEmail] = useState("");
2323+2424+ let publicationName = normalizedPublication?.name || "Subscribe";
2525+ let publicationDescription = normalizedPublication?.description;
2626+2727+ return (
2828+ <BlockLayout
2929+ isSelected={!!isSelected}
3030+ areYouSure={props.areYouSure}
3131+ setAreYouSure={props.setAreYouSure}
3232+ className="accent-container rounded-lg! border-none! p-0! text-center justify-center"
3333+ >
3434+ <div className="px-3 pt-3 pb-4 sm:px-4 sm:pt-4 sm:pb-5">
3535+ <h3 className="leading-snug text-secondary">{publicationName}</h3>
3636+ {publicationDescription && (
3737+ <div className="text-tertiary pb-1">{publicationDescription}</div>
3838+ )}
3939+ <div className="max-w-sm w-full mx-auto pt-2">
4040+ <EmailInput
4141+ value={email}
4242+ onChange={setEmail}
4343+ action={
4444+ <ButtonPrimary
4545+ type="button"
4646+ compact
4747+ className="leading-tight! outline-none! text-sm!"
4848+ >
4949+ Subscribe
5050+ </ButtonPrimary>
5151+ }
5252+ />
5353+ </div>
5454+ </div>
5555+ </BlockLayout>
5656+ );
5757+};
+2
lexicons/api/index.ts
···4040import * as PubLeafletBlocksPage from './types/pub/leaflet/blocks/page'
4141import * as PubLeafletBlocksPoll from './types/pub/leaflet/blocks/poll'
4242import * as PubLeafletBlocksPostsList from './types/pub/leaflet/blocks/postsList'
4343+import * as PubLeafletBlocksSignup from './types/pub/leaflet/blocks/signup'
4344import * as PubLeafletBlocksStandardSitePost from './types/pub/leaflet/blocks/standardSitePost'
4445import * as PubLeafletBlocksText from './types/pub/leaflet/blocks/text'
4546import * as PubLeafletBlocksUnorderedList from './types/pub/leaflet/blocks/unorderedList'
···9697export * as PubLeafletBlocksPage from './types/pub/leaflet/blocks/page'
9798export * as PubLeafletBlocksPoll from './types/pub/leaflet/blocks/poll'
9899export * as PubLeafletBlocksPostsList from './types/pub/leaflet/blocks/postsList'
100100+export * as PubLeafletBlocksSignup from './types/pub/leaflet/blocks/signup'
99101export * as PubLeafletBlocksStandardSitePost from './types/pub/leaflet/blocks/standardSitePost'
100102export * as PubLeafletBlocksText from './types/pub/leaflet/blocks/text'
101103export * as PubLeafletBlocksUnorderedList from './types/pub/leaflet/blocks/unorderedList'
···11+/**
22+ * GENERATED CODE - DO NOT MODIFY
33+ */
44+import { type ValidationResult, BlobRef } from '@atproto/lexicon'
55+import { CID } from 'multiformats/cid'
66+import { validate as _validate } from '../../../../lexicons'
77+import {
88+ type $Typed,
99+ is$typed as _is$typed,
1010+ type OmitKey,
1111+} from '../../../../util'
1212+1313+const is$typed = _is$typed,
1414+ validate = _validate
1515+const id = 'pub.leaflet.blocks.signup'
1616+1717+/** A subscribe/signup form for the publication. Renders the publication's subscribe form; carries no configurable data. */
1818+export interface Main {
1919+ $type?: 'pub.leaflet.blocks.signup'
2020+}
2121+2222+const hashMain = 'main'
2323+2424+export function isMain<V>(v: V) {
2525+ return is$typed(v, id, hashMain)
2626+}
2727+2828+export function validateMain<V>(v: V) {
2929+ return validate<Main & V>(v, id, hashMain)
3030+}
+2
lexicons/api/types/pub/leaflet/pages/canvas.ts
···2626import type * as PubLeafletBlocksPoll from '../blocks/poll'
2727import type * as PubLeafletBlocksButton from '../blocks/button'
2828import type * as PubLeafletBlocksPostsList from '../blocks/postsList'
2929+import type * as PubLeafletBlocksSignup from '../blocks/signup'
29303031const is$typed = _is$typed,
3132 validate = _validate
···6768 | $Typed<PubLeafletBlocksPoll.Main>
6869 | $Typed<PubLeafletBlocksButton.Main>
6970 | $Typed<PubLeafletBlocksPostsList.Main>
7171+ | $Typed<PubLeafletBlocksSignup.Main>
7072 | { $type: string }
7173 x: number
7274 y: number
···2626import type * as PubLeafletBlocksPoll from '../blocks/poll'
2727import type * as PubLeafletBlocksButton from '../blocks/button'
2828import type * as PubLeafletBlocksPostsList from '../blocks/postsList'
2929+import type * as PubLeafletBlocksSignup from '../blocks/signup'
29303031const is$typed = _is$typed,
3132 validate = _validate
···6768 | $Typed<PubLeafletBlocksPoll.Main>
6869 | $Typed<PubLeafletBlocksButton.Main>
6970 | $Typed<PubLeafletBlocksPostsList.Main>
7171+ | $Typed<PubLeafletBlocksSignup.Main>
7072 | { $type: string }
7173 alignment?:
7274 | 'lex:pub.leaflet.pages.linearDocument#textAlignLeft'
+12
lexicons/pub/leaflet/blocks/signup.json
···11+{
22+ "lexicon": 1,
33+ "id": "pub.leaflet.blocks.signup",
44+ "defs": {
55+ "main": {
66+ "type": "object",
77+ "description": "A subscribe/signup form for the publication. Renders the publication's subscribe form; carries no configurable data.",
88+ "required": [],
99+ "properties": {}
1010+ }
1111+ }
1212+}