@fayz-ai/plugin-linkinbio 0.9.1 → 0.10.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.
Files changed (62) hide show
  1. package/dist/public/{data.d.ts → data/index.d.ts} +2 -2
  2. package/dist/public/data/index.d.ts.map +1 -0
  3. package/dist/public/{data.supabase.d.ts → data/supabase.d.ts} +2 -2
  4. package/dist/public/data/supabase.d.ts.map +1 -0
  5. package/dist/public/index.d.ts +8 -8
  6. package/dist/public/index.d.ts.map +1 -1
  7. package/dist/public/index.js +2 -2
  8. package/dist/public/index.js.map +1 -1
  9. package/dist/public/{registry.d.ts → lib/registry.d.ts} +1 -1
  10. package/dist/public/lib/registry.d.ts.map +1 -0
  11. package/dist/public/{utils.d.ts → lib/utils.d.ts} +1 -1
  12. package/dist/public/lib/utils.d.ts.map +1 -0
  13. package/dist/public/views/BioPage.d.ts.map +1 -0
  14. package/dist/public/{BioPageRenderer.d.ts → views/BioPageRenderer.d.ts} +1 -1
  15. package/dist/public/views/BioPageRenderer.d.ts.map +1 -0
  16. package/package.json +12 -15
  17. package/dist/index.cjs +0 -33
  18. package/dist/index.cjs.map +0 -1
  19. package/dist/public/BioPage.d.ts.map +0 -1
  20. package/dist/public/BioPageRenderer.d.ts.map +0 -1
  21. package/dist/public/__tests__/manifest.test.d.ts +0 -2
  22. package/dist/public/__tests__/manifest.test.d.ts.map +0 -1
  23. package/dist/public/data.d.ts.map +0 -1
  24. package/dist/public/data.supabase.d.ts.map +0 -1
  25. package/dist/public/index.cjs +0 -1984
  26. package/dist/public/index.cjs.map +0 -1
  27. package/dist/public/registry.d.ts.map +0 -1
  28. package/dist/public/utils.d.ts.map +0 -1
  29. package/src/index.ts +0 -56
  30. package/src/public/BioPage.tsx +0 -123
  31. package/src/public/BioPageRenderer.tsx +0 -251
  32. package/src/public/__tests__/manifest.test.ts +0 -39
  33. package/src/public/blocks/BioSection.tsx +0 -66
  34. package/src/public/blocks/CTASection.tsx +0 -56
  35. package/src/public/blocks/EmbedSection.tsx +0 -53
  36. package/src/public/blocks/FeaturedBlock.tsx +0 -70
  37. package/src/public/blocks/GallerySection.tsx +0 -148
  38. package/src/public/blocks/HeroSection.tsx +0 -107
  39. package/src/public/blocks/LinksBlock.tsx +0 -80
  40. package/src/public/blocks/MusicReleasesBlock.tsx +0 -83
  41. package/src/public/blocks/ProfileHeader.tsx +0 -174
  42. package/src/public/blocks/ProjectSection.tsx +0 -158
  43. package/src/public/blocks/SocialLinksSection.tsx +0 -125
  44. package/src/public/blocks/StatsSection.tsx +0 -46
  45. package/src/public/blocks/TextSection.tsx +0 -29
  46. package/src/public/blocks/TourDatesBlock.tsx +0 -83
  47. package/src/public/blocks/VenueListSection.tsx +0 -91
  48. package/src/public/blocks/VideoGridSection.tsx +0 -105
  49. package/src/public/blocks/index.ts +0 -16
  50. package/src/public/components/Carousel.tsx +0 -90
  51. package/src/public/components/MediaKit.tsx +0 -249
  52. package/src/public/components/PlatformIcon.tsx +0 -56
  53. package/src/public/components/Reveal.tsx +0 -58
  54. package/src/public/context.tsx +0 -30
  55. package/src/public/createPublicLinkInBioPlugin.ts +0 -81
  56. package/src/public/data.supabase.ts +0 -31
  57. package/src/public/data.ts +0 -29
  58. package/src/public/index.ts +0 -75
  59. package/src/public/registry.ts +0 -40
  60. package/src/public/utils.ts +0 -102
  61. package/src/types.ts +0 -337
  62. /package/dist/public/{BioPage.d.ts → views/BioPage.d.ts} +0 -0
