@fayz-ai/plugin-blog 0.8.0 → 0.8.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/config.d.ts CHANGED
@@ -13,6 +13,11 @@ export interface BlogPluginOptions {
13
13
  siteName?: string;
14
14
  /** Inject a custom provider (e.g. a CMS). Overrides the safe mock/Supabase resolver. */
15
15
  dataProvider?: BlogDataProvider;
16
+ /** Tenant whose PUBLISHED posts the public (anon) site reads from Supabase.
17
+ * Required for the Supabase provider on an anon website (mirrors how the
18
+ * public booking plugin takes an explicit tenantId). Without it the safe
19
+ * resolver falls back to the mock/seed provider. */
20
+ tenantId?: string;
16
21
  /** Section eyebrow / labels for the default list screen. */
17
22
  labels?: {
18
23
  /** Title shown at the top of the /blog list screen. */
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEzC,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,+EAA+E;IAC/E,aAAa,CAAC,EAAE,UAAU,CAAA;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wFAAwF;IACxF,YAAY,CAAC,EAAE,gBAAgB,CAAA;IAC/B,4DAA4D;IAC5D,MAAM,CAAC,EAAE;QACP,uDAAuD;QACvD,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,qCAAqC;QACrC,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,sDAAsD;QACtD,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE;QACN,SAAS,EAAE,MAAM,CAAA;QACjB,YAAY,EAAE,MAAM,CAAA;QACpB,UAAU,EAAE,MAAM,CAAA;KACnB,CAAA;CACF;AAED,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,kBAAkB,CAU7E"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEzC,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,+EAA+E;IAC/E,aAAa,CAAC,EAAE,UAAU,CAAA;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wFAAwF;IACxF,YAAY,CAAC,EAAE,gBAAgB,CAAA;IAC/B;;;yDAGqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4DAA4D;IAC5D,MAAM,CAAC,EAAE;QACP,uDAAuD;QACvD,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,qCAAqC;QACrC,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,sDAAsD;QACtD,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE;QACN,SAAS,EAAE,MAAM,CAAA;QACjB,YAAY,EAAE,MAAM,CAAA;QACpB,UAAU,EAAE,MAAM,CAAA;KACnB,CAAA;CACF;AAED,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,kBAAkB,CAU7E"}
@@ -1,3 +1,7 @@
1
1
  import type { BlogDataProvider } from './types';
2
- export declare function createSupabaseBlogProvider(): BlogDataProvider;
2
+ export interface SupabaseBlogProviderOptions {
3
+ /** Tenant whose published posts to read (anon website). Defaults to the active session tenant. */
4
+ tenantId?: string;
5
+ }
6
+ export declare function createSupabaseBlogProvider(options?: SupabaseBlogProviderOptions): BlogDataProvider;
3
7
  //# sourceMappingURL=supabase.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/data/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAgB/C,wBAAgB,0BAA0B,IAAI,gBAAgB,CAW7D"}
1
+ {"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/data/supabase.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAkB/C,MAAM,WAAW,2BAA2B;IAC1C,kGAAkG;IAClG,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAwCD,wBAAgB,0BAA0B,CAAC,OAAO,CAAC,EAAE,2BAA2B,GAAG,gBAAgB,CAgClG"}
package/dist/index.cjs CHANGED
@@ -59,17 +59,59 @@ function createMockBlogProvider(options) {
59
59
  }
60
60
  };
61
61
  }
62
-
63
- // src/data/supabase.ts
64
- function createSupabaseBlogProvider() {
65
- const notImplemented = () => {
66
- throw new Error(
67
- "[plugin-blog] Supabase provider not implemented yet \u2014 deferred to Phase 2. Run on the mock/seed provider (no Supabase client configured) for now."
68
- );
62
+ var PUBLIC_VIEW = "v_public_blog_posts";
63
+ function getClient() {
64
+ const supabase = core.getSupabaseClientOptional();
65
+ if (!supabase) throw new Error("[plugin-blog] Supabase client not initialized");
66
+ return supabase;
67
+ }
68
+ function humanDate(iso) {
69
+ if (!iso) return "";
70
+ const d = new Date(iso);
71
+ if (Number.isNaN(d.getTime())) return "";
72
+ const label = d.toLocaleDateString("pt-BR", { month: "long", year: "numeric" });
73
+ return label.charAt(0).toUpperCase() + label.slice(1);
74
+ }
75
+ function toPost(row) {
76
+ const author = {
77
+ name: row.author_name ?? "Equipe",
78
+ avatarUrl: row.author_avatar_url ?? void 0,
79
+ role: row.author_role ?? void 0,
80
+ bio: row.author_bio ?? void 0
69
81
  };
70
82
  return {
71
- listPosts: (_query) => notImplemented(),
72
- getPost: (_slug) => notImplemented()
83
+ slug: row.slug,
84
+ title: row.title,
85
+ excerpt: row.excerpt ?? "",
86
+ body: row.body ?? "",
87
+ tag: row.tag ?? "",
88
+ readTime: row.read_time ?? "",
89
+ date: humanDate(row.published_at),
90
+ publishedAt: row.published_at ?? void 0,
91
+ image: row.cover_image ?? "",
92
+ author
93
+ };
94
+ }
95
+ function createSupabaseBlogProvider(options) {
96
+ const resolveTenantId = () => options?.tenantId ?? core.getActiveTenantId() ?? void 0;
97
+ return {
98
+ async listPosts(query) {
99
+ const tenantId = resolveTenantId();
100
+ if (!tenantId) return [];
101
+ let q = getClient().from(PUBLIC_VIEW).select("*").eq("tenant_id", tenantId).order("published_at", { ascending: false, nullsFirst: false });
102
+ if (query?.tag) q = q.eq("tag", query.tag);
103
+ if (query?.limit != null) q = q.limit(query.limit);
104
+ const { data, error } = await q;
105
+ if (error) throw error;
106
+ return (data ?? []).map(toPost);
107
+ },
108
+ async getPost(slug) {
109
+ const tenantId = resolveTenantId();
110
+ if (!tenantId) return null;
111
+ const { data, error } = await getClient().from(PUBLIC_VIEW).select("*").eq("tenant_id", tenantId).eq("slug", slug).maybeSingle();
112
+ if (error) throw error;
113
+ return data ? toPost(data) : null;
114
+ }
73
115
  };
74
116
  }
75
117
  var BlogContext = react.createContext(null);
@@ -325,11 +367,120 @@ function PostDetail() {
325
367
  ] }) });
326
368
  }
327
369
 
