@braintwopoint0/playback-commons 0.1.21 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/email/index.d.ts +44 -0
- package/dist/email/index.js +46 -0
- package/dist/email/index.js.map +1 -0
- package/dist/ui/index.d.ts +112 -2
- package/dist/ui/index.js +497 -57
- package/dist/ui/index.js.map +1 -1
- package/package.json +14 -3
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Resend } from 'resend';
|
|
2
|
+
|
|
3
|
+
interface SendEmailResult {
|
|
4
|
+
success: boolean;
|
|
5
|
+
error?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Returns a memoized Resend client for the given API key. Callers should hold the
|
|
9
|
+
* returned client at module scope so one instance is reused across requests within
|
|
10
|
+
* a Next.js runtime.
|
|
11
|
+
*/
|
|
12
|
+
declare function createResendClient(apiKey: string | undefined): Resend | null;
|
|
13
|
+
type AudienceSyncOptions = {
|
|
14
|
+
role?: string | null;
|
|
15
|
+
source?: string | null;
|
|
16
|
+
firstName?: string | null;
|
|
17
|
+
lastName?: string | null;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Adds a subscriber to a Resend audience. Returns the Resend contact id on success,
|
|
21
|
+
* null on any failure (misconfigured / API error / network). Callers should persist
|
|
22
|
+
* success/failure and rely on a reconciliation job to retry nulls.
|
|
23
|
+
*
|
|
24
|
+
* Intentionally does NOT throw — failures are operational, not business logic, and
|
|
25
|
+
* the caller's primary persistence (Supabase row) is already successful by the time
|
|
26
|
+
* this is invoked.
|
|
27
|
+
*/
|
|
28
|
+
declare function syncToResendAudience(params: {
|
|
29
|
+
client: Resend | null;
|
|
30
|
+
audienceId: string | undefined;
|
|
31
|
+
email: string;
|
|
32
|
+
options?: AudienceSyncOptions;
|
|
33
|
+
}): Promise<string | null>;
|
|
34
|
+
/**
|
|
35
|
+
* Remove a contact from a Resend audience (e.g. on unsubscribe). Returns true on
|
|
36
|
+
* success; false if not configured or the call fails. Safe to retry.
|
|
37
|
+
*/
|
|
38
|
+
declare function removeFromResendAudience(params: {
|
|
39
|
+
client: Resend | null;
|
|
40
|
+
audienceId: string | undefined;
|
|
41
|
+
contactId: string;
|
|
42
|
+
}): Promise<boolean>;
|
|
43
|
+
|
|
44
|
+
export { type AudienceSyncOptions, type SendEmailResult, createResendClient, removeFromResendAudience, syncToResendAudience };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/email/index.ts
|
|
2
|
+
import { Resend } from "resend";
|
|
3
|
+
var clientCache = /* @__PURE__ */ new Map();
|
|
4
|
+
function createResendClient(apiKey) {
|
|
5
|
+
if (!apiKey) return null;
|
|
6
|
+
const cached = clientCache.get(apiKey);
|
|
7
|
+
if (cached) return cached;
|
|
8
|
+
const client = new Resend(apiKey);
|
|
9
|
+
clientCache.set(apiKey, client);
|
|
10
|
+
return client;
|
|
11
|
+
}
|
|
12
|
+
async function syncToResendAudience(params) {
|
|
13
|
+
const { client, audienceId, email, options } = params;
|
|
14
|
+
if (!client || !audienceId) return null;
|
|
15
|
+
try {
|
|
16
|
+
const res = await client.contacts.create({
|
|
17
|
+
audienceId,
|
|
18
|
+
email,
|
|
19
|
+
unsubscribed: false,
|
|
20
|
+
...options?.firstName ? { firstName: options.firstName } : {},
|
|
21
|
+
...options?.lastName ? { lastName: options.lastName } : {}
|
|
22
|
+
});
|
|
23
|
+
const id = res.data?.id ?? res.id ?? null;
|
|
24
|
+
return id;
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error("[commons/email] audience sync failed", err);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function removeFromResendAudience(params) {
|
|
31
|
+
const { client, audienceId, contactId } = params;
|
|
32
|
+
if (!client || !audienceId) return false;
|
|
33
|
+
try {
|
|
34
|
+
await client.contacts.remove({ audienceId, id: contactId });
|
|
35
|
+
return true;
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error("[commons/email] audience remove failed", err);
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
createResendClient,
|
|
43
|
+
removeFromResendAudience,
|
|
44
|
+
syncToResendAudience
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/email/index.ts"],"sourcesContent":["// Shared email primitives for PLAYBACK and PLAYHUB.\n//\n// Transactional templates (welcome, invoice, admin invite, etc.) stay in each app's\n// `src/lib/email/` — they're brand/context specific. What lives here are the pieces\n// that both apps need identically:\n//\n// - Resend client factory (memoized per API key)\n// - Marketing-list audience sync (for footer signups, campaign lists)\n// - Shared result type\n//\n// `escapeHtml` for safe interpolation already lives in `@braintwopoint0/playback-commons/security`.\n\nimport { Resend } from 'resend'\n\nexport interface SendEmailResult {\n success: boolean\n error?: string\n}\n\nconst clientCache = new Map<string, Resend>()\n\n/**\n * Returns a memoized Resend client for the given API key. Callers should hold the\n * returned client at module scope so one instance is reused across requests within\n * a Next.js runtime.\n */\nexport function createResendClient(apiKey: string | undefined): Resend | null {\n if (!apiKey) return null\n const cached = clientCache.get(apiKey)\n if (cached) return cached\n const client = new Resend(apiKey)\n clientCache.set(apiKey, client)\n return client\n}\n\nexport type AudienceSyncOptions = {\n role?: string | null\n source?: string | null\n firstName?: string | null\n lastName?: string | null\n}\n\n/**\n * Adds a subscriber to a Resend audience. Returns the Resend contact id on success,\n * null on any failure (misconfigured / API error / network). Callers should persist\n * success/failure and rely on a reconciliation job to retry nulls.\n *\n * Intentionally does NOT throw — failures are operational, not business logic, and\n * the caller's primary persistence (Supabase row) is already successful by the time\n * this is invoked.\n */\nexport async function syncToResendAudience(params: {\n client: Resend | null\n audienceId: string | undefined\n email: string\n options?: AudienceSyncOptions\n}): Promise<string | null> {\n const { client, audienceId, email, options } = params\n if (!client || !audienceId) return null\n\n try {\n const res = await client.contacts.create({\n audienceId,\n email,\n unsubscribed: false,\n ...(options?.firstName ? { firstName: options.firstName } : {}),\n ...(options?.lastName ? { lastName: options.lastName } : {}),\n })\n // Resend's typing varies across versions — defensively read the id.\n const id =\n (res as { data?: { id?: string } }).data?.id ??\n (res as { id?: string }).id ??\n null\n return id\n } catch (err) {\n console.error('[commons/email] audience sync failed', err)\n return null\n }\n}\n\n/**\n * Remove a contact from a Resend audience (e.g. on unsubscribe). Returns true on\n * success; false if not configured or the call fails. Safe to retry.\n */\nexport async function removeFromResendAudience(params: {\n client: Resend | null\n audienceId: string | undefined\n contactId: string\n}): Promise<boolean> {\n const { client, audienceId, contactId } = params\n if (!client || !audienceId) return false\n try {\n await client.contacts.remove({ audienceId, id: contactId })\n return true\n } catch (err) {\n console.error('[commons/email] audience remove failed', err)\n return false\n }\n}\n"],"mappings":";AAYA,SAAS,cAAc;AAOvB,IAAM,cAAc,oBAAI,IAAoB;AAOrC,SAAS,mBAAmB,QAA2C;AAC5E,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,SAAS,YAAY,IAAI,MAAM;AACrC,MAAI,OAAQ,QAAO;AACnB,QAAM,SAAS,IAAI,OAAO,MAAM;AAChC,cAAY,IAAI,QAAQ,MAAM;AAC9B,SAAO;AACT;AAkBA,eAAsB,qBAAqB,QAKhB;AACzB,QAAM,EAAE,QAAQ,YAAY,OAAO,QAAQ,IAAI;AAC/C,MAAI,CAAC,UAAU,CAAC,WAAY,QAAO;AAEnC,MAAI;AACF,UAAM,MAAM,MAAM,OAAO,SAAS,OAAO;AAAA,MACvC;AAAA,MACA;AAAA,MACA,cAAc;AAAA,MACd,GAAI,SAAS,YAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;AAAA,MAC7D,GAAI,SAAS,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,IAC5D,CAAC;AAED,UAAM,KACH,IAAmC,MAAM,MACzC,IAAwB,MACzB;AACF,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ,MAAM,wCAAwC,GAAG;AACzD,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,yBAAyB,QAI1B;AACnB,QAAM,EAAE,QAAQ,YAAY,UAAU,IAAI;AAC1C,MAAI,CAAC,UAAU,CAAC,WAAY,QAAO;AACnC,MAAI;AACF,UAAM,OAAO,SAAS,OAAO,EAAE,YAAY,IAAI,UAAU,CAAC;AAC1D,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ,MAAM,0CAA0C,GAAG;AAC3D,WAAO;AAAA,EACT;AACF;","names":[]}
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -404,6 +404,116 @@ declare function FadeIn({ children, className, delay, direction, ...props }: Fad
|
|
|
404
404
|
|
|
405
405
|
declare function LumaSpin(): react_jsx_runtime.JSX.Element;
|
|
406
406
|
|
|
407
|
-
|
|
407
|
+
type FooterLinkDef = {
|
|
408
|
+
label: string;
|
|
409
|
+
href: string;
|
|
410
|
+
external?: boolean;
|
|
411
|
+
};
|
|
412
|
+
type FooterColumnDef = {
|
|
413
|
+
title: string;
|
|
414
|
+
links: FooterLinkDef[];
|
|
415
|
+
};
|
|
416
|
+
type FooterSocialDef = {
|
|
417
|
+
label: string;
|
|
418
|
+
href: string;
|
|
419
|
+
src: string;
|
|
420
|
+
};
|
|
421
|
+
type FooterProps = {
|
|
422
|
+
columns?: FooterColumnDef[];
|
|
423
|
+
socials?: FooterSocialDef[];
|
|
424
|
+
newsletter?: boolean;
|
|
425
|
+
tagline?: string;
|
|
426
|
+
showTagline?: boolean;
|
|
427
|
+
logoSrc?: string;
|
|
428
|
+
logoAlt?: string;
|
|
429
|
+
siteName?: string;
|
|
430
|
+
creditHref?: string;
|
|
431
|
+
creditLabel?: string;
|
|
432
|
+
newsletterAction?: (email: string) => void | Promise<void>;
|
|
433
|
+
className?: string;
|
|
434
|
+
};
|
|
435
|
+
declare function Footer({ columns, socials, newsletter, tagline, showTagline, logoSrc, logoAlt, siteName, creditHref, creditLabel, newsletterAction, className, }?: FooterProps): react_jsx_runtime.JSX.Element;
|
|
436
|
+
|
|
437
|
+
type FooterSocialLink = {
|
|
438
|
+
label: string;
|
|
439
|
+
href: string;
|
|
440
|
+
src: string;
|
|
441
|
+
};
|
|
442
|
+
type FooterCreditsBarProps = {
|
|
443
|
+
/**
|
|
444
|
+
* Social links to render on the left. Defaults to the PLAYBACK brand socials.
|
|
445
|
+
* Note: `src` paths are resolved by the consuming app's Next.js static file
|
|
446
|
+
* server, so the assets must exist in the app's /public directory.
|
|
447
|
+
*/
|
|
448
|
+
socials?: FooterSocialLink[];
|
|
449
|
+
/**
|
|
450
|
+
* Company name used in the copyright line. Defaults to "PLAYBACK".
|
|
451
|
+
*/
|
|
452
|
+
companyName?: string;
|
|
453
|
+
/**
|
|
454
|
+
* Credit link href. Defaults to BRAIN2.0's homepage.
|
|
455
|
+
*/
|
|
456
|
+
creditHref?: string;
|
|
457
|
+
/**
|
|
458
|
+
* Show the "Built by BRAIN2.0" credit next to the socials. Defaults to true.
|
|
459
|
+
* The credit uses the Averta Semibold + Thin split-weight wordmark for
|
|
460
|
+
* "BRAIN2.0". Those @font-face declarations must be loaded by the consuming
|
|
461
|
+
* app's globals.css (AvertaStd-Semibold.ttf + AvertaStd-Thin.ttf).
|
|
462
|
+
*/
|
|
463
|
+
showCredit?: boolean;
|
|
464
|
+
/**
|
|
465
|
+
* Starting year for the copyright range. Defaults to current year.
|
|
466
|
+
*/
|
|
467
|
+
copyrightYear?: number;
|
|
468
|
+
/**
|
|
469
|
+
* Additional classes on the outer container.
|
|
470
|
+
*/
|
|
471
|
+
className?: string;
|
|
472
|
+
};
|
|
473
|
+
/**
|
|
474
|
+
* Shared footer bottom-bar: social icons + BRAIN2.0 credit on the left,
|
|
475
|
+
* copyright right-aligned on desktop and centered on mobile.
|
|
476
|
+
*
|
|
477
|
+
* Use inside an existing footer container. Provides its own top hairline.
|
|
478
|
+
*/
|
|
479
|
+
declare function FooterCreditsBar({ socials, companyName, creditHref, showCredit, copyrightYear, className, }?: FooterCreditsBarProps): react_jsx_runtime.JSX.Element;
|
|
480
|
+
|
|
481
|
+
type NewsletterFormProps = {
|
|
482
|
+
/**
|
|
483
|
+
* Endpoint the form POSTs to. Default: /api/newsletter/subscribe.
|
|
484
|
+
* Must accept `{ email, source, website }` and return 200/400/429/500.
|
|
485
|
+
*/
|
|
486
|
+
endpoint?: string;
|
|
487
|
+
/**
|
|
488
|
+
* Identifier for which surface the signup came from. Server uses this to route
|
|
489
|
+
* into segmentation (e.g. 'footer', 'hero', 'clubs-form').
|
|
490
|
+
*/
|
|
491
|
+
source?: string;
|
|
492
|
+
/**
|
|
493
|
+
* Optional role hint (parent / player / club / coach / press). Server must allowlist.
|
|
494
|
+
*/
|
|
495
|
+
role?: string;
|
|
496
|
+
/**
|
|
497
|
+
* Placeholder for the email input.
|
|
498
|
+
*/
|
|
499
|
+
placeholder?: string;
|
|
500
|
+
/**
|
|
501
|
+
* Accessible label for the form.
|
|
502
|
+
*/
|
|
503
|
+
ariaLabel?: string;
|
|
504
|
+
/**
|
|
505
|
+
* Submit button label.
|
|
506
|
+
*/
|
|
507
|
+
submitLabel?: string;
|
|
508
|
+
/**
|
|
509
|
+
* Copy shown while the request is in flight.
|
|
510
|
+
*/
|
|
511
|
+
sendingLabel?: string;
|
|
512
|
+
/**
|
|
513
|
+
* Optional className on the outer <form>.
|
|
514
|
+
*/
|
|
515
|
+
className?: string;
|
|
516
|
+
};
|
|
517
|
+
declare function NewsletterForm({ endpoint, source, role, placeholder, ariaLabel, submitLabel, sendingLabel, className, }: NewsletterFormProps): react_jsx_runtime.JSX.Element;
|
|
408
518
|
|
|
409
|
-
export { AnimatedTooltip, Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, Button, type ButtonProps, Calendar, CalendarDayButton, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, DataRow, type DataRowProps, DatePicker, DateTimePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, EmptyState, type EmptyStateProps, FadeIn, type FadeInProps, FlipWords, Footer, HeroHighlight, Highlight, HoverCard, HoverCardDescription, HoverCardTitle, HoverEffect, Input, type InputProps, Label, LumaSpin, PageShell, type PageShellProps, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, SearchBar, type SearchBarProps, SectionCard, type SectionCardProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Skeleton, type StatItem, StatsGrid, type StatsGridProps, Switch, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, type TextareaProps, TimePicker, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, buttonVariants, easeSmooth, fadeInDown, fadeInUp, hoverLift, hoverScale, pageTransition, springBounce, staggerContainer, staggerItem };
|
|
519
|
+
export { AnimatedTooltip, Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, Button, type ButtonProps, Calendar, CalendarDayButton, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, DataRow, type DataRowProps, DatePicker, DateTimePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, EmptyState, type EmptyStateProps, FadeIn, type FadeInProps, FlipWords, Footer, type FooterColumnDef, FooterCreditsBar, type FooterCreditsBarProps, type FooterLinkDef, type FooterProps, type FooterSocialDef, type FooterSocialLink, HeroHighlight, Highlight, HoverCard, HoverCardDescription, HoverCardTitle, HoverEffect, Input, type InputProps, Label, LumaSpin, NewsletterForm, type NewsletterFormProps, PageShell, type PageShellProps, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, SearchBar, type SearchBarProps, SectionCard, type SectionCardProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Skeleton, type StatItem, StatsGrid, type StatsGridProps, Switch, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, type TextareaProps, TimePicker, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, buttonVariants, easeSmooth, fadeInDown, fadeInUp, hoverLift, hoverScale, pageTransition, springBounce, staggerContainer, staggerItem };
|