create-nextblock 0.10.9 → 0.11.2

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 (64) hide show
  1. package/package.json +1 -1
  2. package/templates/nextblock-template/app/actions/interactions.test.ts +301 -0
  3. package/templates/nextblock-template/app/actions/interactions.ts +372 -0
  4. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +4 -4
  5. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +2 -2
  6. package/templates/nextblock-template/app/api/ai/global-agent/route.ts +56 -57
  7. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +1 -1
  8. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +837 -0
  9. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +6 -0
  10. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +4 -0
  11. package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -0
  12. package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +102 -0
  13. package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +18 -13
  14. package/templates/nextblock-template/app/cms/interactions/InteractionsModerationClient.tsx +408 -0
  15. package/templates/nextblock-template/app/cms/interactions/page.tsx +51 -0
  16. package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +4 -3
  17. package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +1 -1
  18. package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +3 -5
  19. package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +1 -1
  20. package/templates/nextblock-template/app/page.tsx +2 -2
  21. package/templates/nextblock-template/app/product/[slug]/page.tsx +2 -0
  22. package/templates/nextblock-template/components/AppShell.tsx +1 -1
  23. package/templates/nextblock-template/components/PostCommentsSection.tsx +369 -0
  24. package/templates/nextblock-template/components/ProductReviewsSection.tsx +419 -0
  25. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +2 -0
  26. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +62 -19
  27. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +19 -19
  28. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +4 -4
  29. package/templates/nextblock-template/docs/12-VERCEL-DEPLOYMENT.md +9 -8
  30. package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +38 -9
  31. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +2 -0
  32. package/templates/nextblock-template/lib/onboarding/status.ts +13 -6
  33. package/templates/nextblock-template/lib/setup/actions.ts +3 -1
  34. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +30 -0
  35. package/templates/nextblock-template/lib/updates/check-upstream.ts +44 -7
  36. package/templates/nextblock-template/lib/updates/github-device.ts +206 -0
  37. package/templates/nextblock-template/lib/updates/repo-identity.ts +11 -1
  38. package/templates/nextblock-template/package.json +2 -1
  39. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +2 -4
  40. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +1 -1
  41. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +1 -1
  42. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +1 -1
  43. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  44. package/templates/nextblock-template/lib/ai-block-generation.ts +0 -339
  45. package/templates/nextblock-template/lib/ai-client.ts +0 -247
  46. package/templates/nextblock-template/lib/ai-config.ts +0 -98
  47. package/templates/nextblock-template/lib/ai-cortex-widget-builder.ts +0 -125
  48. package/templates/nextblock-template/lib/ai-global-agent-custom-block-tools.ts +0 -363
  49. package/templates/nextblock-template/lib/ai-global-agent-db-tools.test.ts +0 -405
  50. package/templates/nextblock-template/lib/ai-global-agent-db-tools.ts +0 -1228
  51. package/templates/nextblock-template/lib/ai-global-agent-ecommerce.ts +0 -5
  52. package/templates/nextblock-template/lib/ai-global-agent-tools-stats.test.ts +0 -223
  53. package/templates/nextblock-template/lib/ai-global-agent-tools.test.ts +0 -2183
  54. package/templates/nextblock-template/lib/ai-global-agent-tools.ts +0 -4807
  55. package/templates/nextblock-template/lib/ai-key-crypto.test.ts +0 -70
  56. package/templates/nextblock-template/lib/ai-key-crypto.ts +0 -132
  57. package/templates/nextblock-template/lib/ai-model-catalog.test.ts +0 -49
  58. package/templates/nextblock-template/lib/ai-model-catalog.ts +0 -41
  59. package/templates/nextblock-template/lib/ai-model-registry.test.ts +0 -231
  60. package/templates/nextblock-template/lib/ai-model-registry.ts +0 -522
  61. package/templates/nextblock-template/lib/cortex-widget-registry.test.ts +0 -199
  62. package/templates/nextblock-template/lib/cortex-widget-registry.ts +0 -88
  63. package/templates/nextblock-template/lib/cortex-widget-schema.test.tsx +0 -237
  64. package/templates/nextblock-template/lib/cortex-widget-schema.ts +0 -393
@@ -9,6 +9,7 @@ import { useCurrentContent } from '../../../context/CurrentContentContext';
9
9
  import Link from 'next/link';
10
10
  import { estimateReadTimeMinutesFromBlocks } from '../../../lib/posts/readTime';
11
11
  import FeatureImageHero from '../../../components/FeatureImageHero';
12
+ import PostCommentsSection from '../../../components/PostCommentsSection';
12
13
 