370
+ // src/migrations/index.ts
371
+ var MIGRATION_001_BLOG = `-- ============================================================================
372
+ -- plugin-blog 001: blog content model + public (anon) read surface
373
+ --
374
+ -- Backs the blog backoffice (managed from the marketing plugin's "Blog" tab) and
375
+ -- the public website surface (/blog, /blog/:slug). Prefix: plg_blog_.
376
+ -- \xA71 plg_blog_categories \u2014 first-class category (name/slug/description)
377
+ -- \xA72 plg_blog_posts \u2014 articles; category_id FK, author byline, status
378
+ -- \xA73 RLS: authenticated tenant CRUD on both tables (the backoffice)
379
+ -- \xA74 v_public_blog_posts \u2014 anon-readable view of PUBLISHED posts (the site)
380
+ --
381
+ -- Anon-access design mirrors plugin-agenda's public booking surface: the public
382
+ -- website runs with the publishable key and no session, so canonical tenant RLS
383
+ -- yields nothing. Anon read is granted ONLY through the column-whitelisted,
384
+ -- published-only view (\xA74); the front provider filters it by tenant_id.
385
+ -- Idempotent + safe to re-run.
386
+ -- ============================================================================
387
+
388
+ -- \xA71 \u2014 categories
389
+ CREATE TABLE IF NOT EXISTS public.plg_blog_categories (
390
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
391
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
392
+ name text NOT NULL,
393
+ slug text NOT NULL,
394
+ description text,
395
+ created_at timestamptz NOT NULL DEFAULT now(),
396
+ updated_at timestamptz NOT NULL DEFAULT now()
397
+ );
398
+ ALTER TABLE public.plg_blog_categories ENABLE ROW LEVEL SECURITY;
399
+ CREATE INDEX IF NOT EXISTS idx_plg_blog_categories_tenant ON public.plg_blog_categories(tenant_id);
400
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_plg_blog_categories_tenant_slug ON public.plg_blog_categories(tenant_id, slug);
401
+
402
+ -- \xA72 \u2014 posts
403
+ CREATE TABLE IF NOT EXISTS public.plg_blog_posts (
404
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
405
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
406
+ category_id uuid REFERENCES public.plg_blog_categories(id) ON DELETE SET NULL,
407
+ slug text NOT NULL,
408
+ title text NOT NULL,
409
+ excerpt text NOT NULL DEFAULT '',
410
+ body text NOT NULL DEFAULT '',
411
+ cover_image text,
412
+ author_name text,
413
+ author_role text,
414
+ author_avatar_url text,
415
+ author_bio text,
416
+ status text NOT NULL DEFAULT 'draft'
417
+ CHECK (status IN ('draft', 'published')),
418
+ read_time text,
419
+ published_at timestamptz,
420
+ created_at timestamptz NOT NULL DEFAULT now(),
421
+ updated_at timestamptz NOT NULL DEFAULT now()
422
+ );
423
+ ALTER TABLE public.plg_blog_posts ENABLE ROW LEVEL SECURITY;
424
+ CREATE INDEX IF NOT EXISTS idx_plg_blog_posts_tenant ON public.plg_blog_posts(tenant_id);
425
+ CREATE INDEX IF NOT EXISTS idx_plg_blog_posts_category ON public.plg_blog_posts(category_id);
426
+ CREATE INDEX IF NOT EXISTS idx_plg_blog_posts_status ON public.plg_blog_posts(tenant_id, status, published_at DESC);
427
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_plg_blog_posts_tenant_slug ON public.plg_blog_posts(tenant_id, slug);
428
+
429
+ -- \xA73 \u2014 RLS: authenticated tenant CRUD (the backoffice writes here)
430
+ DROP POLICY IF EXISTS plg_blog_categories_select ON public.plg_blog_categories;
431
+ DROP POLICY IF EXISTS plg_blog_categories_insert ON public.plg_blog_categories;
432
+ DROP POLICY IF EXISTS plg_blog_categories_update ON public.plg_blog_categories;
433
+ DROP POLICY IF EXISTS plg_blog_categories_delete ON public.plg_blog_categories;
434
+ CREATE POLICY plg_blog_categories_select ON public.plg_blog_categories FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
435
+ CREATE POLICY plg_blog_categories_insert ON public.plg_blog_categories FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
436
+ CREATE POLICY plg_blog_categories_update ON public.plg_blog_categories FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
437
+ CREATE POLICY plg_blog_categories_delete ON public.plg_blog_categories FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
438
+ GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_blog_categories TO authenticated;
439
+
440
+ DROP POLICY IF EXISTS plg_blog_posts_select ON public.plg_blog_posts;
441
+ DROP POLICY IF EXISTS plg_blog_posts_insert ON public.plg_blog_posts;
442
+ DROP POLICY IF EXISTS plg_blog_posts_update ON public.plg_blog_posts;
443
+ DROP POLICY IF EXISTS plg_blog_posts_delete ON public.plg_blog_posts;
444
+ CREATE POLICY plg_blog_posts_select ON public.plg_blog_posts FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
445
+ CREATE POLICY plg_blog_posts_insert ON public.plg_blog_posts FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
446
+ CREATE POLICY plg_blog_posts_update ON public.plg_blog_posts FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
447
+ CREATE POLICY plg_blog_posts_delete ON public.plg_blog_posts FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
448
+ GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_blog_posts TO authenticated;
449
+
450
+ -- \xA74 \u2014 anon-readable public catalog (owner-rights view = deliberate, whitelisted,
451
+ -- PUBLISHED only). The front provider filters by tenant_id. Category name is
452
+ -- surfaced as \`tag\` so the website's BlogPost shape stays unchanged.
453
+ CREATE OR REPLACE VIEW public.v_public_blog_posts AS
454
+ SELECT
455
+ p.id,
456
+ p.tenant_id,
457
+ p.slug,
458
+ p.title,
459
+ p.excerpt,
460
+ p.body,
461
+ COALESCE(c.name, '') AS tag,
462
+ p.cover_image,
463
+ p.author_name,
464
+ p.author_role,
465
+ p.author_avatar_url,
466
+ p.author_bio,
467
+ p.read_time,
468
+ p.published_at
469
+ FROM public.plg_blog_posts p
470
+ LEFT JOIN public.plg_blog_categories c ON c.id = p.category_id
471
+ WHERE p.status = 'published';
472
+
473
+ GRANT SELECT ON public.v_public_blog_posts TO anon, authenticated, service_role;
474
+ `;
475
+ var MIGRATIONS = [
476
+ { id: "001_blog", sql: MIGRATION_001_BLOG }
477
+ ];
478
+
328
479
  // src/index.ts
329
480
  function createBlogPlugin(options) {
330
481
  const config = resolveConfig(options);
331
482
  const provider = options?.dataProvider ?? core.createSafeDataProvider(
332
- () => createSupabaseBlogProvider(),
483
+ () => createSupabaseBlogProvider({ tenantId: options?.tenantId }),
333
484
  () => createMockBlogProvider({ seed: options?.seed, defaultAuthor: options?.defaultAuthor })
334
485
  );
335
486
  const value = { provider, config };
@@ -352,7 +503,15 @@ function createBlogPlugin(options) {
352
503
  { path: config.basePath, component: BlogList, guard: "public" },
353
504
  { path: `${config.basePath}/:slug`, component: PostDetail, guard: "public" }
354
505
  ],
355
- widgets: []
506
+ widgets: [],
507
+ // Owns the blog domain tables (categories + posts + anon-read view). The
508
+ // marketing "Blog" backoffice writes to these; the website reads them.
509
+ migrations: MIGRATIONS.map((m) => ({
510
+ id: `blog-${m.id}`,
511
+ version: "1.0.0",
512
+ sql: m.sql,
513
+ description: "Blog content model (plg_blog_categories, plg_blog_posts) + anon-read view"
514
+ }))
356
515
  };
357
516
  return { manifest, Provider, dataProvider: provider };
