SPB Git forge

spb/fetcha

Public
11commits 1branches 0releases
1.5 MBsize
maindefault branch
16 days agolast push
TypeScript 97.5% SQL 1.4% Python 0.8%
11.5 KB · 242 lines tsx
Raw Blame History
1import * as React from "react";2import { Body, Button, Container, Head, Heading, Hr, Html, Link, Preview, Section, Text } from "@react-email/components";34const colors = {5  bg: "#f6f7f9",6  card: "#ffffff",7  text: "#0b0f19",8  muted: "#5b6472",9  accent: "#0f62fe",10  border: "#e5e8ec",11};1213const font = "-apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";1415function Layout({ preview, children, siteUrl }: { preview: string; children: React.ReactNode; siteUrl: string }) {16  return (17    <Html lang="en">18      <Head />19      <Preview>{preview}</Preview>20      <Body style={{ backgroundColor: colors.bg, fontFamily: font, margin: 0, padding: "32px 0" }}>21        <Container style={{ maxWidth: 560, margin: "0 auto", backgroundColor: colors.card, border: `1px solid ${colors.border}`, borderRadius: 10, padding: "36px 40px" }}>22          <Section style={{ marginBottom: 24 }}>23            <Link href={siteUrl} style={{ textDecoration: "none", color: colors.text, fontWeight: 700, fontSize: 18, letterSpacing: "-0.02em" }}>24              <span style={{ display: "inline-block", width: 10, height: 10, borderRadius: 2, backgroundColor: colors.accent, marginRight: 8, verticalAlign: "middle" }} />25              fetcha26            </Link>27          </Section>28          {children}29          <Hr style={{ borderColor: colors.border, margin: "32px 0 16px" }} />30          <Text style={{ color: colors.muted, fontSize: 12, lineHeight: "18px", margin: 0 }}>31            Fetcha — Intelligent Web Access Infrastructure. You received this email because an account on{" "}32            <Link href={siteUrl} style={{ color: colors.muted }}>33              fetcha.co34            </Link>{" "}35            uses this address. Questions? <Link href="mailto:support@fetcha.co" style={{ color: colors.muted }}>support@fetcha.co</Link>36          </Text>37        </Container>38      </Body>39    </Html>40  );41}4243const h1: React.CSSProperties = { fontSize: 22, fontWeight: 600, letterSpacing: "-0.02em", color: colors.text, margin: "0 0 12px" };44const p: React.CSSProperties = { fontSize: 15, lineHeight: "24px", color: colors.text, margin: "0 0 16px" };45const small: React.CSSProperties = { fontSize: 13, lineHeight: "20px", color: colors.muted, margin: "16px 0 0" };46const btn: React.CSSProperties = { backgroundColor: colors.accent, color: "#fff", fontSize: 14, fontWeight: 600, padding: "12px 20px", borderRadius: 6, textDecoration: "none", display: "inline-block" };47const code: React.CSSProperties = { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 13, backgroundColor: "#f1f3f6", padding: "2px 6px", borderRadius: 4 };4849export function VerificationEmail({ url, siteUrl }: { url: string; siteUrl: string }) {50  return (51    <Layout preview="Confirm your email address to activate your Fetcha account" siteUrl={siteUrl}>52      <Heading style={h1}>Confirm your email</Heading>53      <Text style={p}>Welcome to Fetcha. Click the button below to verify your email address and unlock production API access.</Text>54      <Button href={url} style={btn}>55        Verify email address56      </Button>57      <Text style={small}>58        This link expires in 24 hours. If the button does not work, paste this URL in your browser:59        <br />60        <Link href={url} style={{ color: colors.accent, wordBreak: "break-all" }}>61          {url}62        </Link>63      </Text>64      <Text style={small}>If you did not create a Fetcha account, you can safely ignore this email.</Text>65    </Layout>66  );67}6869export function WelcomeEmail({ name, dashboardUrl, docsUrl, siteUrl }: { name: string; dashboardUrl: string; docsUrl: string; siteUrl: string }) {70  return (71    <Layout preview="Your Fetcha account is ready" siteUrl={siteUrl}>72      <Heading style={h1}>Welcome to Fetcha{name ? `, ${name}` : ""}</Heading>73      <Text style={p}>Your email is verified and your account is ready. Here is the fastest path to your first request:</Text>74      <Text style={p}>75        1. Create a project and an API key in the dashboard.76        <br />77        2. Open the Playground, enter a URL, run it.78        <br />79        3. Copy the generated code for cURL, Python or JavaScript.80      </Text>81      <Button href={dashboardUrl} style={btn}>82        Open the dashboard83      </Button>84      <Text style={small}>85        Documentation lives at{" "}86        <Link href={docsUrl} style={{ color: colors.accent }}>87          {docsUrl}88        </Link>89        . Fetcha is a private platform: unlimited requests, no plans, no meters.90      </Text>91    </Layout>92  );93}9495export function InviteEmail({ inviterName, signupUrl, siteUrl }: { inviterName: string; signupUrl: string; siteUrl: string }) {96  const by = inviterName.trim();97  return (98    <Layout preview="You have been invited to Fetcha" siteUrl={siteUrl}>99      <Heading style={h1}>You have been invited to Fetcha</Heading>100      <Text style={p}>101        {by ? <><strong>{by}</strong> has added your email address to the Fetcha access list.</> : <>Your email address was added to the Fetcha access list.</>} Fetcha is a private,102        invitation-only platform: one API to reach any web page through managed proxy networks, a headless browser and crawl jobs, with no plans and no meters.103      </Text>104      <Text style={p}>Create your account with this same email address to get started. Your workspace, a default project and the Playground are ready as soon as you sign up.</Text>105      <Button href={signupUrl} style={btn}>106        Create my account107      </Button>108      <Text style={small}>109        If the button does not work, paste this URL in your browser:110        <br />111        <Link href={signupUrl} style={{ color: colors.accent, wordBreak: "break-all" }}>112          {signupUrl}113        </Link>114      </Text>115      <Text style={small}>If you were not expecting this invitation, you can safely ignore this email — no account is created until you sign up.</Text>116    </Layout>117  );118}119120export function PasswordResetEmail({ url, siteUrl }: { url: string; siteUrl: string }) {121  return (122    <Layout preview="Reset your Fetcha password" siteUrl={siteUrl}>123      <Heading style={h1}>Reset your password</Heading>124      <Text style={p}>We received a request to reset the password of your Fetcha account. This link is valid for one hour.</Text>125      <Button href={url} style={btn}>126        Choose a new password127      </Button>128      <Text style={small}>If you did not request a reset, ignore this email — your password will not change.</Text>129    </Layout>130  );131}132133export function EmailChangedEmail({ oldEmail, newEmail, siteUrl }: { oldEmail: string; newEmail: string; siteUrl: string }) {134  return (135    <Layout preview="The email address on your Fetcha account changed" siteUrl={siteUrl}>136      <Heading style={h1}>Email address changed</Heading>137      <Text style={p}>138        The email on your Fetcha account was changed from <span style={code}>{oldEmail}</span> to <span style={code}>{newEmail}</span>.139      </Text>140      <Text style={small}>If you did not make this change, contact support@fetcha.co immediately.</Text>141    </Layout>142  );143}144145export function EmailChangeApprovalEmail({ url, newEmail, siteUrl }: { url: string; newEmail: string; siteUrl: string }) {146  return (147    <Layout preview="Approve the email change on your Fetcha account" siteUrl={siteUrl}>148      <Heading style={h1}>Approve email change</Heading>149      <Text style={p}>150        A request was made to change your Fetcha account email to <span style={code}>{newEmail}</span>. Confirm below to proceed.151      </Text>152      <Button href={url} style={btn}>153        Approve change154      </Button>155      <Text style={small}>If you did not request this, ignore this email and consider changing your password.</Text>156    </Layout>157  );158}159160export function ApiKeyCreatedEmail({ keyName, prefix, projectName, when, siteUrl, dashboardUrl }: { keyName: string; prefix: string; projectName: string; when: string; siteUrl: string; dashboardUrl: string }) {161  return (162    <Layout preview="A new API key was created on your Fetcha account" siteUrl={siteUrl}>163      <Heading style={h1}>New API key created</Heading>164      <Text style={p}>165        The key <strong>{keyName}</strong> (<span style={code}>{prefix}…</span>) was created in project <strong>{projectName}</strong> on {when}.166      </Text>167      <Button href={dashboardUrl} style={btn}>168        Review API keys169      </Button>170      <Text style={small}>If you did not create this key, revoke it now from the dashboard and rotate your other keys.</Text>171    </Layout>172  );173}174175export function ApiKeyRevokedEmail({ keyName, prefix, siteUrl }: { keyName: string; prefix: string; siteUrl: string }) {176  return (177    <Layout preview="An API key was revoked" siteUrl={siteUrl}>178      <Heading style={h1}>API key revoked</Heading>179      <Text style={p}>180        The key <strong>{keyName}</strong> (<span style={code}>{prefix}…</span>) was revoked. Requests using it now fail with <span style={code}>INVALID_API_KEY</span>.181      </Text>182    </Layout>183  );184}185186export function NewLoginEmail({ ip, userAgent, when, siteUrl, securityUrl }: { ip: string; userAgent: string; when: string; siteUrl: string; securityUrl: string }) {187  return (188    <Layout preview="New sign-in to your Fetcha account" siteUrl={siteUrl}>189      <Heading style={h1}>New sign-in detected</Heading>190      <Text style={p}>191        Your account was accessed on {when} from IP <span style={code}>{ip}</span>.192        <br />193        Device: <span style={code}>{userAgent}</span>194      </Text>195      <Button href={securityUrl} style={btn}>196        Review active sessions197      </Button>198      <Text style={small}>If this was not you, revoke the session and change your password right away.</Text>199    </Layout>200  );201}202203export function UsageAlertEmail({ projectName, spent, limit, kind, siteUrl, usageUrl }: { projectName: string; spent: string; limit: string; kind: "soft" | "hard"; siteUrl: string; usageUrl: string }) {204  return (205    <Layout preview={kind === "hard" ? "Requests stopped: spending limit reached" : "Usage alert: spending threshold reached"} siteUrl={siteUrl}>206      <Heading style={h1}>{kind === "hard" ? "Spending limit reached — requests paused" : "Spending alert"}</Heading>207      <Text style={p}>208        Project <strong>{projectName}</strong> has spent {spent} this month against a {kind} limit of {limit}.209        {kind === "hard" ? " Further requests return USAGE_LIMIT_REACHED until the limit is raised or the month rolls over." : ""}210      </Text>211      <Button href={usageUrl} style={btn}>212        View usage213      </Button>214    </Layout>215  );216}217218export function AccountDeletionEmail({ url, siteUrl }: { url: string; siteUrl: string }) {219  return (220    <Layout preview="Confirm the deletion of your Fetcha account" siteUrl={siteUrl}>221      <Heading style={h1}>Confirm account deletion</Heading>222      <Text style={p}>You asked to delete your Fetcha account. Confirm below. After confirmation your account enters a 7-day grace period before data is deleted or anonymized. Invoices are retained as required by law.</Text>223      <Button href={url} style={{ ...btn, backgroundColor: "#c0392b" }}>224        Delete my account225      </Button>226      <Text style={small}>If you did not request this, ignore this email.</Text>227    </Layout>228  );229}230231export function BillingEmail({ title, body, siteUrl, billingUrl }: { title: string; body: string; siteUrl: string; billingUrl: string }) {232  return (233    <Layout preview={title} siteUrl={siteUrl}>234      <Heading style={h1}>{title}</Heading>235      <Text style={p}>{body}</Text>236      <Button href={billingUrl} style={btn}>237        Manage billing238      </Button>239    </Layout>240  );241}242