@@ -1,249 +0,0 @@
1
- import { useMemo, useState, type CSSProperties } from 'react';
2
- import { createPortal } from 'react-dom';
3
- import { Download, X, Copy, Check, FileText, ExternalLink } from 'lucide-react';
4
- import type { BioPage, BioMediaItem } from '../../types';
5
- import { getBrandingVars } from '../utils';
6
-
7
- interface Asset {
8
- label: string;
9
- url: string;
10
- kind: 'image' | 'logo' | 'doc' | 'audio' | 'link';
11
- }
12
-
13
- /** Derive downloadable assets from the page (logo, photos, bio text) + explicit mediaKit. */
14
- function derive(page: BioPage): { images: Asset[]; bioText: string; extras: Asset[]; driveUrl?: string } {
15
- const seen = new Set<string>();
16
- const images: Asset[] = [];
17
- const pushImg = (label: string, url: string, kind: Asset['kind'] = 'image') => {
18
- if (!url || seen.has(url) || !url.startsWith('/')) return; // only local (own) assets
19
- seen.add(url);
20
- images.push({ label, url, kind });
21
- };
22
-
23
- if (page.branding.logoUrl) pushImg('Logo', page.branding.logoUrl, 'logo');
24
-
25
- let bioText = '';
26
- for (const s of page.sections) {
27
- if (s.type === 'profile-header' && s.imageUrl) pushImg('Foto de capa', s.imageUrl);
28
- if (s.type === 'hero' && s.backgroundImageUrl) pushImg('Foto de capa', s.backgroundImageUrl);
29
- if (s.type === 'gallery') s.media.forEach((m: BioMediaItem, i: number) => m.type === 'image' && pushImg(`Foto ${i + 1}`, m.url));
30
- if (s.type === 'bio' || s.type === 'text') {
31
- if (s.content) bioText += `${s.title ? `## ${s.title}\n\n` : ''}${s.content}\n\n`;
32
- if (s.type === 'bio' && s.imageUrl) pushImg('Foto', s.imageUrl);
33
- }
34
- }
35
-
36
- const extras = (page.mediaKit?.assets ?? []).map(
37
- (a: { label: string; url: string; kind?: Asset['kind'] }): Asset => ({ label: a.label, url: a.url, kind: a.kind ?? 'link' }),
38
- );
39
-
40
- return { images, bioText: bioText.trim(), extras, driveUrl: page.mediaKit?.driveUrl };
41
- }
42
-
43
- function fileName(url: string, fallback: string): string {
44
- const base = url.split('/').pop()?.split('?')[0];
45
- return base && base.includes('.') ? base : fallback;
46
- }
47
-
48
- export function MediaKit({ page, variant = 'inline' }: { page: BioPage; variant?: 'inline' | 'fab' }) {
49
- const [open, setOpen] = useState(false);
50
- const [copied, setCopied] = useState(false);
51
- const { images, bioText, extras, driveUrl } = useMemo(() => derive(page), [page]);
52
-
53
- if (!images.length && !bioText && !extras.length && !driveUrl) return null;
54
-
55
- const copyBio = async () => {
56
- try {
57
- await navigator.clipboard.writeText(bioText);
58
- setCopied(true);
59
- setTimeout(() => setCopied(false), 1800);
60
- } catch {
61
- /* clipboard unavailable */
62
- }
63
- };
64
-
65
- const downloadBio = () => {
66
- const blob = new Blob([bioText], { type: 'text/plain;charset=utf-8' });
67
- const url = URL.createObjectURL(blob);
68
- const a = document.createElement('a');
69
- a.href = url;
70
- a.download = `${page.identity.slug || 'bio'}-bio.txt`;
71
- a.click();
72
- setTimeout(() => URL.revokeObjectURL(url), 1000);
73
- };
74
-
75
- const modal = (
76
- <div
77
- className="fixed inset-0 z-[100] flex items-end sm:items-center justify-center p-0 sm:p-4"
78
- style={{
79
- ...(getBrandingVars(page.branding) as CSSProperties),
80
- background: 'rgba(0,0,0,0.6)',
81
- backdropFilter: 'blur(4px)',
82
- fontFamily: 'var(--pk-font-body)',
83
- }}
84
- onClick={() => setOpen(false)}
85
- >
86
- <div
87
- className="w-full sm:max-w-lg max-h-[88vh] overflow-y-auto rounded-t-3xl sm:rounded-3xl"
88
- style={{ backgroundColor: 'var(--pk-bg)', border: '1px solid rgba(255,255,255,0.1)' }}
89
- onClick={(e) => e.stopPropagation()}
90
- >
91
- {/* Header */}
92
- <div
93
- className="sticky top-0 flex items-center justify-between px-5 py-4 z-10"
94
- style={{ backgroundColor: 'var(--pk-bg)', borderBottom: '1px solid rgba(255,255,255,0.08)' }}
95
- >
96
- <div className="flex items-center gap-2">
97
- <Download size={18} style={{ color: 'var(--pk-primary)' }} />
98
- <h3 className="text-base font-bold" style={{ color: 'var(--pk-text)', fontFamily: 'var(--pk-font-heading)' }}>
99
- Mídia Kit
100
- </h3>
101
- </div>
102
- <button
103
- type="button"
104
- aria-label="Fechar"
105
- onClick={() => setOpen(false)}
106
- className="flex items-center justify-center w-8 h-8 rounded-full"
107
- style={{ backgroundColor: 'rgba(255,255,255,0.08)', color: 'var(--pk-text)' }}
108
- >
109
- <X size={16} />
110
- </button>
111
- </div>
112
-
113
- <div className="px-5 py-5 space-y-6">
114
- {/* Full kit (Drive) */}
115
- {driveUrl && (
116
- <a
117
- href={driveUrl}
118
- target="_blank"
119
- rel="noopener noreferrer"
120
- className="flex items-center gap-3 p-3 rounded-xl transition-transform hover:scale-[1.01]"
121
- style={{ backgroundColor: 'var(--pk-primary)', color: 'var(--pk-bg)' }}
122
- >
123
- <ExternalLink size={18} />
124
- <span className="text-sm font-semibold">Abrir kit completo (Drive)</span>
125
- </a>
126
- )}
127
-
128
- {/* Bio */}
129
- {bioText && (
130
- <section>
131
- <h4 className="text-xs font-bold uppercase tracking-wider mb-2 opacity-60" style={{ color: 'var(--pk-muted)' }}>
132
- Bio / Release
133
- </h4>
134
- <p
135
- className="text-sm leading-relaxed max-h-32 overflow-y-auto p-3 rounded-xl"
136
- style={{ color: 'var(--pk-muted)', backgroundColor: 'rgba(255,255,255,0.04)' }}
137
- >
138
- {bioText.replace(/[*#]/g, '')}
139
- </p>
140
- <div className="flex gap-2 mt-2">
141
- <button
142
- type="button"
143
- onClick={copyBio}
144
- className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold rounded-full"
145
- style={{ backgroundColor: 'rgba(255,255,255,0.08)', color: 'var(--pk-text)' }}
146
- >
147
- {copied ? <Check size={13} /> : <Copy size={13} />} {copied ? 'Copiado' : 'Copiar'}
148
- </button>
149
- <button
150
- type="button"
151
- onClick={downloadBio}
152
- className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold rounded-full"
153
- style={{ backgroundColor: 'rgba(255,255,255,0.08)', color: 'var(--pk-text)' }}
154
- >
155
- <FileText size={13} /> .txt
156
- </button>
157
- </div>
158
- </section>
159
- )}
160
-
161
- {/* Photos + logo */}
162
- {images.length > 0 && (
163
- <section>
164
- <h4 className="text-xs font-bold uppercase tracking-wider mb-2 opacity-60" style={{ color: 'var(--pk-muted)' }}>
165
- Fotos & Logo
166
- </h4>
167
- <div className="grid grid-cols-3 gap-2">
168
- {images.map((img) => (
169
- <a
170
- key={img.url}
171
- href={img.url}
172
- download={fileName(img.url, `${page.identity.slug}-${img.label}`)}
173
- className="group relative aspect-square overflow-hidden rounded-xl"
174
- style={{ backgroundColor: 'rgba(255,255,255,0.06)' }}
175
- title={`Baixar ${img.label}`}
176
- >
177
- <img
178
- src={img.url}
179
- alt={img.label}
180
- className={`w-full h-full ${img.kind === 'logo' ? 'object-contain p-3' : 'object-cover'}`}
181
- loading="lazy"
182
- />
183
- <div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
184
- style={{ background: 'rgba(0,0,0,0.45)' }}
185
- >
186
- <Download size={20} style={{ color: '#fff' }} />
187
- </div>
188
- </a>
189
- ))}
190
- </div>
191
- </section>
192
- )}
193
-
194
- {/* Extra resources */}
195
- {extras.length > 0 && (
196
- <section>
197
- <h4 className="text-xs font-bold uppercase tracking-wider mb-2 opacity-60" style={{ color: 'var(--pk-muted)' }}>
198
- Recursos
199
- </h4>
200
- <div className="flex flex-col gap-2">
201
- {extras.map((a) => (
202
- <a
203
- key={a.url}
204
- href={a.url}
205
- target="_blank"
206
- rel="noopener noreferrer"
207
- download={a.kind !== 'link' ? fileName(a.url, a.label) : undefined}
208
- className="flex items-center gap-3 p-3 rounded-xl"
209
- style={{ backgroundColor: 'rgba(255,255,255,0.05)', color: 'var(--pk-text)' }}
210
- >
211
- {a.kind === 'link' ? <ExternalLink size={16} /> : <FileText size={16} />}
212
- <span className="text-sm font-medium">{a.label}</span>
213
- <Download size={15} className="ml-auto opacity-50" />
214
- </a>
215
- ))}
216
- </div>
217
- </section>
218
- )}
219
- </div>
220
- </div>
221
- </div>
222
- );
223
-
224
- const isFab = variant === 'fab';
225
- return (
226
- <>
227
- <button
228
- type="button"
229
- onClick={() => setOpen(true)}
230
- aria-label="Baixar mídia kit"
231
- title="Mídia Kit"
232
- className={`flex items-center justify-center rounded-full transition-transform hover:scale-110 ${isFab ? 'w-12 h-12' : 'w-11 h-11'}`}
233
- style={
234
- isFab
235
- ? {
236
- color: 'var(--pk-primary)',
237
- border: '1.5px solid var(--pk-primary)',
238
- backgroundColor: 'color-mix(in srgb, var(--pk-bg) 70%, transparent)',
239
- backdropFilter: 'blur(6px)',
240
- }
241
- : { backgroundColor: 'rgba(255,255,255,0.07)', color: 'var(--pk-text)' }
242
- }
243
- >
244
- <Download size={isFab ? 20 : 19} />
245
- </button>
246
- {open && typeof document !== 'undefined' && createPortal(modal, document.body)}
247
- </>
248
- );
249
- }
@@ -1,56 +0,0 @@
1
- import type { CSSProperties, ComponentType } from 'react';
2
- import {
3
- SiInstagram,
4
- SiYoutube,
5
- SiSoundcloud,
6
- SiTiktok,
7
- SiSpotify,
8
- SiWhatsapp,
9
- SiApplemusic,
10
- SiBandcamp,
11
- SiFacebook,
12
- SiX,
13
- SiTelegram,
14
- SiBeatport,
15
- SiGoogledrive,
16
- SiMixcloud,
17
- } from 'react-icons/si';
18
- import { Globe, Mail, ExternalLink } from 'lucide-react';
19
-
20
- type IconProps = { size?: number | string; style?: CSSProperties; className?: string };
21
-
22
- // Real brand glyphs for social/platform links (react-icons → simple-icons set).
23
- const BRAND: Record<string, ComponentType<IconProps>> = {
24
- instagram: SiInstagram,
25
- youtube: SiYoutube,
26
- soundcloud: SiSoundcloud,
27
- tiktok: SiTiktok,
28
- spotify: SiSpotify,
29
- whatsapp: SiWhatsapp,
30
- 'apple-music': SiApplemusic,
31
- bandcamp: SiBandcamp,
32
- facebook: SiFacebook,
33
- twitter: SiX,
34
- telegram: SiTelegram,
35
- beatport: SiBeatport,
36
- mixcloud: SiMixcloud,
37
- drive: SiGoogledrive,
38
- email: Mail,
39
- website: Globe,
40
- };
41
-
42
- /** Renders the real brand icon for a platform, falling back to a generic link glyph. */
43
- export function PlatformIcon({
44
- platform,
45
- size = 20,
46
- style,
47
- className,
48
- }: {
49
- platform: string;
50
- size?: number;
51
- style?: CSSProperties;
52
- className?: string;
53
- }) {
54
- const Icon = BRAND[platform] ?? ExternalLink;
55
- return <Icon size={size} style={style} className={className} />;
56
- }
@@ -1,58 +0,0 @@
1
- import { useEffect, useRef, useState, type ReactNode } from 'react';
2
-
3
- /**
4
- * Scroll-reveal wrapper: fades + lifts its children into view the first time
5
- * they intersect the viewport. Respects prefers-reduced-motion (renders shown).
6
- * Works inside the page's `fixed inset-0` scroll container because the default
7
- * IntersectionObserver root is the viewport.
8
- */
9
- export function Reveal({
10
- children,
11
- delay = 0,
12
- className,
13
- }: {
14
- children: ReactNode;
15
- delay?: number;
16
- className?: string;
17
- }) {
18
- const ref = useRef<HTMLDivElement>(null);
19
- const [shown, setShown] = useState(false);
20
-
21
- useEffect(() => {
22
- const el = ref.current;
23
- if (!el) return;
24
- const reduce =
25
- typeof window !== 'undefined' &&
26
- window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
27
- if (reduce || typeof IntersectionObserver === 'undefined') {
28
- setShown(true);
29
- return;
30
- }
31
- const io = new IntersectionObserver(
32
- ([entry]) => {
33
- if (entry.isIntersecting) {
34
- setShown(true);
35
- io.disconnect();
36
- }
37
- },
38
- { threshold: 0.1, rootMargin: '0px 0px -6% 0px' },
39
- );
40
- io.observe(el);
41
- return () => io.disconnect();
42
- }, []);
43
-
44
- return (
45
- <div
46
- ref={ref}
47
- className={className}
48
- style={{
49
- opacity: shown ? 1 : 0,
50
- transform: shown ? 'none' : 'translateY(22px)',
51
- transition: `opacity 0.6s ease ${delay}ms, transform 0.6s cubic-bezier(0.2,0.7,0.2,1) ${delay}ms`,
52
- willChange: 'opacity, transform',
53
- }}
54
- >
55
- {children}
56
- </div>
57
- );
58
- }
@@ -1,30 +0,0 @@
1
- import { createContext, useContext, type ReactNode } from 'react';
2
- import type { BioPageDataProvider } from './data';
3
-
4
- export interface LinkInBioContextValue {
5
- provider: BioPageDataProvider;
6
- /** Optional "made with" footer link rendered by BioPageRenderer. */
7
- poweredBy?: { label: string; url: string };
8
- /** Fallback document.title suffix when a page omits seo.title. Default 'Bio'. */
9
- titleSuffix: string;
10
- }
11
-
12
- const LinkInBioContext = createContext<LinkInBioContextValue | null>(null);
13
-
14
- export function LinkInBioProvider({
15
- value,
16
- children,
17
- }: {
18
- value: LinkInBioContextValue;
19
- children: ReactNode;
20
- }) {
21
- return <LinkInBioContext.Provider value={value}>{children}</LinkInBioContext.Provider>;
22
- }
23
-
24
- export function useLinkInBio(): LinkInBioContextValue {
25
- const ctx = useContext(LinkInBioContext);
26
- if (!ctx) {
27
- throw new Error('useLinkInBio must be used within a LinkInBioProvider (mount the plugin Provider).');
28
- }
29
- return ctx;
30
- }
@@ -1,81 +0,0 @@
1
- import { createElement, type FC, type ReactNode } from 'react';
2
- import { createSafeDataProvider, type PluginManifest, type PluginScope, type VerticalId } from '@fayz-ai/core';
3
- import type { BioPage } from '../types';
4
- import { createMockBioPageProvider, type BioPageDataProvider } from './data';
5
- import { createSupabaseBioPageProvider } from './data.supabase';
6
- import { LinkInBioProvider, type LinkInBioContextValue } from './context';
7
- import BioPageRoute from './BioPage';
8
-
9
- export interface PublicLinkInBioOptions {
10
- /** Mount prefix for the public bio route. The route is `${basePath}/:slug`. Default '/p'. */
11
- basePath?: string;
12
- /**
13
- * When a Supabase client is configured (setGlobalSupabaseClient) this selects
14
- * the Supabase-backed provider. Omit to force the seeded mock provider.
15
- */
16
- useSupabase?: boolean;
17
- /** Table holding bio pages (Supabase mode). Default 'press_kits'. */
18
- table?: string;
19
- /** Inject a custom provider (overrides the mock/Supabase safe resolver). */
20
- dataProvider?: BioPageDataProvider;
21
- /** Seed catalog for the mock provider: slug → BioPage (host config / demo content). */
22
- seed?: Record<string, BioPage>;
23
- /** Optional "made with" footer link rendered on every page. */
24
- poweredBy?: { label: string; url: string };
25
- /** Fallback `<title>` suffix when a page omits seo.title. Default 'Bio'. */
26
- titleSuffix?: string;
27
- scope?: PluginScope;
28
- verticalId?: VerticalId;
29
- defaultEnabled?: boolean;
30
- }
31
-
32
- export interface PublicLinkInBioPlugin {
33
- manifest: PluginManifest;
34
- Provider: FC<{ children: ReactNode }>;
35
- dataProvider: BioPageDataProvider;
36
- }
37
-
38
- export function createPublicLinkInBioPlugin(options: PublicLinkInBioOptions = {}): PublicLinkInBioPlugin {
39
- const basePath = options.basePath ?? '/p';
40
- const table = options.table ?? 'press_kits';
41
- const seed = options.seed ?? {};
42
-
43
- // Resolution: explicit dataProvider → Supabase (when a global client is
44
- // configured AND useSupabase !== false) → seeded mock. Deferred + memoized so
45
- // importing the plugin never forces a Supabase connection.
46
- const provider: BioPageDataProvider =
47
- options.dataProvider ??
48
- (options.useSupabase === false
49
- ? createMockBioPageProvider({ seed })
50
- : createSafeDataProvider<BioPageDataProvider>(
51
- () => createSupabaseBioPageProvider({ table }),
52
- () => createMockBioPageProvider({ seed }),
53
- ));
54
-
55
- const contextValue: LinkInBioContextValue = {
56
- provider,
57
- poweredBy: options.poweredBy,
58
- titleSuffix: options.titleSuffix ?? 'Bio',
59
- };
60
-
61
- const Provider: FC<{ children: ReactNode }> = ({ children }) =>
62
- createElement(LinkInBioProvider, { value: contextValue, children });
63
- Provider.displayName = 'LinkInBioProvider';
64
-
65
- const manifest: PluginManifest = {
66
- id: 'linkinbio',
67
- name: 'Link in Bio',
68
- icon: 'Link',
69
- version: '0.1.0',
70
- scope: options.scope ?? 'universal',
71
- verticalId: options.verticalId,
72
- scaffolds: ['website', 'landing_page'],
73
- defaultEnabled: options.defaultEnabled ?? true,
74
- dependencies: [],
75
- navigation: [],
76
- routes: [{ path: `${basePath}/:slug`, component: BioPageRoute, guard: 'public', fullBleed: true }],
77
- widgets: [],
78
- };
79
-
80
- return { manifest, Provider, dataProvider: provider };
81
- }
@@ -1,31 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { getSupabaseClientOptional } from '@fayz-ai/core';
3
- import type { BioPage } from '../types';
4
- import type { BioPageDataProvider } from './data';
5
-
6
- export interface SupabaseBioPageOptions {
7
- /**
8
- * Table holding bio pages. Defaults to `press_kits` — a jsonb `data` column
9
- * with the full BioPage payload, plus `slug` and `is_published` columns and
10
- * an RLS policy allowing anon SELECT when `is_published` is true.
11
- */
12
- table?: string;
13
- }
14
-
15
- export function createSupabaseBioPageProvider(options: SupabaseBioPageOptions = {}): BioPageDataProvider {
16
- const table = options.table ?? 'press_kits';
17
- return {
18
- async getBySlug(slug: string): Promise<BioPage | null> {
19
- const client = getSupabaseClientOptional() as any;
20
- if (!client) return null;
21
- const { data, error } = await client
22
- .from(table)
23
- .select('data, is_published')
24
- .eq('slug', slug)
25
- .maybeSingle();
26
- if (error || !data) return null;
27
- if (!data.is_published) return null;
28
- return data.data as BioPage;
29
- },
30
- };
31
- }
@@ -1,29 +0,0 @@
1
- import type { BioPage } from '../types';
2
-
3
- // ---------------------------------------------------------------------------
4
- // BioPageDataProvider — the read seam for the public bio page.
5
- //
6
- // Two implementations ship: a mock provider seeded from an in-memory map (host
7
- // config / demo content) and a Supabase provider (data.supabase.ts). The
8
- // factory picks between them via createSafeDataProvider — Supabase when a global
9
- // client is configured, mock otherwise.
10
- // ---------------------------------------------------------------------------
11
-
12
- export interface BioPageDataProvider {
13
- /** Resolve a published bio page by its public slug, or null when missing/unpublished. */
14
- getBySlug(slug: string): Promise<BioPage | null>;
15
- }
16
-
17
- export interface MockBioPageOptions {
18
- /** slug → BioPage map used as the mock catalog. */
19
- seed?: Record<string, BioPage>;
20
- }
21
-
22
- export function createMockBioPageProvider(options: MockBioPageOptions = {}): BioPageDataProvider {
23
- const seed = options.seed ?? {};
24
- return {
25
- async getBySlug(slug: string): Promise<BioPage | null> {
26
- return seed[slug] ?? null;
27
- },
28
- };
29
- }
@@ -1,75 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // @fayz-ai/plugin-linkinbio/public — customer-facing link-in-bio surface.
3
- //
4
- // A komi/linktree-style public bio page for any creator: identity + branding +
5
- // an ordered list of content blocks. Separate entry from the admin plugin so a
6
- // website host imports only the renderer + data seam (no editor graph).
7
- //
8
- // Data: BioPageDataProvider — a seeded mock by default, Supabase (`press_kits`
9
- // jsonb) when a global client is configured. Extend the block set with
10
- // registerBlock(type, component).
11
- // ---------------------------------------------------------------------------
12
-
13
- export { createPublicLinkInBioPlugin } from './createPublicLinkInBioPlugin';
14
- export type { PublicLinkInBioOptions, PublicLinkInBioPlugin } from './createPublicLinkInBioPlugin';
15
-
16
- export { default as BioPageRenderer } from './BioPageRenderer';
17
- export type { BioPageRendererProps } from './BioPageRenderer';
18
- export { default as BioPage } from './BioPage';
19
- export { LinkInBioProvider, useLinkInBio } from './context';
20
- export type { LinkInBioContextValue } from './context';
21
-
22
- // Block registry — the extensibility seam
23
- export { registerBlock, getBlock, hasBlock } from './registry';
24
- export type { BlockRenderProps, BlockComponent } from './registry';
25
-
26
- // Data seam
27
- export { createMockBioPageProvider } from './data';
28
- export type { BioPageDataProvider, MockBioPageOptions } from './data';
29
- export { createSupabaseBioPageProvider } from './data.supabase';
30
- export type { SupabaseBioPageOptions } from './data.supabase';
31
-
32
- // Rendering helpers (useful inside custom blocks)
33
- export {
34
- renderMarkdown,
35
- platformColors,
36
- platformLabels,
37
- platformIconMap,
38
- borderRadiusMap,
39
- getBrandingVars,
40
- } from './utils';
41
-
42
- // Domain types
43
- export type {
44
- BioPage as BioPageData,
45
- BioBlock,
46
- BioBranding,
47
- BioIdentity,
48
- BioSEO,
49
- BioSocialLink,
50
- BioMediaItem,
51
- BioFloatingCTA,
52
- HeroSection,
53
- BioSection,
54
- GallerySection,
55
- VideoGridSection,
56
- SocialLinksSection,
57
- EmbedSection,
58
- ProjectSection,
59
- StatsSection,
60
- VenueListSection,
61
- CTASection,
62
- TextSection,
63
- ProfileHeaderSection,
64
- LinksSection,
65
- LinkCardItem,
66
- MusicReleasesSection,
67
- MusicReleaseItem,
68
- TourDatesSection,
69
- TourDateItem,
70
- FeaturedSection,
71
- } from '../types';
72
-
73
- // Shared UI (useful when composing custom blocks)
74
- export { Reveal } from './components/Reveal';
75
- export { Carousel } from './components/Carousel';
@@ -1,40 +0,0 @@
1
- import type { ComponentType } from 'react';
2
- import type { BioBlock, BioPage } from '../types';
3
-
4
- // ---------------------------------------------------------------------------
5
- // Block registry — the extensibility seam.
6
- //
7
- // The 11 built-in blocks are rendered by BioPageRenderer's switch. Hosts that
8
- // need a niche block type (e.g. a DJ tour map, a course catalog) register it
9
- // here; BioPageRenderer consults the registry BEFORE its built-in switch, so a
10
- // registered type wins and unknown types render nothing. This keeps the plugin
11
- // komi-generic while letting each app extend it without forking.
12
- //
13
- // import { registerBlock } from '@fayz-ai/plugin-linkinbio/public'
14
- // registerBlock('tour-map', ({ block }) => <TourMap stops={block.stops} />)
15
- // ---------------------------------------------------------------------------
16
-
17
- /** Uniform props every registered block component receives. */
18
- export interface BlockRenderProps<B = BioBlock> {
19
- block: B;
20
- page: BioPage;
21
- }
22
-
23
- export type BlockComponent<B = BioBlock> = ComponentType<BlockRenderProps<B>>;
24
-
25
- const registry = new Map<string, BlockComponent<never>>();
26
-
27
- /** Register a custom block renderer for a `type` string not covered by the built-ins. */
28
- export function registerBlock<B extends { type: string }>(type: B['type'], component: BlockComponent<B>): void {
29
- registry.set(type, component as unknown as BlockComponent<never>);
30
- }
31
-
32
- /** Look up a host-registered block renderer, or undefined for built-in/unknown types. */
33
- export function getBlock(type: string): BlockComponent<never> | undefined {
34
- return registry.get(type);
35
- }
36
-
37
- /** Whether a host has registered a renderer for this type (built-ins are not in the registry). */
38
- export function hasBlock(type: string): boolean {
39
- return registry.has(type);
40
- }