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.
- package/package.json +1 -1
- package/templates/nextblock-template/app/actions/interactions.test.ts +301 -0
- package/templates/nextblock-template/app/actions/interactions.ts +372 -0
- package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +4 -4
- package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +2 -2
- package/templates/nextblock-template/app/api/ai/global-agent/route.ts +56 -57
- package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +1 -1
- package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +837 -0
- package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +6 -0
- package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +4 -0
- package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -0
- package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +102 -0
- package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +18 -13
- package/templates/nextblock-template/app/cms/interactions/InteractionsModerationClient.tsx +408 -0
- package/templates/nextblock-template/app/cms/interactions/page.tsx +51 -0
- package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +4 -3
- package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +1 -1
- package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +3 -5
- package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +1 -1
- package/templates/nextblock-template/app/page.tsx +2 -2
- package/templates/nextblock-template/app/product/[slug]/page.tsx +2 -0
- package/templates/nextblock-template/components/AppShell.tsx +1 -1
- package/templates/nextblock-template/components/PostCommentsSection.tsx +369 -0
- package/templates/nextblock-template/components/ProductReviewsSection.tsx +419 -0
- package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +2 -0
- package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +62 -19
- package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +19 -19
- package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +4 -4
- package/templates/nextblock-template/docs/12-VERCEL-DEPLOYMENT.md +9 -8
- package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +38 -9
- package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +2 -0
- package/templates/nextblock-template/lib/onboarding/status.ts +13 -6
- package/templates/nextblock-template/lib/setup/actions.ts +3 -1
- package/templates/nextblock-template/lib/setup/migrations-bundle.ts +30 -0
- package/templates/nextblock-template/lib/updates/check-upstream.ts +44 -7
- package/templates/nextblock-template/lib/updates/github-device.ts +206 -0
- package/templates/nextblock-template/lib/updates/repo-identity.ts +11 -1
- package/templates/nextblock-template/package.json +2 -1
- package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +2 -4
- package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +1 -1
- package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +1 -1
- package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +1 -1
- package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
- package/templates/nextblock-template/lib/ai-block-generation.ts +0 -339
- package/templates/nextblock-template/lib/ai-client.ts +0 -247
- package/templates/nextblock-template/lib/ai-config.ts +0 -98
- package/templates/nextblock-template/lib/ai-cortex-widget-builder.ts +0 -125
- package/templates/nextblock-template/lib/ai-global-agent-custom-block-tools.ts +0 -363
- package/templates/nextblock-template/lib/ai-global-agent-db-tools.test.ts +0 -405
- package/templates/nextblock-template/lib/ai-global-agent-db-tools.ts +0 -1228
- package/templates/nextblock-template/lib/ai-global-agent-ecommerce.ts +0 -5
- package/templates/nextblock-template/lib/ai-global-agent-tools-stats.test.ts +0 -223
- package/templates/nextblock-template/lib/ai-global-agent-tools.test.ts +0 -2183
- package/templates/nextblock-template/lib/ai-global-agent-tools.ts +0 -4807
- package/templates/nextblock-template/lib/ai-key-crypto.test.ts +0 -70
- package/templates/nextblock-template/lib/ai-key-crypto.ts +0 -132
- package/templates/nextblock-template/lib/ai-model-catalog.test.ts +0 -49
- package/templates/nextblock-template/lib/ai-model-catalog.ts +0 -41
- package/templates/nextblock-template/lib/ai-model-registry.test.ts +0 -231
- package/templates/nextblock-template/lib/ai-model-registry.ts +0 -522
- package/templates/nextblock-template/lib/cortex-widget-registry.test.ts +0 -199
- package/templates/nextblock-template/lib/cortex-widget-registry.ts +0 -88
- package/templates/nextblock-template/lib/cortex-widget-schema.test.tsx +0 -237
- 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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
))}
|