create-nextblock 0.15.5 → 0.15.9

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 (79) hide show
  1. package/package.json +50 -26
  2. package/templates/nextblock-template/app/ToasterProvider.tsx +26 -17
  3. package/templates/nextblock-template/app/actions/contactSellerActions.test.ts +280 -0
  4. package/templates/nextblock-template/app/actions/contactSellerActions.ts +222 -0
  5. package/templates/nextblock-template/app/actions/email-retry.test.ts +62 -0
  6. package/templates/nextblock-template/app/actions/email.ts +241 -110
  7. package/templates/nextblock-template/app/actions/formActions.ts +245 -116
  8. package/templates/nextblock-template/app/actions/interactions.ts +489 -396
  9. package/templates/nextblock-template/app/actions/threadActions.ts +166 -0
  10. package/templates/nextblock-template/app/api/checkout/route.ts +162 -146
  11. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +14 -0
  12. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +664 -1
  13. package/templates/nextblock-template/app/checkout/page.tsx +57 -52
  14. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +552 -529
  15. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +304 -181
  16. package/templates/nextblock-template/app/cms/components/ContactReminderBanner.tsx +75 -0
  17. package/templates/nextblock-template/app/cms/components/PaymentsReminderBanner.tsx +58 -0
  18. package/templates/nextblock-template/app/cms/components/VisibilityControl.tsx +542 -528
  19. package/templates/nextblock-template/app/cms/inquiries/actions.ts +66 -0
  20. package/templates/nextblock-template/app/cms/inquiries/page.tsx +12 -0
  21. package/templates/nextblock-template/app/cms/interactions/page.tsx +12 -51
  22. package/templates/nextblock-template/app/cms/layout.tsx +101 -73
  23. package/templates/nextblock-template/app/cms/messages/MessagesClient.tsx +661 -0
  24. package/templates/nextblock-template/app/cms/messages/actions.ts +404 -0
  25. package/templates/nextblock-template/app/cms/messages/loadInbox.ts +333 -0
  26. package/templates/nextblock-template/app/cms/messages/page.tsx +87 -0
  27. package/templates/nextblock-template/app/cms/messages/require-admin.ts +37 -0
  28. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +370 -362
  29. package/templates/nextblock-template/app/cms/revisions/service.ts +20 -0
  30. package/templates/nextblock-template/app/cms/settings/email/components/EmailForm.tsx +227 -185
  31. package/templates/nextblock-template/app/layout.tsx +671 -667
  32. package/templates/nextblock-template/app/lib/seo.ts +319 -311
  33. package/templates/nextblock-template/app/product/[slug]/page.tsx +502 -482
  34. package/templates/nextblock-template/app/providers.tsx +96 -96
  35. package/templates/nextblock-template/app/thread/ThreadView.tsx +164 -0
  36. package/templates/nextblock-template/app/thread/[token]/route.ts +57 -0
  37. package/templates/nextblock-template/app/thread/layout.tsx +15 -0
  38. package/templates/nextblock-template/app/thread/page.tsx +98 -0
  39. package/templates/nextblock-template/components/BlockRenderer.tsx +312 -296
  40. package/templates/nextblock-template/components/ContactSellerSection.tsx +188 -0
  41. package/templates/nextblock-template/components/PostCommentsSection.tsx +378 -369
  42. package/templates/nextblock-template/components/ProductReviewsSection.tsx +426 -419
  43. package/templates/nextblock-template/components/StaffReplies.tsx +102 -0
  44. package/templates/nextblock-template/components/blocks/renderers/CartBlockRenderer.tsx +18 -17
  45. package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +20 -19
  46. package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +25 -22
  47. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +385 -381
  48. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +157 -92
  49. package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +34 -31
  50. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +612 -600
  51. package/templates/nextblock-template/components/commerce/PaymentReadinessBoundary.tsx +32 -0
  52. package/templates/nextblock-template/docs/05-DEVELOPER-GUIDE.md +25 -19
  53. package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +1 -1
  54. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +2 -2
  55. package/templates/nextblock-template/docs/12-VERCEL-DEPLOYMENT.md +32 -9
  56. package/templates/nextblock-template/docs/14-MESSAGES-INBOX.md +309 -0
  57. package/templates/nextblock-template/docs/README.md +42 -41
  58. package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +540 -542
  59. package/templates/nextblock-template/docs/assets/lighthouse-scores.png +0 -0
  60. package/templates/nextblock-template/lib/blocks/blockColors.test.ts +22 -2
  61. package/templates/nextblock-template/lib/blocks/blockColors.ts +44 -1
  62. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +761 -753
  63. package/templates/nextblock-template/lib/cms/contact-reminder.ts +64 -0
  64. package/templates/nextblock-template/lib/cms/payments-reminder.ts +98 -0
  65. package/templates/nextblock-template/lib/cms/unread-messages.ts +42 -0
  66. package/templates/nextblock-template/lib/commerce/seller-contact.ts +162 -0
  67. package/templates/nextblock-template/lib/config/email-settings.ts +323 -254
  68. package/templates/nextblock-template/lib/config/email-tls.test.ts +57 -0
  69. package/templates/nextblock-template/lib/email/placeholder-address.test.ts +59 -0
  70. package/templates/nextblock-template/lib/email/placeholder-address.ts +39 -0
  71. package/templates/nextblock-template/lib/messages/thread-reference.test.ts +70 -0
  72. package/templates/nextblock-template/lib/messages/thread-token.test.ts +93 -0
  73. package/templates/nextblock-template/lib/messages/thread-token.ts +157 -0
  74. package/templates/nextblock-template/lib/messages/threads.ts +579 -0
  75. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +20 -0
  76. package/templates/nextblock-template/lib/site-url.test.ts +89 -0
  77. package/templates/nextblock-template/lib/site-url.ts +102 -48
  78. package/templates/nextblock-template/package.json +14 -1
  79. package/templates/nextblock-template/public/assets/nextblock-banner.jpg +0 -0