13
14
  type PostType = Database['public']['Tables']['posts']['Row'];
14
15
  type BlockType = Database['public']['Tables']['blocks']['Row'];
@@ -430,6 +431,11 @@ export default function PostClientContent({ initialPostData, currentSlug, childr
430
431
  <div ref={articleBodyRef} className="post-article__body mx-auto mt-10 w-full px-4 md:mt-14">
431
432
  {children}
432
433
  </div>
434
+
435
+ {/* Post Comments Section */}
436
+ <div className="mx-auto max-w-4xl px-4 mt-16 border-t pt-10 border-slate-200 dark:border-slate-800">
437
+ <PostCommentsSection postId={currentPostData.id} />
438
+ </div>
433
439
  </article>
434
440
  );
435
441
  }
@@ -244,6 +244,7 @@ export default function CmsClientLayout({
244
244
 
245
245
  else if (pathname.startsWith("/cms/settings/packages")) pageTitle = "Packages";
246
246
  else if (pathname.startsWith("/cms/settings")) pageTitle = "Settings";
247
+ else if (pathname.startsWith("/cms/interactions")) pageTitle = "Interactions";
247
248
  else if (pathname.startsWith("/cms/products/inventory")) pageTitle = "Inventory";
248
249
  else if (pathname.startsWith("/cms/coupons/") && pathname.endsWith("/edit")) pageTitle = "Edit Coupon";
249
250
  else if (pathname.startsWith("/cms/coupons")) pageTitle = "Coupons";
@@ -316,6 +317,9 @@ export default function CmsClientLayout({
316
317
  <NavItem href="/cms/custom-blocks" icon={Boxes} isActive={pathname.startsWith("/cms/custom-blocks")} writerOnly isAdmin={isAdmin} isWriter={isWriter} onClick={closeSidebarOnMobile}>
317
318
  Blocks
318
319
  </NavItem>
320
+ <NavItem href="/cms/interactions" icon={MessageSquare} isActive={pathname.startsWith("/cms/interactions")} writerOnly isAdmin={isAdmin} isWriter={isWriter} onClick={closeSidebarOnMobile}>
321
+ Interactions
322
+ </NavItem>
319
323
  <NavItem href="/cms/navigation" icon={ListTree} isActive={pathname.startsWith("/cms/navigation")} adminOnly isAdmin={isAdmin} onClick={closeSidebarOnMobile}>
320
324
  Navigation
321
325
  </NavItem>
@@ -0,0 +1,122 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useEffect, useRef, useState } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ import { Check, ExternalLink, Github, Loader2 } from 'lucide-react';
6
+ import { Button } from '@nextblock-cms/ui';
7
+ import { startGithubConnect, pollGithubConnect } from './github-connect-actions';
8
+
9
+ type Phase = 'idle' | 'starting' | 'awaiting' | 'installed' | 'error';
10
+
11
+ /**
12
+ * One-click "Connect GitHub" via the OAuth device flow. On authorization, the server
13
+ * installs the upstream-sync workflow into the repo (the file Vercel's clone strips).
14
+ * No PAT, no env config — the public client id is baked into the app.
15
+ */
16
+ export default function ConnectGitHubButton() {
17
+ const router = useRouter();
18
+ const [phase, setPhase] = useState<Phase>('idle');
19
+ const [userCode, setUserCode] = useState('');
20
+ const [verificationUri, setVerificationUri] = useState('https://github.com/login/device');
21
+ const [error, setError] = useState('');
22
+
23
+ // Cancel any in-flight polling when the component unmounts.
24
+ const activeRef = useRef(false);
25
+ useEffect(() => {
26
+ activeRef.current = true;
27
+ return () => {
28
+ activeRef.current = false;
29
+ };
30
+ }, []);
31
+
32
+ const poll = useCallback(async (intervalMs: number) => {
33
+ if (!activeRef.current) return;
34
+ const result = await pollGithubConnect();
35
+ if (!activeRef.current) return;
36
+
37
+ if (result.status === 'installed') {
38
+ setPhase('installed');
39
+ // Re-render the dashboard so the onboarding step picks up the now-active workflow
40
+ // (the step then flips to done and this control is replaced).
41
+ router.refresh();
42
+ return;
43
+ }
44
+ if (result.status === 'error') {
45
+ setError(result.error);
46
+ setPhase('error');
47
+ return;
48
+ }
49
+ // pending — back off a little on slow_down, then poll again.
50
+ const next = result.slowDown ? intervalMs + 5000 : intervalMs;
51
+ setTimeout(() => void poll(next), next);
52
+ }, [router]);
53
+
54
+ const connect = useCallback(async () => {
55
+ setError('');
56
+ setPhase('starting');
57
+ const res = await startGithubConnect();
58
+ if (!activeRef.current) return;
59
+ if (!res.ok || !res.userCode) {
60
+ setError(res.error ?? 'Could not start GitHub connect.');
61
+ setPhase('error');
62
+ return;
63
+ }
64
+ setUserCode(res.userCode);
65
+ if (res.verificationUri) setVerificationUri(res.verificationUri);
66
+ setPhase('awaiting');
67
+ const intervalMs = Math.max((res.interval ?? 5) + 1, 5) * 1000;
68
+ setTimeout(() => void poll(intervalMs), intervalMs);
69
+ }, [poll]);
70
+
71
+ if (phase === 'installed') {
72
+ return (
73
+ <div className="flex items-center gap-1.5 text-sm font-medium text-emerald-600 dark:text-emerald-400">
74
+ <Check className="h-4 w-4" />
75
+ Connected — workflow installed
76
+ </div>
77
+ );
78
+ }
79
+
80
+ if (phase === 'awaiting') {
81
+ return (
82
+ <div className="flex flex-col items-end gap-1.5 text-right">
83
+ <Button asChild size="sm" variant="outline">
84
+ <a href={verificationUri} target="_blank" rel="noopener noreferrer">
85
+ Authorize on GitHub
86
+ <ExternalLink className="ml-1 h-3.5 w-3.5" />
87
+ </a>
88
+ </Button>
89
+ <p className="text-xs text-muted-foreground">
90
+ Enter code{' '}
91
+ <span className="font-mono font-semibold tracking-wider text-foreground">{userCode}</span>
92
+ </p>
93
+ <p className="flex items-center gap-1 text-xs text-muted-foreground">
94
+ <Loader2 className="h-3 w-3 animate-spin" />
95
+ Waiting for authorization…
96
+ </p>
97
+ </div>
98
+ );
99
+ }
100
+
101
+ return (
102
+ <div className="flex flex-col items-end gap-1">
103
+ <Button
104
+ size="sm"
105
+ variant="outline"
106
+ onClick={() => void connect()}
107
+ disabled={phase === 'starting'}
108
+ className="shrink-0"
109
+ >
110
+ {phase === 'starting' ? (
111
+ <Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
112
+ ) : (
113
+ <Github className="mr-1 h-3.5 w-3.5" />
114
+ )}
115
+ {phase === 'starting' ? 'Starting…' : 'Connect GitHub'}
116
+ </Button>
117
+ {phase === 'error' && (
118
+ <p className="max-w-[16rem] text-right text-xs text-red-600 dark:text-red-400">{error}</p>
119
+ )}
120
+ </div>
121
+ );
122
+ }
@@ -0,0 +1,102 @@
1
+ 'use server';
2
+
3
+ import { cookies } from 'next/headers';
4
+ import { revalidatePath } from 'next/cache';
5
+ import { createClient } from '@nextblock-cms/db/server';
6
+ import {
7
+ startDeviceFlow,
8
+ pollDeviceFlowOnce,
9
+ installSyncWorkflow,
10
+ } from '../../../lib/updates/github-device';
11
+ import { markSyncWorkflowInstalled } from '../../../lib/updates/check-upstream';
12
+
13
+ const DEVICE_COOKIE = 'nb_gh_device';
14
+
15
+ /** ADMIN gate, mirroring the rest of the CMS server entry points. */
16
+ async function isAdmin(): Promise<boolean> {
17
+ const supabase = createClient();
18
+ const {
19
+ data: { user },
20
+ } = await supabase.auth.getUser();
21
+ if (!user) return false;
22
+ const { data: profile, error } = await supabase
23
+ .from('profiles')
24
+ .select('role')
25
+ .eq('id', user.id)
26
+ .single();
27
+ return !error && profile?.role === 'ADMIN';
28
+ }
29
+
30
+ export interface StartConnectResult {
31
+ ok: boolean;
32
+ userCode?: string;
33
+ verificationUri?: string;
34
+ interval?: number;
35
+ expiresIn?: number;
36
+ error?: string;
37
+ }
38
+
39
+ /** Begin the device flow; stash the device code in an httpOnly cookie for polling. */
40
+ export async function startGithubConnect(): Promise<StartConnectResult> {
41
+ if (!(await isAdmin())) return { ok: false, error: 'Administrator role required.' };
42
+
43
+ try {
44
+ const flow = await startDeviceFlow();
45
+ const store = await cookies();
46
+ store.set(DEVICE_COOKIE, flow.deviceCode, {
47
+ httpOnly: true,
48
+ secure: process.env.NODE_ENV === 'production',
49
+ sameSite: 'lax',
50
+ path: '/cms',
51
+ maxAge: flow.expiresIn,
52
+ });
53
+ return {
54
+ ok: true,
55
+ userCode: flow.userCode,
56
+ verificationUri: flow.verificationUri,
57
+ interval: flow.interval,
58
+ expiresIn: flow.expiresIn,
59
+ };
60
+ } catch (caught) {
61
+ return { ok: false, error: caught instanceof Error ? caught.message : 'Could not start GitHub connect.' };
62
+ }
63
+ }
64
+
65
+ export type PollConnectResult =
66
+ | { status: 'installed'; htmlUrl?: string }
67
+ | { status: 'pending'; slowDown?: boolean }
68
+ | { status: 'error'; error: string };
69
+
70
+ /** Poll once; on authorization, install the workflow and clear the device cookie. */
71
+ export async function pollGithubConnect(): Promise<PollConnectResult> {
72
+ if (!(await isAdmin())) return { status: 'error', error: 'Administrator role required.' };
73
+
74
+ const store = await cookies();
75
+ const deviceCode = store.get(DEVICE_COOKIE)?.value;
76
+ if (!deviceCode) {
77
+ return { status: 'error', error: 'Connect session expired — start again.' };
78
+ }
79
+
80
+ const poll = await pollDeviceFlowOnce(deviceCode);
81
+
82
+ if (poll.status === 'pending') return { status: 'pending', slowDown: poll.slowDown };
83
+
84
+ if (poll.status === 'authorized') {
85
+ const install = await installSyncWorkflow(poll.token);
86
+ store.delete({ name: DEVICE_COOKIE, path: '/cms' });
87
+ if (install.ok) {
88
+ // We just installed the workflow — flip the onboarding state now instead of waiting
89
+ // for the throttled background poll / GitHub's registration lag.
90
+ await markSyncWorkflowInstalled();
91
+ revalidatePath('/cms', 'layout');
92
+ return { status: 'installed', htmlUrl: install.htmlUrl };
93
+ }
94
+ return { status: 'error', error: install.error ?? 'Could not install the workflow.' };
95
+ }
96
+
97
+ // expired / denied / error — clear the session.
98
+ store.delete({ name: DEVICE_COOKIE, path: '/cms' });
99
+ if (poll.status === 'expired') return { status: 'error', error: 'Authorization timed out — start again.' };
100
+ if (poll.status === 'denied') return { status: 'error', error: 'Authorization was declined.' };
101
+ return { status: 'error', error: poll.error };
102
+ }
@@ -12,6 +12,7 @@ import {
12
12
  } from '@nextblock-cms/ui';
13
13
  import { ArrowRight, CheckCircle2, Circle, X } from 'lucide-react';
14
14
  import type { OnboardingStatus } from '../../../../lib/onboarding/status';
15
+ import ConnectGitHubButton from '../../components/ConnectGitHubButton';
15
16
 
16
17
  export default function DashboardOnboarding({
17
18
  status,
@@ -102,19 +103,23 @@ export default function DashboardOnboarding({
102
103
  <p className="text-xs text-muted-foreground">{step.description}</p>
103
104
  </div>
104
105
  {!step.done && step.key !== 'admin' && (
105
- <Button asChild variant="outline" size="sm" className="shrink-0">
106
- {step.isExternal ? (
107
- <a href={step.href} target="_blank" rel="noopener noreferrer">
108
- Set up
109
- <ArrowRight className="ml-1 h-3.5 w-3.5" />
110
- </a>
111
- ) : (
112
- <Link href={step.href}>
113
- Set up
114
- <ArrowRight className="ml-1 h-3.5 w-3.5" />
115
- </Link>
116
- )}
117
- </Button>
106
+ step.connectGithub ? (
107
+ <ConnectGitHubButton />
108
+ ) : (
109
+ <Button asChild variant="outline" size="sm" className="shrink-0">
110
+ {step.isExternal ? (
111
+ <a href={step.href} target="_blank" rel="noopener noreferrer">
112
+ Set up
113
+ <ArrowRight className="ml-1 h-3.5 w-3.5" />
114
+ </a>
115
+ ) : (
116
+ <Link href={step.href}>
117
+ Set up
118
+ <ArrowRight className="ml-1 h-3.5 w-3.5" />
119
+ </Link>
120
+ )}
121
+ </Button>
122
+ )
118
123
  )}
119
124
  </li>
120
125
  ))}