create-nextblock 0.15.8 → 0.15.10

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 (71) hide show
  1. package/package.json +1 -1
  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/sandboxResetSql.ts +664 -1
  12. package/templates/nextblock-template/app/checkout/page.tsx +57 -52
  13. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +552 -529
  14. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +304 -181
  15. package/templates/nextblock-template/app/cms/components/ContactReminderBanner.tsx +75 -0
  16. package/templates/nextblock-template/app/cms/components/PaymentsReminderBanner.tsx +58 -0
  17. package/templates/nextblock-template/app/cms/components/VisibilityControl.tsx +542 -528
  18. package/templates/nextblock-template/app/cms/inquiries/actions.ts +66 -0
  19. package/templates/nextblock-template/app/cms/inquiries/page.tsx +12 -0
  20. package/templates/nextblock-template/app/cms/interactions/page.tsx +12 -51
  21. package/templates/nextblock-template/app/cms/layout.tsx +101 -73
  22. package/templates/nextblock-template/app/cms/messages/MessagesClient.tsx +661 -0
  23. package/templates/nextblock-template/app/cms/messages/actions.ts +404 -0
  24. package/templates/nextblock-template/app/cms/messages/loadInbox.ts +333 -0
  25. package/templates/nextblock-template/app/cms/messages/page.tsx +87 -0
  26. package/templates/nextblock-template/app/cms/messages/require-admin.ts +37 -0
  27. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +370 -362
  28. package/templates/nextblock-template/app/cms/revisions/service.ts +20 -0
  29. package/templates/nextblock-template/app/cms/settings/email/components/EmailForm.tsx +227 -185
  30. package/templates/nextblock-template/app/layout.tsx +671 -671
  31. package/templates/nextblock-template/app/product/[slug]/page.tsx +502 -482
  32. package/templates/nextblock-template/app/providers.tsx +96 -96
  33. package/templates/nextblock-template/app/thread/ThreadView.tsx +164 -0
  34. package/templates/nextblock-template/app/thread/[token]/route.ts +57 -0
  35. package/templates/nextblock-template/app/thread/layout.tsx +15 -0
  36. package/templates/nextblock-template/app/thread/page.tsx +98 -0
  37. package/templates/nextblock-template/components/BlockRenderer.tsx +312 -296
  38. package/templates/nextblock-template/components/ContactSellerSection.tsx +188 -0
  39. package/templates/nextblock-template/components/PostCommentsSection.tsx +378 -369
  40. package/templates/nextblock-template/components/ProductReviewsSection.tsx +426 -419
  41. package/templates/nextblock-template/components/StaffReplies.tsx +102 -0
  42. package/templates/nextblock-template/components/blocks/renderers/CartBlockRenderer.tsx +18 -17
  43. package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +20 -19
  44. package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +25 -22
  45. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +385 -381
  46. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +157 -92
  47. package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +34 -31
  48. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +612 -600
  49. package/templates/nextblock-template/components/commerce/PaymentReadinessBoundary.tsx +32 -0
  50. package/templates/nextblock-template/docs/14-MESSAGES-INBOX.md +309 -0
  51. package/templates/nextblock-template/docs/README.md +42 -41
  52. package/templates/nextblock-template/docs/assets/lighthouse-scores.png +0 -0
  53. package/templates/nextblock-template/lib/blocks/blockColors.test.ts +22 -2
  54. package/templates/nextblock-template/lib/blocks/blockColors.ts +44 -1
  55. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +761 -753
  56. package/templates/nextblock-template/lib/cms/contact-reminder.ts +64 -0
  57. package/templates/nextblock-template/lib/cms/payments-reminder.ts +98 -0
  58. package/templates/nextblock-template/lib/cms/unread-messages.ts +42 -0
  59. package/templates/nextblock-template/lib/commerce/seller-contact.ts +162 -0
  60. package/templates/nextblock-template/lib/config/email-settings.ts +323 -254
  61. package/templates/nextblock-template/lib/config/email-tls.test.ts +57 -0
  62. package/templates/nextblock-template/lib/email/placeholder-address.test.ts +59 -0
  63. package/templates/nextblock-template/lib/email/placeholder-address.ts +39 -0
  64. package/templates/nextblock-template/lib/messages/thread-reference.test.ts +70 -0
  65. package/templates/nextblock-template/lib/messages/thread-token.test.ts +93 -0
  66. package/templates/nextblock-template/lib/messages/thread-token.ts +157 -0
  67. package/templates/nextblock-template/lib/messages/threads.ts +579 -0
  68. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +20 -0
  69. package/templates/nextblock-template/lib/site-url.test.ts +89 -0
  70. package/templates/nextblock-template/lib/site-url.ts +102 -48
  71. package/templates/nextblock-template/package.json +1 -1
@@ -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
+ }