@@ -1,92 +1,157 @@
1
- import React from 'react';
2
- import { ProductDetailsLayout } from '@nextblock-cms/ecommerce/components/ProductDetailsLayout';
3
- import type { VisualEditAttributes, VisualEditingDocumentContext } from '../../../lib/visual-editing/types';
4
- import { createClient } from "@nextblock-cms/db/server";
5
- import BlockRenderer from "../../BlockRenderer";
6
- import ProductReviewsSection from "../../ProductReviewsSection";
7
-
8
- interface ProductDetailsBlockRendererProps {
9
- visualEditAttributes?: VisualEditAttributes;
10
- productVisualEditingEnabled?: boolean;
11
- excludeProductId?: string;
12
- languageId: number;
13
- visualEditing?: VisualEditingDocumentContext;
14
- }
15
-
16
- export default async function ProductDetailsBlockRenderer({
17
- visualEditAttributes,
18
- productVisualEditingEnabled = false,
19
- excludeProductId,
20
- languageId,
21
- visualEditing,
22
- }: ProductDetailsBlockRendererProps) {
23
- const supabase = createClient();
24
-
25
- let descriptionBlocks: any[] = [];
26
- let productSlug = "";
27
- let productDraftId: number | null = null;
28
-
29
- if (excludeProductId) {
30
- const { data: productInfo } = await supabase
31
- .from('products')
32
- .select('slug')
33
- .eq('id', excludeProductId)
34
- .maybeSingle();
35
-
36
- if (productInfo) {
37
- productSlug = productInfo.slug;
38
- }
39
-
40
- if (productVisualEditingEnabled) {
41
- const { data: draftData } = await supabase
42
- .from('product_drafts')
43
- .select('id, blocks')
44
- .eq('product_id', excludeProductId)
45
- .maybeSingle();
46
-
47
- if (draftData) {
48
- productDraftId = draftData.id;
49
- if (draftData.blocks && Array.isArray(draftData.blocks)) {
50
- descriptionBlocks = draftData.blocks;
51
- }
52
- }
53
- }
54
-
55
- if (descriptionBlocks.length === 0) {
56
- const { data: liveBlocks } = await supabase
57
- .from('blocks')
58
- .select('*')
59
- .eq('product_id', excludeProductId)
60
- .order('order', { ascending: true });
61
- descriptionBlocks = liveBlocks || [];
62
- }
63
- }
64
-
65
- const productDescriptionVisualEditing = {
66
- enabled: productVisualEditingEnabled,
67
- documentType: "product" as const,
68
- documentId: excludeProductId!,
69
- slug: productSlug,
70
- languageId: languageId,
71
- draftId: productDraftId,
72
- };
73
-
74
- const descriptionNode = descriptionBlocks.length > 0 ? (
75
- <BlockRenderer
76
- blocks={descriptionBlocks}
77
- languageId={languageId}
78
- productVisualEditingEnabled={productVisualEditingEnabled}
79
- visualEditing={productDescriptionVisualEditing}
80
- />
81
- ) : undefined;
82
-
83
- return (
84
- <div {...visualEditAttributes}>
85
- <ProductDetailsLayout
86
- visualEditingEnabled={productVisualEditingEnabled}
87
- descriptionNode={descriptionNode}
88
- reviewsNode={excludeProductId ? <ProductReviewsSection productId={excludeProductId} /> : undefined}
89
- />
90
- </div>
91
- );
92
- }
1
+ import React from 'react';
2
+ import { ProductDetailsLayout } from '@nextblock-cms/ecommerce/components/ProductDetailsLayout';
3
+ import type { VisualEditAttributes, VisualEditingDocumentContext } from '../../../lib/visual-editing/types';
4
+ import { headers } from 'next/headers';
5
+ import { createClient } from "@nextblock-cms/db/server";
6
+ import { getProviderReadiness } from "@nextblock-cms/ecommerce/server";
7
+ import BlockRenderer from "../../BlockRenderer";
8
+ import ProductReviewsSection from "../../ProductReviewsSection";
9
+ import ContactSellerSection from "../../ContactSellerSection";
10
+ import PaymentReadinessBoundary from "../../commerce/PaymentReadinessBoundary";
11
+
12
+ interface ProductDetailsBlockRendererProps {
13
+ visualEditAttributes?: VisualEditAttributes;
14
+ productVisualEditingEnabled?: boolean;
15
+ excludeProductId?: string;
16
+ languageId: number;
17
+ visualEditing?: VisualEditingDocumentContext;
18
+ }
19
+
20
+ export default async function ProductDetailsBlockRenderer({
21
+ visualEditAttributes,
22
+ productVisualEditingEnabled = false,
23
+ excludeProductId,
24
+ languageId,
25
+ visualEditing,
26
+ }: ProductDetailsBlockRendererProps) {
27
+ const supabase = createClient();
28
+
29
+ let descriptionBlocks: any[] = [];
30
+ let productSlug = "";
31
+ let productDraftId: number | null = null;
32
+ // Default to purchasable: a store that is set up correctly must never be degraded by
33
+ // a lookup that failed for some unrelated reason.
34
+ let canPurchase = true;
35
+
36
+ if (excludeProductId) {
37
+ const { data: productInfo } = await supabase
38
+ .from('products')
39
+ .select('slug, payment_provider')
40
+ .eq('id', excludeProductId)
41
+ .maybeSingle();
42
+
43
+ if (productInfo) {
44
+ productSlug = productInfo.slug;
45
+
46
+ const provider = productInfo.payment_provider;
47
+ if (provider === 'stripe' || provider === 'freemius') {
48
+ try {
49
+ const readiness = await getProviderReadiness(provider);
50
+ canPurchase = readiness.ready;
51
+ } catch (error) {
52
+ console.error('[ProductDetails] Could not resolve payment readiness:', error);
53
+ }
54
+ }
55
+ }
56
+
57
+ if (productVisualEditingEnabled) {
58
+ const { data: draftData } = await supabase
59
+ .from('product_drafts')
60
+ .select('id, blocks')
61
+ .eq('product_id', excludeProductId)
62
+ .maybeSingle();
63
+
64
+ if (draftData) {
65
+ productDraftId = draftData.id;
66
+ if (draftData.blocks && Array.isArray(draftData.blocks)) {
67
+ descriptionBlocks = draftData.blocks;
68
+ }
69
+ }
70
+ }
71
+
72
+ if (descriptionBlocks.length === 0) {
73
+ const { data: liveBlocks } = await supabase
74
+ .from('blocks')
75
+ .select('*')
76
+ .eq('product_id', excludeProductId)
77
+ .order('order', { ascending: true });
78
+ descriptionBlocks = liveBlocks || [];
79
+ }
80
+ }
81
+
82
+ const productDescriptionVisualEditing = {
83
+ enabled: productVisualEditingEnabled,
84
+ documentType: "product" as const,
85
+ documentId: excludeProductId!,
86
+ slug: productSlug,
87
+ languageId: languageId,
88
+ draftId: productDraftId,
89
+ };
90
+
91
+ // Bot protection for the enquiry form, resolved the same way BlockRenderer does for
92
+ // the contact-form block. Only read when the form will actually render.
93
+ let botProtection: { provider: 'none' | 'turnstile' | 'recaptcha'; siteKey: string } = {
94
+ provider: 'none',
95
+ siteKey: '',
96
+ };
97
+ let scriptNonce = '';
98
+
99
+ if (!canPurchase && excludeProductId) {
100
+ try {
101
+ scriptNonce = (await headers()).get('x-nonce') || '';
102
+ } catch (error) {
103
+ console.error('[Bot Protection] Error loading CSP nonce in ProductDetails:', error);
104
+ }
105
+
106
+ try {
107
+ const { data: publicSetting } = await supabase
108
+ .from('site_settings')
109
+ .select('value')
110
+ .eq('key', 'bot_protection_public')
111
+ .maybeSingle();
112
+ if (publicSetting?.value) {
113
+ const publicVal = publicSetting.value as Record<string, any>;
114
+ botProtection = {
115
+ provider: publicVal.provider || 'none',
116
+ siteKey: publicVal.siteKey || '',
117
+ };
118
+ }
119
+ } catch (error) {
120
+ console.error('[Bot Protection] Error loading settings in ProductDetails:', error);
121
+ }
122
+ }
123
+
124
+ const descriptionNode = descriptionBlocks.length > 0 ? (
125
+ <BlockRenderer
126
+ blocks={descriptionBlocks}
127
+ languageId={languageId}
128
+ productVisualEditingEnabled={productVisualEditingEnabled}
129
+ visualEditing={productDescriptionVisualEditing}
130
+ />
131
+ ) : undefined;
132
+
133
+ // Only resolved when actually needed — a working store pays nothing for this.
134
+ const purchaseFallbackNode =
135
+ !canPurchase && excludeProductId ? (
136
+ <ContactSellerSection
137
+ productId={excludeProductId}
138
+ botProtectionProvider={botProtection.provider}
139
+ botProtectionSiteKey={botProtection.siteKey}
140
+ scriptNonce={scriptNonce}
141
+ />
142
+ ) : undefined;
143
+
144
+ return (
145
+ <div {...visualEditAttributes}>
146
+ <PaymentReadinessBoundary>
147
+ <ProductDetailsLayout
148
+ visualEditingEnabled={productVisualEditingEnabled}
149
+ descriptionNode={descriptionNode}
150
+ reviewsNode={excludeProductId ? <ProductReviewsSection productId={excludeProductId} /> : undefined}
151
+ canPurchase={canPurchase}
152
+ purchaseFallbackNode={purchaseFallbackNode}
153
+ />
154
+ </PaymentReadinessBoundary>
155
+ </div>
156
+ );
157
+ }
@@ -1,31 +1,34 @@
1
- import React from 'react';
2
- import { ProductGridBlock } from '../../../lib/blocks/ProductGridBlock';
3
- import type { ProductGridBlockContent } from '../../../lib/blocks/ecommerce-block-schemas';
4
- import type { VisualEditAttributes } from '../../../lib/visual-editing/types';
5
-
6
- interface ProductGridBlockRendererProps {
7
- content: ProductGridBlockContent;
8
- languageId: number;
9
- excludeProductId?: string;
10
- excludeTranslationGroupId?: string | null;
11
- visualEditAttributes?: VisualEditAttributes;
12
- }
13
-
14
- export default function ProductGridBlockRenderer({
15
- content,
16
- languageId,
17
- excludeProductId,
18
- excludeTranslationGroupId,
19
- visualEditAttributes,
20
- }: ProductGridBlockRendererProps) {
21
- return (
22
- <div {...visualEditAttributes}>
23
- <ProductGridBlock
24
- content={content}
25
- languageId={languageId}
26
- excludeProductId={excludeProductId}
27
- excludeTranslationGroupId={excludeTranslationGroupId}
28
- />
29
- </div>
30
- );
31
- }
1
+ import React from 'react';
2
+ import PaymentReadinessBoundary from '../../commerce/PaymentReadinessBoundary';
3
+ import { ProductGridBlock } from '../../../lib/blocks/ProductGridBlock';
4
+ import type { ProductGridBlockContent } from '../../../lib/blocks/ecommerce-block-schemas';
5
+ import type { VisualEditAttributes } from '../../../lib/visual-editing/types';
6
+
7
+ interface ProductGridBlockRendererProps {
8
+ content: ProductGridBlockContent;
9
+ languageId: number;
10
+ excludeProductId?: string;
11
+ excludeTranslationGroupId?: string | null;
12
+ visualEditAttributes?: VisualEditAttributes;
13
+ }
14
+
15
+ export default function ProductGridBlockRenderer({
16
+ content,
17
+ languageId,
18
+ excludeProductId,
19
+ excludeTranslationGroupId,
20
+ visualEditAttributes,
21
+ }: ProductGridBlockRendererProps) {
22
+ return (
23
+ <div {...visualEditAttributes}>
24
+ <PaymentReadinessBoundary>
25
+ <ProductGridBlock
26
+ content={content}
27
+ languageId={languageId}
28
+ excludeProductId={excludeProductId}
29
+ excludeTranslationGroupId={excludeTranslationGroupId}
30
+ />
31
+ </PaymentReadinessBoundary>
32
+ </div>
33
+ );
34
+ }