358
517
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts","../src/data/mock.ts","../src/data/supabase.ts","../src/context.tsx","../src/hooks/useBlogPosts.ts","../src/components/AuthorAvatar.tsx","../src/components/PostCard.tsx","../src/seo.ts","../src/components/BlogList.tsx","../src/hooks/usePost.ts","../src/components/PostDetail.tsx","../src/index.ts"],"names":["createContext","jsx","useContext","useState","useEffect","jsxs","Link","Clock","absoluteUrl","organizationJsonLd","breadcrumbJsonLd","useSeo","useParams","Fragment","createSafeDataProvider","createElement"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { PluginScope, VerticalId } from '@fayz-ai/core'\nimport type { BlogDataProvider } from './data/types'\nimport type { BlogSeed } from './data/mock'\nimport type { BlogAuthor } from './types'\n\nexport interface BlogPluginOptions {\n /** Base path the plugin mounts its public routes under. Default '/blog'. */\n basePath?: string\n /** Seed posts for the mock provider (used until a real backend is wired). */\n seed?: BlogSeed\n /** Default byline author, applied to any post that doesn't specify its own. */\n defaultAuthor?: BlogAuthor\n /** Publisher/brand name — used for SEO (og:site_name, JSON-LD publisher). */\n siteName?: string\n /** Inject a custom provider (e.g. a CMS). Overrides the safe mock/Supabase resolver. */\n dataProvider?: BlogDataProvider\n /** Section eyebrow / labels for the default list screen. */\n labels?: {\n /** Title shown at the top of the /blog list screen. */\n listTitle?: string\n /** Subtitle under the list title. */\n listSubtitle?: string\n /** \"Back to blog\" link label on the detail screen. */\n backToList?: string\n }\n scope?: PluginScope\n verticalId?: VerticalId\n}\n\nexport interface ResolvedBlogConfig {\n basePath: string\n siteName: string\n labels: {\n listTitle: string\n listSubtitle: string\n backToList: string\n }\n}\n\nexport function resolveConfig(options?: BlogPluginOptions): ResolvedBlogConfig {\n return {\n basePath: options?.basePath ?? '/blog',\n siteName: options?.siteName ?? '',\n labels: {\n listTitle: options?.labels?.listTitle ?? 'Blog',\n listSubtitle: options?.labels?.listSubtitle ?? '',\n backToList: options?.labels?.backToList ?? '← Voltar ao blog',\n },\n }\n}\n","import type { BlogDataProvider } from './types'\nimport type { BlogPost, BlogListQuery, BlogAuthor } from '../types'\n\n/** Slugify a title when a seed post omits an explicit slug. */\nfunction slugify(input: string): string {\n return input\n .normalize('NFD')\n // Drop combining accent marks (and any other non-ASCII) so \"saúde\" -> \"saude\".\n .replace(/[^\\x00-\\x7f]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)/g, '')\n .slice(0, 80)\n}\n\n/** Seed post — same shape as BlogPost but slug/body/author may be derived. */\nexport type BlogSeedPost = Omit<BlogPost, 'slug' | 'body' | 'author'> & {\n slug?: string\n body?: string\n author?: BlogAuthor\n}\n\nexport interface BlogSeed {\n posts: BlogSeedPost[]\n}\n\nexport interface MockBlogProviderOptions {\n seed?: BlogSeed\n /** Fallback byline used for posts without their own author. */\n defaultAuthor?: BlogAuthor\n}\n\nconst FALLBACK_AUTHOR: BlogAuthor = { name: 'Equipe' }\n\nfunction normalize(post: BlogSeedPost, defaultAuthor: BlogAuthor): BlogPost {\n return {\n ...post,\n slug: post.slug ?? slugify(post.title),\n body: post.body ?? post.excerpt,\n author: post.author ?? defaultAuthor,\n }\n}\n\n/** A built-in fallback post so an unseeded plugin still renders something. */\nconst FALLBACK: BlogSeedPost[] = [\n {\n title: 'Bem-vindo ao blog',\n excerpt: 'Este é um artigo de exemplo servido pelo plugin de blog do Fayz.',\n tag: 'Novidades',\n readTime: '2 min',\n date: 'Hoje',\n image: 'https://images.unsplash.com/photo-1499750310107-5fef28a66643?w=800&q=80',\n },\n]\n\nexport function createMockBlogProvider(options?: MockBlogProviderOptions): BlogDataProvider {\n const defaultAuthor = options?.defaultAuthor ?? FALLBACK_AUTHOR\n const posts: BlogPost[] = (options?.seed?.posts ?? FALLBACK).map((p) => normalize(p, defaultAuthor))\n\n return {\n async listPosts(query?: BlogListQuery): Promise<BlogPost[]> {\n let result = posts\n if (query?.tag) result = result.filter((p) => p.tag === query.tag)\n if (query?.limit != null) result = result.slice(0, query.limit)\n return result\n },\n async getPost(slug: string): Promise<BlogPost | null> {\n return posts.find((p) => p.slug === slug) ?? null\n },\n }\n}\n","import type { BlogDataProvider } from './types'\nimport type { BlogPost, BlogListQuery } from '../types'\n\n// ---------------------------------------------------------------------------\n// Supabase-backed blog provider — STUB.\n//\n// Deferred (Phase 2): read from a `blog_posts` table (tenant-scoped, RLS) with\n// columns mirroring BlogPost (slug, title, excerpt, body, tag, read_time, date,\n// image, author). Wiring this up is a pure provider swap — no hook/component\n// change — because everything downstream depends only on BlogDataProvider.\n//\n// Until then this throws, which makes createSafeDataProvider fall back to the\n// mock provider whenever no Supabase client is configured. If a client IS\n// configured but this is still a stub, surface a clear error.\n// ---------------------------------------------------------------------------\n\nexport function createSupabaseBlogProvider(): BlogDataProvider {\n const notImplemented = (): never => {\n throw new Error(\n '[plugin-blog] Supabase provider not implemented yet — deferred to Phase 2. ' +\n 'Run on the mock/seed provider (no Supabase client configured) for now.',\n )\n }\n return {\n listPosts: (_query?: BlogListQuery): Promise<BlogPost[]> => notImplemented(),\n getPost: (_slug: string): Promise<BlogPost | null> => notImplemented(),\n }\n}\n","import { createContext, useContext, type ReactNode } from 'react'\nimport type { BlogDataProvider } from './data/types'\nimport type { ResolvedBlogConfig } from './config'\n\nexport interface BlogContextValue {\n provider: BlogDataProvider\n config: ResolvedBlogConfig\n}\n\nconst BlogContext = createContext<BlogContextValue | null>(null)\n\nexport function BlogProvider({ value, children }: { value: BlogContextValue; children: ReactNode }) {\n return <BlogContext.Provider value={value}>{children}</BlogContext.Provider>\n}\n\n/** Read the blog provider + config. Throws if used outside a BlogProvider. */\nexport function useBlogContext(): BlogContextValue {\n const ctx = useContext(BlogContext)\n if (!ctx) {\n throw new Error('[plugin-blog] useBlogContext must be used within <BlogProvider>. Mount the plugin Provider at the app root.')\n }\n return ctx\n}\n","import { useEffect, useState } from 'react'\nimport { useBlogContext } from '../context'\nimport type { BlogPost, BlogListQuery } from '../types'\n\nexport interface UseBlogPostsResult {\n posts: BlogPost[]\n loading: boolean\n error: Error | null\n}\n\n/**\n * Fetch the post list from the active blog provider. The host maps `posts` into\n * its own card markup; the plugin never dictates presentation.\n */\nexport function useBlogPosts(query?: BlogListQuery): UseBlogPostsResult {\n const { provider } = useBlogContext()\n const [posts, setPosts] = useState<BlogPost[]>([])\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n const limit = query?.limit\n const tag = query?.tag\n\n useEffect(() => {\n let active = true\n setLoading(true)\n provider\n .listPosts({ limit, tag })\n .then((result) => {\n if (!active) return\n setPosts(result)\n setError(null)\n })\n .catch((err) => {\n if (!active) return\n setError(err instanceof Error ? err : new Error(String(err)))\n })\n .finally(() => {\n if (active) setLoading(false)\n })\n return () => {\n active = false\n }\n }, [provider, limit, tag])\n\n return { posts, loading, error }\n}\n","import type { BlogAuthor } from '../types'\n\n/** Round author avatar with a name-initial fallback (token-only). */\nexport function AuthorAvatar({ author, className = 'h-8 w-8 text-xs' }: { author: BlogAuthor; className?: string }) {\n if (author.avatarUrl) {\n return (\n <img\n src={author.avatarUrl}\n alt={author.name}\n loading=\"lazy\"\n className={`shrink-0 rounded-full object-cover ${className}`}\n />\n )\n }\n return (\n <span className={`flex shrink-0 items-center justify-center rounded-full bg-accent font-semibold text-primary ${className}`}>\n {author.name.charAt(0).toUpperCase()}\n </span>\n )\n}\n","import { Link } from 'react-router-dom'\nimport { Clock } from 'lucide-react'\nimport type { BlogPost } from '../types'\nimport { AuthorAvatar } from './AuthorAvatar'\n\n/**\n * Default post card — token-only so it inherits the host theme. Optional: a host\n * with bespoke card markup can ignore this and map `useBlogPosts()` itself.\n */\nexport function PostCard({ post, basePath = '/blog' }: { post: BlogPost; basePath?: string }) {\n return (\n <Link\n to={`${basePath}/${post.slug}`}\n className=\"group flex flex-col rounded-2xl border border-border bg-card overflow-hidden shadow-sm hover:shadow-lg hover:-translate-y-1 transition-all duration-300\"\n >\n <div className=\"overflow-hidden h-48\">\n <img\n src={post.image}\n alt={post.title}\n loading=\"lazy\"\n className=\"h-full w-full object-cover transition-transform duration-500 group-hover:scale-105\"\n />\n </div>\n <div className=\"flex flex-col flex-1 p-6\">\n <span className=\"inline-block rounded-full bg-accent px-3 py-0.5 text-xs font-semibold text-primary mb-3 w-fit\">\n {post.tag}\n </span>\n <h3 className=\"font-heading text-lg font-semibold text-foreground leading-snug mb-3 group-hover:text-primary transition-colors duration-200\">\n {post.title}\n </h3>\n <p className=\"text-muted-foreground text-sm leading-relaxed flex-1 mb-4\">{post.excerpt}</p>\n <div className=\"flex items-center gap-2.5 pt-4 border-t border-border\">\n <AuthorAvatar author={post.author} className=\"h-7 w-7 text-xs\" />\n <div className=\"min-w-0\">\n <p className=\"truncate text-xs font-semibold text-foreground\">{post.author.name}</p>\n <p className=\"flex items-center gap-1 text-[11px] text-muted-foreground\">\n {post.date}<span aria-hidden>·</span><Clock className=\"h-3 w-3\" />{post.readTime}\n </p>\n </div>\n </div>\n </div>\n </Link>\n )\n}\n","import { absoluteUrl, breadcrumbJsonLd, organizationJsonLd } from '@fayz-ai/core'\nimport type { BlogPost } from './types'\n\n// ---------------------------------------------------------------------------\n// Blog-specific schema.org builders. The head-manager mechanics (useSeo /\n// applySeo / meta+OG+Twitter) live in @fayz-ai/core — this only owns the\n// domain shapes (BlogPosting, Blog) that plug into core's SeoConfig.jsonLd.\n// ---------------------------------------------------------------------------\n\n/** Absolute canonical URL for a post. */\nexport function postUrl(basePath: string, slug: string): string {\n return absoluteUrl(`${basePath}/${slug}`)\n}\n\n/** schema.org BlogPosting + BreadcrumbList for an article page. */\nexport function articleJsonLd(post: BlogPost, opts: { basePath: string; siteName: string }): Array<Record<string, unknown>> {\n const url = postUrl(opts.basePath, post.slug)\n const posting: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': 'BlogPosting',\n headline: post.title,\n description: post.excerpt,\n image: post.image ? [post.image] : undefined,\n articleSection: post.tag,\n mainEntityOfPage: { '@type': 'WebPage', '@id': url },\n author: {\n '@type': 'Person',\n name: post.author.name,\n ...(post.author.role ? { jobTitle: post.author.role } : {}),\n },\n ...(post.publishedAt ? { datePublished: post.publishedAt, dateModified: post.publishedAt } : {}),\n ...(opts.siteName ? { publisher: organizationJsonLd(opts.siteName) } : {}),\n }\n const breadcrumb = breadcrumbJsonLd([\n { name: 'Blog', url: absoluteUrl(opts.basePath) },\n { name: post.title, url },\n ])\n return [posting, breadcrumb]\n}\n\n/** schema.org Blog + post list for the listing page. */\nexport function blogJsonLd(posts: BlogPost[], opts: { basePath: string; siteName: string; title: string }): Array<Record<string, unknown>> {\n return [\n {\n '@context': 'https://schema.org',\n '@type': 'Blog',\n name: opts.title,\n url: absoluteUrl(opts.basePath),\n ...(opts.siteName ? { publisher: organizationJsonLd(opts.siteName) } : {}),\n blogPost: posts.map((p) => ({\n '@type': 'BlogPosting',\n headline: p.title,\n url: postUrl(opts.basePath, p.slug),\n image: p.image,\n author: { '@type': 'Person', name: p.author.name },\n ...(p.publishedAt ? { datePublished: p.publishedAt } : {}),\n })),\n },\n ]\n}\n\n// Re-export the generic hook so blog components import SEO from one place.\nexport { useSeo } from '@fayz-ai/core'\n","import { useBlogPosts } from '../hooks/useBlogPosts'\nimport { useBlogContext } from '../context'\nimport { PostCard } from './PostCard'\nimport { useSeo, blogJsonLd } from '../seo'\n\n/**\n * Full /blog list screen. Rendered inside the host's chrome (Header/Footer),\n * so it only owns the section body — token-only for theme inheritance.\n */\nexport function BlogList() {\n const { config } = useBlogContext()\n const { posts, loading, error } = useBlogPosts()\n\n const listTitle = config.labels.listSubtitle || config.labels.listTitle\n useSeo({\n title: `${listTitle}${config.siteName ? ` · ${config.siteName}` : ''}`,\n description: config.labels.listSubtitle || `Artigos do blog${config.siteName ? ` da ${config.siteName}` : ''}.`,\n canonical: `${typeof window !== 'undefined' ? window.location.origin : ''}${config.basePath}`,\n type: 'website',\n siteName: config.siteName,\n jsonLd: posts.length ? blogJsonLd(posts, { basePath: config.basePath, siteName: config.siteName, title: listTitle }) : undefined,\n })\n\n return (\n <section className=\"py-20 bg-background\">\n <div className=\"container mx-auto px-6\">\n <div className=\"mb-12 max-w-2xl\">\n <span className=\"inline-block rounded-full bg-accent px-4 py-1 text-xs font-semibold uppercase tracking-widest text-primary mb-4\">\n {config.labels.listTitle}\n </span>\n {config.labels.listSubtitle ? (\n <h1 className=\"font-heading text-4xl md:text-5xl font-bold text-foreground\">\n {config.labels.listSubtitle}\n </h1>\n ) : null}\n </div>\n\n {loading ? (\n <p className=\"text-muted-foreground\">Carregando artigos…</p>\n ) : error ? (\n <p className=\"text-muted-foreground\">Não foi possível carregar os artigos.</p>\n ) : posts.length === 0 ? (\n <p className=\"text-muted-foreground\">Nenhum artigo publicado ainda.</p>\n ) : (\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {posts.map((post) => (\n <PostCard key={post.slug} post={post} basePath={config.basePath} />\n ))}\n </div>\n )}\n </div>\n </section>\n )\n}\n","import { useEffect, useState } from 'react'\nimport { useBlogContext } from '../context'\nimport type { BlogPost } from '../types'\n\nexport interface UsePostResult {\n post: BlogPost | null\n loading: boolean\n error: Error | null\n}\n\n/** Fetch a single post by slug from the active blog provider. */\nexport function usePost(slug: string | undefined): UsePostResult {\n const { provider } = useBlogContext()\n const [post, setPost] = useState<BlogPost | null>(null)\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n useEffect(() => {\n let active = true\n if (!slug) {\n setPost(null)\n setLoading(false)\n return\n }\n setLoading(true)\n provider\n .getPost(slug)\n .then((result) => {\n if (!active) return\n setPost(result)\n setError(null)\n })\n .catch((err) => {\n if (!active) return\n setError(err instanceof Error ? err : new Error(String(err)))\n })\n .finally(() => {\n if (active) setLoading(false)\n })\n return () => {\n active = false\n }\n }, [provider, slug])\n\n return { post, loading, error }\n}\n","import { Link, useParams } from 'react-router-dom'\nimport { Clock } from 'lucide-react'\nimport { usePost } from '../hooks/usePost'\nimport { useBlogContext } from '../context'\nimport { AuthorAvatar } from './AuthorAvatar'\nimport { useSeo, articleJsonLd, postUrl } from '../seo'\n\n/**\n * /blog/:slug detail screen. Reads the slug from the router and renders the\n * article inside the host chrome. Token-only for theme inheritance. Sets rich\n * SEO/JSON-LD per article (BlogPosting + BreadcrumbList).\n */\nexport function PostDetail() {\n const { config } = useBlogContext()\n const { slug } = useParams<{ slug: string }>()\n const { post, loading, error } = usePost(slug)\n\n useSeo(\n post\n ? {\n title: `${post.title}${config.siteName ? ` · ${config.siteName}` : ''}`,\n description: post.excerpt,\n canonical: postUrl(config.basePath, post.slug),\n image: post.image,\n type: 'article',\n siteName: config.siteName,\n jsonLd: articleJsonLd(post, { basePath: config.basePath, siteName: config.siteName }),\n }\n : { title: config.siteName || 'Blog' },\n )\n\n const paragraphs = post?.body ? post.body.split(/\\n\\s*\\n/).map((p) => p.trim()).filter(Boolean) : []\n\n return (\n <article className=\"py-16 bg-background\">\n <div className=\"container mx-auto px-6 max-w-3xl\">\n <Link to={config.basePath} className=\"text-sm font-medium text-primary hover:underline\">\n {config.labels.backToList}\n </Link>\n\n {loading ? (\n <p className=\"mt-10 text-muted-foreground\">Carregando…</p>\n ) : error || !post ? (\n <div className=\"mt-10\">\n <h1 className=\"font-heading text-3xl font-bold text-foreground\">Artigo não encontrado</h1>\n <p className=\"mt-2 text-muted-foreground\">Este artigo pode ter sido movido ou removido.</p>\n </div>\n ) : (\n <>\n <h1 className=\"mt-8 font-heading text-4xl md:text-5xl font-bold text-foreground leading-tight\">\n {post.title}\n </h1>\n\n {/* Medium-style byline */}\n <div className=\"mt-6 flex items-center gap-3\">\n <AuthorAvatar author={post.author} className=\"h-11 w-11 text-base\" />\n <div>\n <p className=\"font-semibold text-foreground\">{post.author.name}</p>\n <p className=\"flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground\">\n {post.author.role ? <span>{post.author.role}<span aria-hidden> · </span></span> : null}\n <time dateTime={post.publishedAt}>{post.date}</time>\n <span className=\"flex items-center gap-1\"><Clock className=\"h-3 w-3\" />{post.readTime} de leitura</span>\n </p>\n </div>\n </div>\n\n <div className=\"mt-8 overflow-hidden rounded-2xl\">\n <img src={post.image} alt={post.title} className=\"w-full object-cover\" loading=\"lazy\" />\n </div>\n\n <div className=\"mt-8 space-y-5\">\n <p className=\"text-lg text-foreground/90 leading-relaxed font-medium\">{post.excerpt}</p>\n {paragraphs.map((para, i) => (\n <p key={i} className=\"text-muted-foreground leading-relaxed\">\n {para}\n </p>\n ))}\n </div>\n\n {/* Category tag — at the bottom, Medium-style */}\n <div className=\"mt-10 flex flex-wrap items-center gap-2 border-t border-border pt-6\">\n <span className=\"text-sm text-muted-foreground\">Categoria:</span>\n <Link\n to={config.basePath}\n className=\"rounded-full bg-accent px-3 py-1 text-xs font-semibold text-primary transition-colors hover:bg-accent/70\"\n >\n {post.tag}\n </Link>\n </div>\n\n {/* About the author */}\n {post.author.bio ? (\n <div className=\"mt-8 flex items-start gap-4 rounded-2xl border border-border bg-muted/40 p-6\">\n <AuthorAvatar author={post.author} className=\"h-14 w-14 text-lg\" />\n <div>\n <p className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">Escrito por</p>\n <p className=\"font-heading text-lg font-bold text-foreground\">{post.author.name}</p>\n {post.author.role ? <p className=\"text-sm text-primary\">{post.author.role}</p> : null}\n <p className=\"mt-2 text-sm text-muted-foreground leading-relaxed\">{post.author.bio}</p>\n </div>\n </div>\n ) : null}\n </>\n )}\n </div>\n </article>\n )\n}\n","import { createElement, type FC, type ReactNode } from 'react'\nimport type { PluginManifest } from '@fayz-ai/core'\nimport { createSafeDataProvider } from '@fayz-ai/core'\nimport { resolveConfig, type BlogPluginOptions } from './config'\nimport { createMockBlogProvider } from './data/mock'\nimport { createSupabaseBlogProvider } from './data/supabase'\nimport { BlogProvider, type BlogContextValue } from './context'\nimport { BlogList } from './components/BlogList'\nimport { PostDetail } from './components/PostDetail'\n\n// ---------------------------------------------------------------------------\n// @fayz-ai/plugin-blog — website-surface blog/articles plugin.\n//\n// First real consumer of scaffolds:['website']. Ships public routes (/blog,\n// /blog/:slug) + data hooks (useBlogPosts/usePost). A bespoke host site feeds\n// its own card markup from the hooks and mounts the routes inside its chrome.\n// See createBlogPlugin's return: a { manifest, Provider } bundle the host reads\n// uniformly (manifest.routes for mounting, Provider for the app-root context).\n// ---------------------------------------------------------------------------\n\nexport interface BlogWebsitePlugin {\n /** Standard plugin manifest (routes + metadata). */\n manifest: PluginManifest\n /** App-root context provider, pre-bound to this plugin's resolved provider/config. */\n Provider: FC<{ children: ReactNode }>\n /** The resolved data provider (exposed for advanced host wiring/tests). */\n dataProvider: BlogContextValue['provider']\n}\n\nexport function createBlogPlugin(options?: BlogPluginOptions): BlogWebsitePlugin {\n const config = resolveConfig(options)\n const provider =\n options?.dataProvider ??\n createSafeDataProvider(\n () => createSupabaseBlogProvider(),\n () => createMockBlogProvider({ seed: options?.seed, defaultAuthor: options?.defaultAuthor }),\n )\n\n const value: BlogContextValue = { provider, config }\n const Provider: FC<{ children: ReactNode }> = ({ children }) =>\n createElement(BlogProvider, { value, children })\n Provider.displayName = 'BlogPluginProvider'\n\n const manifest: PluginManifest = {\n id: 'blog',\n name: 'Blog',\n icon: 'Newspaper',\n version: '0.1.0',\n scope: options?.scope ?? 'universal',\n verticalId: options?.verticalId,\n scaffolds: ['website', 'landing_page'],\n defaultEnabled: true,\n dependencies: [],\n declaredFeatures: [{ id: 'blog', label: 'Blog', group: 'Attract' }],\n // Host owns website nav; no admin nav in M1.\n navigation: [],\n routes: [\n { path: config.basePath, component: BlogList, guard: 'public' },\n { path: `${config.basePath}/:slug`, component: PostDetail, guard: 'public' },\n ],\n widgets: [],\n }\n\n return { manifest, Provider, dataProvider: provider }\n}\n\n// Public API\nexport type { BlogPluginOptions, ResolvedBlogConfig } from './config'\nexport type { BlogPost, BlogListQuery, BlogAuthor } from './types'\nexport type { BlogDataProvider } from './data/types'\nexport { createMockBlogProvider, createSupabaseBlogProvider } from './data'\nexport type { BlogSeed, BlogSeedPost } from './data/mock'\nexport { BlogProvider, useBlogContext } from './context'\nexport type { BlogContextValue } from './context'\nexport { useBlogPosts, usePost } from './hooks'\nexport type { UseBlogPostsResult, UsePostResult } from './hooks'\nexport { BlogList } from './components/BlogList'\nexport { PostDetail } from './components/PostDetail'\nexport { PostCard } from './components/PostCard'\nexport { AuthorAvatar } from './components/AuthorAvatar'\n"]}
1
+ {"version":3,"sources":["../src/config.ts","../src/data/mock.ts","../src/data/supabase.ts","../src/context.tsx","../src/hooks/useBlogPosts.ts","../src/components/AuthorAvatar.tsx","../src/components/PostCard.tsx","../src/seo.ts","../src/components/BlogList.tsx","../src/hooks/usePost.ts","../src/components/PostDetail.tsx","../src/migrations/index.ts","../src/index.ts"],"names":["getSupabaseClientOptional","getActiveTenantId","createContext","jsx","useContext","useState","useEffect","jsxs","Link","Clock","absoluteUrl","organizationJsonLd","breadcrumbJsonLd","useSeo","useParams","Fragment","createSafeDataProvider","createElement"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { PluginScope, VerticalId } from '@fayz-ai/core'\nimport type { BlogDataProvider } from './data/types'\nimport type { BlogSeed } from './data/mock'\nimport type { BlogAuthor } from './types'\n\nexport interface BlogPluginOptions {\n /** Base path the plugin mounts its public routes under. Default '/blog'. */\n basePath?: string\n /** Seed posts for the mock provider (used until a real backend is wired). */\n seed?: BlogSeed\n /** Default byline author, applied to any post that doesn't specify its own. */\n defaultAuthor?: BlogAuthor\n /** Publisher/brand name — used for SEO (og:site_name, JSON-LD publisher). */\n siteName?: string\n /** Inject a custom provider (e.g. a CMS). Overrides the safe mock/Supabase resolver. */\n dataProvider?: BlogDataProvider\n /** Tenant whose PUBLISHED posts the public (anon) site reads from Supabase.\n * Required for the Supabase provider on an anon website (mirrors how the\n * public booking plugin takes an explicit tenantId). Without it the safe\n * resolver falls back to the mock/seed provider. */\n tenantId?: string\n /** Section eyebrow / labels for the default list screen. */\n labels?: {\n /** Title shown at the top of the /blog list screen. */\n listTitle?: string\n /** Subtitle under the list title. */\n listSubtitle?: string\n /** \"Back to blog\" link label on the detail screen. */\n backToList?: string\n }\n scope?: PluginScope\n verticalId?: VerticalId\n}\n\nexport interface ResolvedBlogConfig {\n basePath: string\n siteName: string\n labels: {\n listTitle: string\n listSubtitle: string\n backToList: string\n }\n}\n\nexport function resolveConfig(options?: BlogPluginOptions): ResolvedBlogConfig {\n return {\n basePath: options?.basePath ?? '/blog',\n siteName: options?.siteName ?? '',\n labels: {\n listTitle: options?.labels?.listTitle ?? 'Blog',\n listSubtitle: options?.labels?.listSubtitle ?? '',\n backToList: options?.labels?.backToList ?? '← Voltar ao blog',\n },\n }\n}\n","import type { BlogDataProvider } from './types'\nimport type { BlogPost, BlogListQuery, BlogAuthor } from '../types'\n\n/** Slugify a title when a seed post omits an explicit slug. */\nfunction slugify(input: string): string {\n return input\n .normalize('NFD')\n // Drop combining accent marks (and any other non-ASCII) so \"saúde\" -> \"saude\".\n .replace(/[^\\x00-\\x7f]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)/g, '')\n .slice(0, 80)\n}\n\n/** Seed post — same shape as BlogPost but slug/body/author may be derived. */\nexport type BlogSeedPost = Omit<BlogPost, 'slug' | 'body' | 'author'> & {\n slug?: string\n body?: string\n author?: BlogAuthor\n}\n\nexport interface BlogSeed {\n posts: BlogSeedPost[]\n}\n\nexport interface MockBlogProviderOptions {\n seed?: BlogSeed\n /** Fallback byline used for posts without their own author. */\n defaultAuthor?: BlogAuthor\n}\n\nconst FALLBACK_AUTHOR: BlogAuthor = { name: 'Equipe' }\n\nfunction normalize(post: BlogSeedPost, defaultAuthor: BlogAuthor): BlogPost {\n return {\n ...post,\n slug: post.slug ?? slugify(post.title),\n body: post.body ?? post.excerpt,\n author: post.author ?? defaultAuthor,\n }\n}\n\n/** A built-in fallback post so an unseeded plugin still renders something. */\nconst FALLBACK: BlogSeedPost[] = [\n {\n title: 'Bem-vindo ao blog',\n excerpt: 'Este é um artigo de exemplo servido pelo plugin de blog do Fayz.',\n tag: 'Novidades',\n readTime: '2 min',\n date: 'Hoje',\n image: 'https://images.unsplash.com/photo-1499750310107-5fef28a66643?w=800&q=80',\n },\n]\n\nexport function createMockBlogProvider(options?: MockBlogProviderOptions): BlogDataProvider {\n const defaultAuthor = options?.defaultAuthor ?? FALLBACK_AUTHOR\n const posts: BlogPost[] = (options?.seed?.posts ?? FALLBACK).map((p) => normalize(p, defaultAuthor))\n\n return {\n async listPosts(query?: BlogListQuery): Promise<BlogPost[]> {\n let result = posts\n if (query?.tag) result = result.filter((p) => p.tag === query.tag)\n if (query?.limit != null) result = result.slice(0, query.limit)\n return result\n },\n async getPost(slug: string): Promise<BlogPost | null> {\n return posts.find((p) => p.slug === slug) ?? null\n },\n }\n}\n","import { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\nimport type { BlogDataProvider } from './types'\nimport type { BlogPost, BlogListQuery, BlogAuthor } from '../types'\n\n// ---------------------------------------------------------------------------\n// Supabase-backed blog provider (public/website read side).\n//\n// Reads PUBLISHED posts from the anon-safe view public.v_public_blog_posts\n// (see src/migrations/001_blog.sql §4), scoped to a tenant. The backoffice\n// writes to plg_blog_posts directly via the generic CRUD provider; here we only\n// read the published, column-whitelisted view — safe under the publishable key.\n//\n// The tenant is taken from the configured tenantId (anon site) or, failing that,\n// the active session tenant. Without a client or a tenant we return nothing, and\n// createSafeDataProvider falls back to the mock/seed provider.\n// ---------------------------------------------------------------------------\n\nconst PUBLIC_VIEW = 'v_public_blog_posts'\n\nexport interface SupabaseBlogProviderOptions {\n /** Tenant whose published posts to read (anon website). Defaults to the active session tenant. */\n tenantId?: string\n}\n\nfunction getClient() {\n const supabase = getSupabaseClientOptional() as {\n from: (t: string) => any\n } | null\n if (!supabase) throw new Error('[plugin-blog] Supabase client not initialized')\n return supabase\n}\n\n/** Human month-year label (pt-BR) derived from an ISO date, e.g. \"Janeiro 2025\". */\nfunction humanDate(iso?: string | null): string {\n if (!iso) return ''\n const d = new Date(iso)\n if (Number.isNaN(d.getTime())) return ''\n const label = d.toLocaleDateString('pt-BR', { month: 'long', year: 'numeric' })\n return label.charAt(0).toUpperCase() + label.slice(1)\n}\n\nfunction toPost(row: Record<string, any>): BlogPost {\n const author: BlogAuthor = {\n name: row.author_name ?? 'Equipe',\n avatarUrl: row.author_avatar_url ?? undefined,\n role: row.author_role ?? undefined,\n bio: row.author_bio ?? undefined,\n }\n return {\n slug: row.slug,\n title: row.title,\n excerpt: row.excerpt ?? '',\n body: row.body ?? '',\n tag: row.tag ?? '',\n readTime: row.read_time ?? '',\n date: humanDate(row.published_at),\n publishedAt: row.published_at ?? undefined,\n image: row.cover_image ?? '',\n author,\n }\n}\n\nexport function createSupabaseBlogProvider(options?: SupabaseBlogProviderOptions): BlogDataProvider {\n const resolveTenantId = (): string | undefined => options?.tenantId ?? getActiveTenantId() ?? undefined\n\n return {\n async listPosts(query?: BlogListQuery): Promise<BlogPost[]> {\n const tenantId = resolveTenantId()\n if (!tenantId) return []\n let q = getClient()\n .from(PUBLIC_VIEW)\n .select('*')\n .eq('tenant_id', tenantId)\n .order('published_at', { ascending: false, nullsFirst: false })\n if (query?.tag) q = q.eq('tag', query.tag)\n if (query?.limit != null) q = q.limit(query.limit)\n const { data, error } = await q\n if (error) throw error\n return (data ?? []).map(toPost)\n },\n\n async getPost(slug: string): Promise<BlogPost | null> {\n const tenantId = resolveTenantId()\n if (!tenantId) return null\n const { data, error } = await getClient()\n .from(PUBLIC_VIEW)\n .select('*')\n .eq('tenant_id', tenantId)\n .eq('slug', slug)\n .maybeSingle()\n if (error) throw error\n return data ? toPost(data) : null\n },\n }\n}\n","import { createContext, useContext, type ReactNode } from 'react'\nimport type { BlogDataProvider } from './data/types'\nimport type { ResolvedBlogConfig } from './config'\n\nexport interface BlogContextValue {\n provider: BlogDataProvider\n config: ResolvedBlogConfig\n}\n\nconst BlogContext = createContext<BlogContextValue | null>(null)\n\nexport function BlogProvider({ value, children }: { value: BlogContextValue; children: ReactNode }) {\n return <BlogContext.Provider value={value}>{children}</BlogContext.Provider>\n}\n\n/** Read the blog provider + config. Throws if used outside a BlogProvider. */\nexport function useBlogContext(): BlogContextValue {\n const ctx = useContext(BlogContext)\n if (!ctx) {\n throw new Error('[plugin-blog] useBlogContext must be used within <BlogProvider>. Mount the plugin Provider at the app root.')\n }\n return ctx\n}\n","import { useEffect, useState } from 'react'\nimport { useBlogContext } from '../context'\nimport type { BlogPost, BlogListQuery } from '../types'\n\nexport interface UseBlogPostsResult {\n posts: BlogPost[]\n loading: boolean\n error: Error | null\n}\n\n/**\n * Fetch the post list from the active blog provider. The host maps `posts` into\n * its own card markup; the plugin never dictates presentation.\n */\nexport function useBlogPosts(query?: BlogListQuery): UseBlogPostsResult {\n const { provider } = useBlogContext()\n const [posts, setPosts] = useState<BlogPost[]>([])\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n const limit = query?.limit\n const tag = query?.tag\n\n useEffect(() => {\n let active = true\n setLoading(true)\n provider\n .listPosts({ limit, tag })\n .then((result) => {\n if (!active) return\n setPosts(result)\n setError(null)\n })\n .catch((err) => {\n if (!active) return\n setError(err instanceof Error ? err : new Error(String(err)))\n })\n .finally(() => {\n if (active) setLoading(false)\n })\n return () => {\n active = false\n }\n }, [provider, limit, tag])\n\n return { posts, loading, error }\n}\n","import type { BlogAuthor } from '../types'\n\n/** Round author avatar with a name-initial fallback (token-only). */\nexport function AuthorAvatar({ author, className = 'h-8 w-8 text-xs' }: { author: BlogAuthor; className?: string }) {\n if (author.avatarUrl) {\n return (\n <img\n src={author.avatarUrl}\n alt={author.name}\n loading=\"lazy\"\n className={`shrink-0 rounded-full object-cover ${className}`}\n />\n )\n }\n return (\n <span className={`flex shrink-0 items-center justify-center rounded-full bg-accent font-semibold text-primary ${className}`}>\n {author.name.charAt(0).toUpperCase()}\n </span>\n )\n}\n","import { Link } from 'react-router-dom'\nimport { Clock } from 'lucide-react'\nimport type { BlogPost } from '../types'\nimport { AuthorAvatar } from './AuthorAvatar'\n\n/**\n * Default post card — token-only so it inherits the host theme. Optional: a host\n * with bespoke card markup can ignore this and map `useBlogPosts()` itself.\n */\nexport function PostCard({ post, basePath = '/blog' }: { post: BlogPost; basePath?: string }) {\n return (\n <Link\n to={`${basePath}/${post.slug}`}\n className=\"group flex flex-col rounded-2xl border border-border bg-card overflow-hidden shadow-sm hover:shadow-lg hover:-translate-y-1 transition-all duration-300\"\n >\n <div className=\"overflow-hidden h-48\">\n <img\n src={post.image}\n alt={post.title}\n loading=\"lazy\"\n className=\"h-full w-full object-cover transition-transform duration-500 group-hover:scale-105\"\n />\n </div>\n <div className=\"flex flex-col flex-1 p-6\">\n <span className=\"inline-block rounded-full bg-accent px-3 py-0.5 text-xs font-semibold text-primary mb-3 w-fit\">\n {post.tag}\n </span>\n <h3 className=\"font-heading text-lg font-semibold text-foreground leading-snug mb-3 group-hover:text-primary transition-colors duration-200\">\n {post.title}\n </h3>\n <p className=\"text-muted-foreground text-sm leading-relaxed flex-1 mb-4\">{post.excerpt}</p>\n <div className=\"flex items-center gap-2.5 pt-4 border-t border-border\">\n <AuthorAvatar author={post.author} className=\"h-7 w-7 text-xs\" />\n <div className=\"min-w-0\">\n <p className=\"truncate text-xs font-semibold text-foreground\">{post.author.name}</p>\n <p className=\"flex items-center gap-1 text-[11px] text-muted-foreground\">\n {post.date}<span aria-hidden>·</span><Clock className=\"h-3 w-3\" />{post.readTime}\n </p>\n </div>\n </div>\n </div>\n </Link>\n )\n}\n","import { absoluteUrl, breadcrumbJsonLd, organizationJsonLd } from '@fayz-ai/core'\nimport type { BlogPost } from './types'\n\n// ---------------------------------------------------------------------------\n// Blog-specific schema.org builders. The head-manager mechanics (useSeo /\n// applySeo / meta+OG+Twitter) live in @fayz-ai/core — this only owns the\n// domain shapes (BlogPosting, Blog) that plug into core's SeoConfig.jsonLd.\n// ---------------------------------------------------------------------------\n\n/** Absolute canonical URL for a post. */\nexport function postUrl(basePath: string, slug: string): string {\n return absoluteUrl(`${basePath}/${slug}`)\n}\n\n/** schema.org BlogPosting + BreadcrumbList for an article page. */\nexport function articleJsonLd(post: BlogPost, opts: { basePath: string; siteName: string }): Array<Record<string, unknown>> {\n const url = postUrl(opts.basePath, post.slug)\n const posting: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': 'BlogPosting',\n headline: post.title,\n description: post.excerpt,\n image: post.image ? [post.image] : undefined,\n articleSection: post.tag,\n mainEntityOfPage: { '@type': 'WebPage', '@id': url },\n author: {\n '@type': 'Person',\n name: post.author.name,\n ...(post.author.role ? { jobTitle: post.author.role } : {}),\n },\n ...(post.publishedAt ? { datePublished: post.publishedAt, dateModified: post.publishedAt } : {}),\n ...(opts.siteName ? { publisher: organizationJsonLd(opts.siteName) } : {}),\n }\n const breadcrumb = breadcrumbJsonLd([\n { name: 'Blog', url: absoluteUrl(opts.basePath) },\n { name: post.title, url },\n ])\n return [posting, breadcrumb]\n}\n\n/** schema.org Blog + post list for the listing page. */\nexport function blogJsonLd(posts: BlogPost[], opts: { basePath: string; siteName: string; title: string }): Array<Record<string, unknown>> {\n return [\n {\n '@context': 'https://schema.org',\n '@type': 'Blog',\n name: opts.title,\n url: absoluteUrl(opts.basePath),\n ...(opts.siteName ? { publisher: organizationJsonLd(opts.siteName) } : {}),\n blogPost: posts.map((p) => ({\n '@type': 'BlogPosting',\n headline: p.title,\n url: postUrl(opts.basePath, p.slug),\n image: p.image,\n author: { '@type': 'Person', name: p.author.name },\n ...(p.publishedAt ? { datePublished: p.publishedAt } : {}),\n })),\n },\n ]\n}\n\n// Re-export the generic hook so blog components import SEO from one place.\nexport { useSeo } from '@fayz-ai/core'\n","import { useBlogPosts } from '../hooks/useBlogPosts'\nimport { useBlogContext } from '../context'\nimport { PostCard } from './PostCard'\nimport { useSeo, blogJsonLd } from '../seo'\n\n/**\n * Full /blog list screen. Rendered inside the host's chrome (Header/Footer),\n * so it only owns the section body — token-only for theme inheritance.\n */\nexport function BlogList() {\n const { config } = useBlogContext()\n const { posts, loading, error } = useBlogPosts()\n\n const listTitle = config.labels.listSubtitle || config.labels.listTitle\n useSeo({\n title: `${listTitle}${config.siteName ? ` · ${config.siteName}` : ''}`,\n description: config.labels.listSubtitle || `Artigos do blog${config.siteName ? ` da ${config.siteName}` : ''}.`,\n canonical: `${typeof window !== 'undefined' ? window.location.origin : ''}${config.basePath}`,\n type: 'website',\n siteName: config.siteName,\n jsonLd: posts.length ? blogJsonLd(posts, { basePath: config.basePath, siteName: config.siteName, title: listTitle }) : undefined,\n })\n\n return (\n <section className=\"py-20 bg-background\">\n <div className=\"container mx-auto px-6\">\n <div className=\"mb-12 max-w-2xl\">\n <span className=\"inline-block rounded-full bg-accent px-4 py-1 text-xs font-semibold uppercase tracking-widest text-primary mb-4\">\n {config.labels.listTitle}\n </span>\n {config.labels.listSubtitle ? (\n <h1 className=\"font-heading text-4xl md:text-5xl font-bold text-foreground\">\n {config.labels.listSubtitle}\n </h1>\n ) : null}\n </div>\n\n {loading ? (\n <p className=\"text-muted-foreground\">Carregando artigos…</p>\n ) : error ? (\n <p className=\"text-muted-foreground\">Não foi possível carregar os artigos.</p>\n ) : posts.length === 0 ? (\n <p className=\"text-muted-foreground\">Nenhum artigo publicado ainda.</p>\n ) : (\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {posts.map((post) => (\n <PostCard key={post.slug} post={post} basePath={config.basePath} />\n ))}\n </div>\n )}\n </div>\n </section>\n )\n}\n","import { useEffect, useState } from 'react'\nimport { useBlogContext } from '../context'\nimport type { BlogPost } from '../types'\n\nexport interface UsePostResult {\n post: BlogPost | null\n loading: boolean\n error: Error | null\n}\n\n/** Fetch a single post by slug from the active blog provider. */\nexport function usePost(slug: string | undefined): UsePostResult {\n const { provider } = useBlogContext()\n const [post, setPost] = useState<BlogPost | null>(null)\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n useEffect(() => {\n let active = true\n if (!slug) {\n setPost(null)\n setLoading(false)\n return\n }\n setLoading(true)\n provider\n .getPost(slug)\n .then((result) => {\n if (!active) return\n setPost(result)\n setError(null)\n })\n .catch((err) => {\n if (!active) return\n setError(err instanceof Error ? err : new Error(String(err)))\n })\n .finally(() => {\n if (active) setLoading(false)\n })\n return () => {\n active = false\n }\n }, [provider, slug])\n\n return { post, loading, error }\n}\n","import { Link, useParams } from 'react-router-dom'\nimport { Clock } from 'lucide-react'\nimport { usePost } from '../hooks/usePost'\nimport { useBlogContext } from '../context'\nimport { AuthorAvatar } from './AuthorAvatar'\nimport { useSeo, articleJsonLd, postUrl } from '../seo'\n\n/**\n * /blog/:slug detail screen. Reads the slug from the router and renders the\n * article inside the host chrome. Token-only for theme inheritance. Sets rich\n * SEO/JSON-LD per article (BlogPosting + BreadcrumbList).\n */\nexport function PostDetail() {\n const { config } = useBlogContext()\n const { slug } = useParams<{ slug: string }>()\n const { post, loading, error } = usePost(slug)\n\n useSeo(\n post\n ? {\n title: `${post.title}${config.siteName ? ` · ${config.siteName}` : ''}`,\n description: post.excerpt,\n canonical: postUrl(config.basePath, post.slug),\n image: post.image,\n type: 'article',\n siteName: config.siteName,\n jsonLd: articleJsonLd(post, { basePath: config.basePath, siteName: config.siteName }),\n }\n : { title: config.siteName || 'Blog' },\n )\n\n const paragraphs = post?.body ? post.body.split(/\\n\\s*\\n/).map((p) => p.trim()).filter(Boolean) : []\n\n return (\n <article className=\"py-16 bg-background\">\n <div className=\"container mx-auto px-6 max-w-3xl\">\n <Link to={config.basePath} className=\"text-sm font-medium text-primary hover:underline\">\n {config.labels.backToList}\n </Link>\n\n {loading ? (\n <p className=\"mt-10 text-muted-foreground\">Carregando…</p>\n ) : error || !post ? (\n <div className=\"mt-10\">\n <h1 className=\"font-heading text-3xl font-bold text-foreground\">Artigo não encontrado</h1>\n <p className=\"mt-2 text-muted-foreground\">Este artigo pode ter sido movido ou removido.</p>\n </div>\n ) : (\n <>\n <h1 className=\"mt-8 font-heading text-4xl md:text-5xl font-bold text-foreground leading-tight\">\n {post.title}\n </h1>\n\n {/* Medium-style byline */}\n <div className=\"mt-6 flex items-center gap-3\">\n <AuthorAvatar author={post.author} className=\"h-11 w-11 text-base\" />\n <div>\n <p className=\"font-semibold text-foreground\">{post.author.name}</p>\n <p className=\"flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground\">\n {post.author.role ? <span>{post.author.role}<span aria-hidden> · </span></span> : null}\n <time dateTime={post.publishedAt}>{post.date}</time>\n <span className=\"flex items-center gap-1\"><Clock className=\"h-3 w-3\" />{post.readTime} de leitura</span>\n </p>\n </div>\n </div>\n\n <div className=\"mt-8 overflow-hidden rounded-2xl\">\n <img src={post.image} alt={post.title} className=\"w-full object-cover\" loading=\"lazy\" />\n </div>\n\n <div className=\"mt-8 space-y-5\">\n <p className=\"text-lg text-foreground/90 leading-relaxed font-medium\">{post.excerpt}</p>\n {paragraphs.map((para, i) => (\n <p key={i} className=\"text-muted-foreground leading-relaxed\">\n {para}\n </p>\n ))}\n </div>\n\n {/* Category tag — at the bottom, Medium-style */}\n <div className=\"mt-10 flex flex-wrap items-center gap-2 border-t border-border pt-6\">\n <span className=\"text-sm text-muted-foreground\">Categoria:</span>\n <Link\n to={config.basePath}\n className=\"rounded-full bg-accent px-3 py-1 text-xs font-semibold text-primary transition-colors hover:bg-accent/70\"\n >\n {post.tag}\n </Link>\n </div>\n\n {/* About the author */}\n {post.author.bio ? (\n <div className=\"mt-8 flex items-start gap-4 rounded-2xl border border-border bg-muted/40 p-6\">\n <AuthorAvatar author={post.author} className=\"h-14 w-14 text-lg\" />\n <div>\n <p className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">Escrito por</p>\n <p className=\"font-heading text-lg font-bold text-foreground\">{post.author.name}</p>\n {post.author.role ? <p className=\"text-sm text-primary\">{post.author.role}</p> : null}\n <p className=\"mt-2 text-sm text-muted-foreground leading-relaxed\">{post.author.bio}</p>\n </div>\n </div>\n ) : null}\n </>\n )}\n </div>\n </article>\n )\n}\n","// AUTO-GENERATED from 001_blog.sql — regenerate with scripts/embed-migrations.mjs\n// SQL files are the source of truth; this inline copy lets the manifest declare\n// migrations as data. Do not edit by hand — run the embed script instead.\n\nexport const MIGRATION_001_BLOG = `-- ============================================================================\n-- plugin-blog 001: blog content model + public (anon) read surface\n--\n-- Backs the blog backoffice (managed from the marketing plugin's \"Blog\" tab) and\n-- the public website surface (/blog, /blog/:slug). Prefix: plg_blog_.\n-- §1 plg_blog_categories — first-class category (name/slug/description)\n-- §2 plg_blog_posts — articles; category_id FK, author byline, status\n-- §3 RLS: authenticated tenant CRUD on both tables (the backoffice)\n-- §4 v_public_blog_posts — anon-readable view of PUBLISHED posts (the site)\n--\n-- Anon-access design mirrors plugin-agenda's public booking surface: the public\n-- website runs with the publishable key and no session, so canonical tenant RLS\n-- yields nothing. Anon read is granted ONLY through the column-whitelisted,\n-- published-only view (§4); the front provider filters it by tenant_id.\n-- Idempotent + safe to re-run.\n-- ============================================================================\n\n-- §1 — categories\nCREATE TABLE IF NOT EXISTS public.plg_blog_categories (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n name text NOT NULL,\n slug text NOT NULL,\n description text,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_blog_categories ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_blog_categories_tenant ON public.plg_blog_categories(tenant_id);\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_blog_categories_tenant_slug ON public.plg_blog_categories(tenant_id, slug);\n\n-- §2 — posts\nCREATE TABLE IF NOT EXISTS public.plg_blog_posts (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n category_id uuid REFERENCES public.plg_blog_categories(id) ON DELETE SET NULL,\n slug text NOT NULL,\n title text NOT NULL,\n excerpt text NOT NULL DEFAULT '',\n body text NOT NULL DEFAULT '',\n cover_image text,\n author_name text,\n author_role text,\n author_avatar_url text,\n author_bio text,\n status text NOT NULL DEFAULT 'draft'\n CHECK (status IN ('draft', 'published')),\n read_time text,\n published_at timestamptz,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_blog_posts ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_blog_posts_tenant ON public.plg_blog_posts(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_blog_posts_category ON public.plg_blog_posts(category_id);\nCREATE INDEX IF NOT EXISTS idx_plg_blog_posts_status ON public.plg_blog_posts(tenant_id, status, published_at DESC);\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_blog_posts_tenant_slug ON public.plg_blog_posts(tenant_id, slug);\n\n-- §3 — RLS: authenticated tenant CRUD (the backoffice writes here)\nDROP POLICY IF EXISTS plg_blog_categories_select ON public.plg_blog_categories;\nDROP POLICY IF EXISTS plg_blog_categories_insert ON public.plg_blog_categories;\nDROP POLICY IF EXISTS plg_blog_categories_update ON public.plg_blog_categories;\nDROP POLICY IF EXISTS plg_blog_categories_delete ON public.plg_blog_categories;\nCREATE POLICY plg_blog_categories_select ON public.plg_blog_categories FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_categories_insert ON public.plg_blog_categories FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_categories_update ON public.plg_blog_categories FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_categories_delete ON public.plg_blog_categories FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_blog_categories TO authenticated;\n\nDROP POLICY IF EXISTS plg_blog_posts_select ON public.plg_blog_posts;\nDROP POLICY IF EXISTS plg_blog_posts_insert ON public.plg_blog_posts;\nDROP POLICY IF EXISTS plg_blog_posts_update ON public.plg_blog_posts;\nDROP POLICY IF EXISTS plg_blog_posts_delete ON public.plg_blog_posts;\nCREATE POLICY plg_blog_posts_select ON public.plg_blog_posts FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_posts_insert ON public.plg_blog_posts FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_posts_update ON public.plg_blog_posts FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_blog_posts_delete ON public.plg_blog_posts FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_blog_posts TO authenticated;\n\n-- §4 — anon-readable public catalog (owner-rights view = deliberate, whitelisted,\n-- PUBLISHED only). The front provider filters by tenant_id. Category name is\n-- surfaced as \\`tag\\` so the website's BlogPost shape stays unchanged.\nCREATE OR REPLACE VIEW public.v_public_blog_posts AS\nSELECT\n p.id,\n p.tenant_id,\n p.slug,\n p.title,\n p.excerpt,\n p.body,\n COALESCE(c.name, '') AS tag,\n p.cover_image,\n p.author_name,\n p.author_role,\n p.author_avatar_url,\n p.author_bio,\n p.read_time,\n p.published_at\nFROM public.plg_blog_posts p\nLEFT JOIN public.plg_blog_categories c ON c.id = p.category_id\nWHERE p.status = 'published';\n\nGRANT SELECT ON public.v_public_blog_posts TO anon, authenticated, service_role;\n`\n\nexport const MIGRATIONS: Array<{ id: string; sql: string }> = [\n { id: \"001_blog\", sql: MIGRATION_001_BLOG },\n]\n","import { createElement, type FC, type ReactNode } from 'react'\nimport type { PluginManifest } from '@fayz-ai/core'\nimport { createSafeDataProvider } from '@fayz-ai/core'\nimport { resolveConfig, type BlogPluginOptions } from './config'\nimport { createMockBlogProvider } from './data/mock'\nimport { createSupabaseBlogProvider } from './data/supabase'\nimport { BlogProvider, type BlogContextValue } from './context'\nimport { BlogList } from './components/BlogList'\nimport { PostDetail } from './components/PostDetail'\nimport { MIGRATIONS } from './migrations'\n\n// ---------------------------------------------------------------------------\n// @fayz-ai/plugin-blog — website-surface blog/articles plugin.\n//\n// First real consumer of scaffolds:['website']. Ships public routes (/blog,\n// /blog/:slug) + data hooks (useBlogPosts/usePost). A bespoke host site feeds\n// its own card markup from the hooks and mounts the routes inside its chrome.\n// See createBlogPlugin's return: a { manifest, Provider } bundle the host reads\n// uniformly (manifest.routes for mounting, Provider for the app-root context).\n// ---------------------------------------------------------------------------\n\nexport interface BlogWebsitePlugin {\n /** Standard plugin manifest (routes + metadata). */\n manifest: PluginManifest\n /** App-root context provider, pre-bound to this plugin's resolved provider/config. */\n Provider: FC<{ children: ReactNode }>\n /** The resolved data provider (exposed for advanced host wiring/tests). */\n dataProvider: BlogContextValue['provider']\n}\n\nexport function createBlogPlugin(options?: BlogPluginOptions): BlogWebsitePlugin {\n const config = resolveConfig(options)\n const provider =\n options?.dataProvider ??\n createSafeDataProvider(\n () => createSupabaseBlogProvider({ tenantId: options?.tenantId }),\n () => createMockBlogProvider({ seed: options?.seed, defaultAuthor: options?.defaultAuthor }),\n )\n\n const value: BlogContextValue = { provider, config }\n const Provider: FC<{ children: ReactNode }> = ({ children }) =>\n createElement(BlogProvider, { value, children })\n Provider.displayName = 'BlogPluginProvider'\n\n const manifest: PluginManifest = {\n id: 'blog',\n name: 'Blog',\n icon: 'Newspaper',\n version: '0.1.0',\n scope: options?.scope ?? 'universal',\n verticalId: options?.verticalId,\n scaffolds: ['website', 'landing_page'],\n defaultEnabled: true,\n dependencies: [],\n declaredFeatures: [{ id: 'blog', label: 'Blog', group: 'Attract' }],\n // Host owns website nav; no admin nav in M1.\n navigation: [],\n routes: [\n { path: config.basePath, component: BlogList, guard: 'public' },\n { path: `${config.basePath}/:slug`, component: PostDetail, guard: 'public' },\n ],\n widgets: [],\n // Owns the blog domain tables (categories + posts + anon-read view). The\n // marketing \"Blog\" backoffice writes to these; the website reads them.\n migrations: MIGRATIONS.map((m) => ({\n id: `blog-${m.id}`,\n version: '1.0.0',\n sql: m.sql,\n description: 'Blog content model (plg_blog_categories, plg_blog_posts) + anon-read view',\n })),\n }\n\n return { manifest, Provider, dataProvider: provider }\n}\n\n// Public API\nexport type { BlogPluginOptions, ResolvedBlogConfig } from './config'\nexport type { BlogPost, BlogListQuery, BlogAuthor, BlogCategory } from './types'\nexport type { BlogDataProvider } from './data/types'\nexport { createMockBlogProvider, createSupabaseBlogProvider } from './data'\nexport type { BlogSeed, BlogSeedPost } from './data/mock'\nexport { BlogProvider, useBlogContext } from './context'\nexport type { BlogContextValue } from './context'\nexport { useBlogPosts, usePost } from './hooks'\nexport type { UseBlogPostsResult, UsePostResult } from './hooks'\nexport { BlogList } from './components/BlogList'\nexport { PostDetail } from './components/PostDetail'\nexport { PostCard } from './components/PostCard'\nexport { AuthorAvatar } from './components/AuthorAvatar'\n"]}
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export interface BlogWebsitePlugin {
14
14
  }
15
15
  export declare function createBlogPlugin(options?: BlogPluginOptions): BlogWebsitePlugin;
16
16
  export type { BlogPluginOptions, ResolvedBlogConfig } from './config';
17
- export type { BlogPost, BlogListQuery, BlogAuthor } from './types';
17
+ export type { BlogPost, BlogListQuery, BlogAuthor, BlogCategory } from './types';
18
18
  export type { BlogDataProvider } from './data/types';
19
19
  export { createMockBlogProvider, createSupabaseBlogProvider } from './data';
20
20
  export type { BlogSeed, BlogSeedPost } from './data/mock';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD,OAAO,EAAiB,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAGhE,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAc/D,MAAM,WAAW,iBAAiB;IAChC,oDAAoD;IACpD,QAAQ,EAAE,cAAc,CAAA;IACxB,sFAAsF;IACtF,QAAQ,EAAE,EAAE,CAAC;QAAE,QAAQ,EAAE,SAAS,CAAA;KAAE,CAAC,CAAA;IACrC,2EAA2E;IAC3E,YAAY,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAA;CAC3C;AAED,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,CAmC/E;AAGD,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AACrE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAClE,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,QAAQ,CAAA;AAC3E,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AACxD,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAC/C,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD,OAAO,EAAiB,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAGhE,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAe/D,MAAM,WAAW,iBAAiB;IAChC,oDAAoD;IACpD,QAAQ,EAAE,cAAc,CAAA;IACxB,sFAAsF;IACtF,QAAQ,EAAE,EAAE,CAAC;QAAE,QAAQ,EAAE,SAAS,CAAA;KAAE,CAAC,CAAA;IACrC,2EAA2E;IAC3E,YAAY,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAA;CAC3C;AAED,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,CA2C/E;AAGD,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AACrE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAChF,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,QAAQ,CAAA;AAC3E,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AACxD,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAC/C,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA"}