create-nextblock 0.16.3 → 0.17.0

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 (201) hide show
  1. package/CLAUDE.md +22 -0
  2. package/docker-template/.env.docker.example +69 -69
  3. package/docker-template/docker/db/init/99-jwt.sql +6 -6
  4. package/docker-template/docker/db/init/99-roles.sql +25 -25
  5. package/docker-template/docker/kong/kong.yml +112 -112
  6. package/docker-template/scripts/docker-setup.mjs +310 -310
  7. package/package.json +1 -1
  8. package/templates/nextblock-template/.browserslistrc +11 -11
  9. package/templates/nextblock-template/.swcrc +30 -30
  10. package/templates/nextblock-template/CLAUDE.md +26 -0
  11. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +46 -46
  12. package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -51
  13. package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -16
  14. package/templates/nextblock-template/app/actions/consent.ts +57 -57
  15. package/templates/nextblock-template/app/actions/twoFactorEmail.ts +22 -22
  16. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -153
  17. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -96
  18. package/templates/nextblock-template/app/api/brand/email-logo/route.ts +48 -48
  19. package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -29
  20. package/templates/nextblock-template/app/api/cms/check-updates/route.ts +44 -44
  21. package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -33
  22. package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -63
  23. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +270 -205
  24. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +10388 -9315
  25. package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +39 -39
  26. package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -92
  27. package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -43
  28. package/templates/nextblock-template/app/api/media/library/route.ts +69 -69
  29. package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -53
  30. package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -47
  31. package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -47
  32. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +442 -441
  33. package/templates/nextblock-template/app/article/[slug]/page.utils.ts +4 -1
  34. package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -20
  35. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +241 -241
  36. package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -160
  37. package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -167
  38. package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -122
  39. package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -23
  40. package/templates/nextblock-template/app/cms/components/SeoScoreBadge.tsx +3 -40
  41. package/templates/nextblock-template/app/cms/components/SystemAlertsBanner.tsx +112 -112
  42. package/templates/nextblock-template/app/cms/components/TablePagination.tsx +136 -136
  43. package/templates/nextblock-template/app/cms/components/TwoFactorReminderBanner.tsx +45 -45
  44. package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +102 -102
  45. package/templates/nextblock-template/app/cms/components/system-alerts-actions.ts +31 -31
  46. package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -16
  47. package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -16
  48. package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -66
  49. package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -519
  50. package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -256
  51. package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -384
  52. package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -221
  53. package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -12
  54. package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -438
  55. package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -200
  56. package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +130 -130
  57. package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -226
  58. package/templates/nextblock-template/app/cms/pages/page.tsx +273 -273
  59. package/templates/nextblock-template/app/cms/posts/page.tsx +251 -251
  60. package/templates/nextblock-template/app/cms/products/ProductFormClientShell.tsx +19 -0
  61. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +14 -2
  62. package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -12
  63. package/templates/nextblock-template/app/cms/products/new/page.tsx +135 -135
  64. package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -456
  65. package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -115
  66. package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -31
  67. package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -1004
  68. package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -29
  69. package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -24
  70. package/templates/nextblock-template/app/cms/settings/email/page.tsx +28 -28
  71. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +276 -276
  72. package/templates/nextblock-template/app/cms/settings/google-analytics/page.tsx +26 -26
  73. package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -21
  74. package/templates/nextblock-template/app/cms/settings/logos/components/SetActiveLogoButton.tsx +42 -42
  75. package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -133
  76. package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +27 -27
  77. package/templates/nextblock-template/app/cms/settings/registration/page.tsx +27 -27
  78. package/templates/nextblock-template/app/cms/settings/security/page.tsx +33 -33
  79. package/templates/nextblock-template/app/lib/site-settings.ts +105 -105
  80. package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -190
  81. package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -56
  82. package/templates/nextblock-template/app/setup/SetupWizard.tsx +678 -678
  83. package/templates/nextblock-template/app/setup/layout.tsx +13 -13
  84. package/templates/nextblock-template/app/setup/page.tsx +111 -111
  85. package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -38
  86. package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -68
  87. package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -35
  88. package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -35
  89. package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -35
  90. package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -34
  91. package/templates/nextblock-template/components/CartTranslator.tsx +210 -210
  92. package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -51
  93. package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -68
  94. package/templates/nextblock-template/components/DeferredGoogleAnalytics.tsx +70 -70
  95. package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -47
  96. package/templates/nextblock-template/components/GlobalSearch.tsx +557 -557
  97. package/templates/nextblock-template/components/Header.tsx +38 -38
  98. package/templates/nextblock-template/components/PublicEnvBootstrap.tsx +30 -30
  99. package/templates/nextblock-template/components/ResponsiveNav.tsx +14 -14
  100. package/templates/nextblock-template/components/auth/AuthBotProtection.tsx +182 -182
  101. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +48 -48
  102. package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +9 -9
  103. package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -25
  104. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -92
  105. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +11 -0
  106. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +24 -24
  107. package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -57
  108. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +2 -2
  109. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +2 -2
  110. package/templates/nextblock-template/components/media/YouTubeFacade.tsx +105 -105
  111. package/templates/nextblock-template/components/media/youtube-embed-replace.tsx +32 -32
  112. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +170 -170
  113. package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +70 -70
  114. package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -28
  115. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -166
  116. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +471 -471
  117. package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -21
  118. package/templates/nextblock-template/context/language-rest-client.ts +32 -32
  119. package/templates/nextblock-template/docker/db/init/99-jwt.sql +6 -6
  120. package/templates/nextblock-template/docker/db/init/99-roles.sql +25 -25
  121. package/templates/nextblock-template/docker/kong/kong.yml +112 -112
  122. package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -364
  123. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +11 -0
  124. package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -146
  125. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +19 -0
  126. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -222
  127. package/templates/nextblock-template/lib/app-secrets.ts +39 -39
  128. package/templates/nextblock-template/lib/auth/cookies.ts +47 -47
  129. package/templates/nextblock-template/lib/auth/crypto.ts +45 -45
  130. package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -92
  131. package/templates/nextblock-template/lib/blocks/README.md +13 -13
  132. package/templates/nextblock-template/lib/botProtection/verify.ts +134 -134
  133. package/templates/nextblock-template/lib/cms/payments-reminder.test.ts +135 -135
  134. package/templates/nextblock-template/lib/cms/payments-reminder.ts +105 -105
  135. package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -145
  136. package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -87
  137. package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -178
  138. package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -140
  139. package/templates/nextblock-template/lib/custom-block-r2-upload.ts +88 -88
  140. package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -227
  141. package/templates/nextblock-template/lib/custom-block-relations.ts +279 -279
  142. package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -14
  143. package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -172
  144. package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -213
  145. package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -22
  146. package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -193
  147. package/templates/nextblock-template/lib/email/branding-format.test.ts +133 -133
  148. package/templates/nextblock-template/lib/email/branding-format.ts +123 -123
  149. package/templates/nextblock-template/lib/email/branding.ts +76 -76
  150. package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -121
  151. package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -206
  152. package/templates/nextblock-template/lib/logos/active-logo.ts +53 -53
  153. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +56 -54
  154. package/templates/nextblock-template/lib/media/youtube.ts +99 -99
  155. package/templates/nextblock-template/lib/onboarding/actions.ts +31 -31
  156. package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -57
  157. package/templates/nextblock-template/lib/privacy/contact-emails.ts +64 -64
  158. package/templates/nextblock-template/lib/privacy/settings.ts +115 -115
  159. package/templates/nextblock-template/lib/privacy/types.ts +69 -69
  160. package/templates/nextblock-template/lib/promotions/server.test.ts +74 -74
  161. package/templates/nextblock-template/lib/promotions/server.ts +741 -741
  162. package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -142
  163. package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -255
  164. package/templates/nextblock-template/lib/seo/page-audit-context.tsx +3 -3
  165. package/templates/nextblock-template/lib/seo/page-document.test.ts +34 -0
  166. package/templates/nextblock-template/lib/seo/page-document.ts +3 -462
  167. package/templates/nextblock-template/lib/seo/robots-txt.ts +5 -4
  168. package/templates/nextblock-template/lib/setup/actions.ts +460 -460
  169. package/templates/nextblock-template/lib/setup/env-status.ts +125 -125
  170. package/templates/nextblock-template/lib/setup/env-write.ts +111 -111
  171. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +50 -10
  172. package/templates/nextblock-template/lib/setup/provisioning.ts +59 -59
  173. package/templates/nextblock-template/lib/setup/schema-apply.ts +408 -408
  174. package/templates/nextblock-template/lib/setup/system-config.ts +105 -105
  175. package/templates/nextblock-template/lib/setup/types.ts +18 -18
  176. package/templates/nextblock-template/lib/storage/provider.ts +66 -66
  177. package/templates/nextblock-template/lib/storage/supabase-storage.ts +103 -103
  178. package/templates/nextblock-template/lib/updates/github-device.ts +206 -206
  179. package/templates/nextblock-template/lib/updates/repo-identity.ts +56 -56
  180. package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -105
  181. package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -42
  182. package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -143
  183. package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -94
  184. package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -81
  185. package/templates/nextblock-template/lib/zod-config.ts +5 -5
  186. package/templates/nextblock-template/next-env.d.ts +1 -0
  187. package/templates/nextblock-template/package.json +1 -1
  188. package/templates/nextblock-template/public/images/cortex_post.webp +0 -0
  189. package/templates/nextblock-template/public/images/update_nextblock.webp +0 -0
  190. package/templates/nextblock-template/scripts/docker-setup.mjs +310 -310
  191. package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -112
  192. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +98 -98
  193. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -62
  194. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -537
  195. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -58
  196. package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -188
  197. package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -123
  198. package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -133
  199. package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -65
  200. package/templates/nextblock-template/tools/deploy-supabase.js +159 -159
  201. package/templates/nextblock-template/types/jsdom.d.ts +6 -6
@@ -1,68 +1,68 @@
1
- "use client";
2
-
3
- import dynamic from "next/dynamic";
4
- import { Search } from "lucide-react";
5
- import { useEffect, useState } from "react";
6
- import { Button } from "@nextblock-cms/ui";
7
- import { cn } from "@nextblock-cms/utils";
8
-
9
- type TriggerVariant = "desktop" | "mobile";
10
-
11
- interface DeferredGlobalSearchProps {
12
- isEcommerceActive: boolean;
13
- variant: TriggerVariant;
14
- }
15
-
16
- const GlobalSearch = dynamic(() => import("./GlobalSearch"), {
17
- ssr: false,
18
- loading: () => null,
19
- });
20
-
21
- export function DeferredGlobalSearch(props: DeferredGlobalSearchProps) {
22
- const [shouldLoad, setShouldLoad] = useState(false);
23
- const [openOnMount, setOpenOnMount] = useState(false);
24
-
25
- const openSearch = () => {
26
- setOpenOnMount(true);
27
- setShouldLoad(true);
28
- };
29
-
30
- useEffect(() => {
31
- if (props.variant !== "desktop") {
32
- return;
33
- }
34
-
35
- const handleKeyDown = (event: KeyboardEvent) => {
36
- if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
37
- event.preventDefault();
38
- openSearch();
39
- }
40
- };
41
-
42
- window.addEventListener("keydown", handleKeyDown);
43
- return () => window.removeEventListener("keydown", handleKeyDown);
44
- }, [props.variant]);
45
-
46
- if (shouldLoad) {
47
- return <GlobalSearch {...props} openOnMount={openOnMount} />;
48
- }
49
-
50
- return (
51
- <Button
52
- type="button"
53
- variant="outline"
54
- size={props.variant === "mobile" ? "icon" : "default"}
55
- className={cn(
56
- "shrink-0 border-foreground/15 bg-background/70",
57
- props.variant === "desktop" && "h-9 gap-2 px-3 text-sm",
58
- props.variant === "mobile" && "h-10 w-10"
59
- )}
60
- aria-label="Search"
61
- aria-keyshortcuts="Control+K Meta+K"
62
- onClick={openSearch}
63
- >
64
- <Search className="h-4 w-4" />
65
- {props.variant === "desktop" ? <span>Search</span> : null}
66
- </Button>
67
- );
68
- }
1
+ "use client";
2
+
3
+ import dynamic from "next/dynamic";
4
+ import { Search } from "lucide-react";
5
+ import { useEffect, useState } from "react";
6
+ import { Button } from "@nextblock-cms/ui";
7
+ import { cn } from "@nextblock-cms/utils";
8
+
9
+ type TriggerVariant = "desktop" | "mobile";
10
+
11
+ interface DeferredGlobalSearchProps {
12
+ isEcommerceActive: boolean;
13
+ variant: TriggerVariant;
14
+ }
15
+
16
+ const GlobalSearch = dynamic(() => import("./GlobalSearch"), {
17
+ ssr: false,
18
+ loading: () => null,
19
+ });
20
+
21
+ export function DeferredGlobalSearch(props: DeferredGlobalSearchProps) {
22
+ const [shouldLoad, setShouldLoad] = useState(false);
23
+ const [openOnMount, setOpenOnMount] = useState(false);
24
+
25
+ const openSearch = () => {
26
+ setOpenOnMount(true);
27
+ setShouldLoad(true);
28
+ };
29
+
30
+ useEffect(() => {
31
+ if (props.variant !== "desktop") {
32
+ return;
33
+ }
34
+
35
+ const handleKeyDown = (event: KeyboardEvent) => {
36
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
37
+ event.preventDefault();
38
+ openSearch();
39
+ }
40
+ };
41
+
42
+ window.addEventListener("keydown", handleKeyDown);
43
+ return () => window.removeEventListener("keydown", handleKeyDown);
44
+ }, [props.variant]);
45
+
46
+ if (shouldLoad) {
47
+ return <GlobalSearch {...props} openOnMount={openOnMount} />;
48
+ }
49
+
50
+ return (
51
+ <Button
52
+ type="button"
53
+ variant="outline"
54
+ size={props.variant === "mobile" ? "icon" : "default"}
55
+ className={cn(
56
+ "shrink-0 border-foreground/15 bg-background/70",
57
+ props.variant === "desktop" && "h-9 gap-2 px-3 text-sm",
58
+ props.variant === "mobile" && "h-10 w-10"
59
+ )}
60
+ aria-label="Search"
61
+ aria-keyshortcuts="Control+K Meta+K"
62
+ onClick={openSearch}
63
+ >
64
+ <Search className="h-4 w-4" />
65
+ {props.variant === "desktop" ? <span>Search</span> : null}
66
+ </Button>
67
+ );
68
+ }
@@ -1,70 +1,70 @@
1
- "use client";
2
-
3
- import { useEffect, useState, type ComponentType } from "react";
4
-
5
- interface DeferredGoogleAnalyticsProps {
6
- gaId?: string;
7
- nonce?: string;
8
- }
9
-
10
- const interactionEvents: Array<keyof WindowEventMap> = [
11
- "pointerdown",
12
- "keydown",
13
- "scroll",
14
- "touchstart",
15
- ];
16
-
17
- type GoogleAnalyticsComponent = ComponentType<{
18
- gaId: string;
19
- nonce?: string;
20
- }>;
21
-
22
- export function DeferredGoogleAnalytics({
23
- gaId,
24
- nonce,
25
- }: DeferredGoogleAnalyticsProps) {
26
- const [GoogleAnalytics, setGoogleAnalytics] =
27
- useState<GoogleAnalyticsComponent | null>(null);
28
-
29
- useEffect(() => {
30
- if (!gaId) {
31
- return;
32
- }
33
-
34
- let isMounted = true;
35
-
36
- function removeListeners() {
37
- interactionEvents.forEach((eventName) => {
38
- window.removeEventListener(eventName, enableGa);
39
- });
40
- }
41
-
42
- function enableGa() {
43
- removeListeners();
44
-
45
- void import("@next/third-parties/google").then(({ GoogleAnalytics }) => {
46
- if (isMounted) {
47
- setGoogleAnalytics(() => GoogleAnalytics as GoogleAnalyticsComponent);
48
- }
49
- });
50
- }
51
-
52
- interactionEvents.forEach((eventName) => {
53
- window.addEventListener(eventName, enableGa, {
54
- once: true,
55
- passive: true,
56
- });
57
- });
58
-
59
- return () => {
60
- isMounted = false;
61
- removeListeners();
62
- };
63
- }, [gaId]);
64
-
65
- if (!gaId || !GoogleAnalytics) {
66
- return null;
67
- }
68
-
69
- return <GoogleAnalytics gaId={gaId} nonce={nonce} />;
70
- }
1
+ "use client";
2
+
3
+ import { useEffect, useState, type ComponentType } from "react";
4
+
5
+ interface DeferredGoogleAnalyticsProps {
6
+ gaId?: string;
7
+ nonce?: string;
8
+ }
9
+
10
+ const interactionEvents: Array<keyof WindowEventMap> = [
11
+ "pointerdown",
12
+ "keydown",
13
+ "scroll",
14
+ "touchstart",
15
+ ];
16
+
17
+ type GoogleAnalyticsComponent = ComponentType<{
18
+ gaId: string;
19
+ nonce?: string;
20
+ }>;
21
+
22
+ export function DeferredGoogleAnalytics({
23
+ gaId,
24
+ nonce,
25
+ }: DeferredGoogleAnalyticsProps) {
26
+ const [GoogleAnalytics, setGoogleAnalytics] =
27
+ useState<GoogleAnalyticsComponent | null>(null);
28
+
29
+ useEffect(() => {
30
+ if (!gaId) {
31
+ return;
32
+ }
33
+
34
+ let isMounted = true;
35
+
36
+ function removeListeners() {
37
+ interactionEvents.forEach((eventName) => {
38
+ window.removeEventListener(eventName, enableGa);
39
+ });
40
+ }
41
+
42
+ function enableGa() {
43
+ removeListeners();
44
+
45
+ void import("@next/third-parties/google").then(({ GoogleAnalytics }) => {
46
+ if (isMounted) {
47
+ setGoogleAnalytics(() => GoogleAnalytics as GoogleAnalyticsComponent);
48
+ }
49
+ });
50
+ }
51
+
52
+ interactionEvents.forEach((eventName) => {
53
+ window.addEventListener(eventName, enableGa, {
54
+ once: true,
55
+ passive: true,
56
+ });
57
+ });
58
+
59
+ return () => {
60
+ isMounted = false;
61
+ removeListeners();
62
+ };
63
+ }, [gaId]);
64
+
65
+ if (!gaId || !GoogleAnalytics) {
66
+ return null;
67
+ }
68
+
69
+ return <GoogleAnalytics gaId={gaId} nonce={nonce} />;
70
+ }
@@ -1,47 +1,47 @@
1
- import Image from "next/image";
2
-
3
- const DEFAULT_FRAME_CLASS_NAME =
4
- "bg-[radial-gradient(circle_at_top,_rgba(59,130,246,0.18),_transparent_45%),linear-gradient(135deg,#020617,#111827_55%,#1f2937)]";
5
-
6
- interface FeatureImageHeroProps {
7
- alt: string;
8
- blurDataURL?: string | null;
9
- frameClassName?: string;
10
- height?: number | null;
11
- imageClassName?: string;
12
- imageUrl: string;
13
- priority?: boolean;
14
- width?: number | null;
15
- }
16
-
17
- export default function FeatureImageHero({
18
- alt,
19
- blurDataURL,
20
- frameClassName = DEFAULT_FRAME_CLASS_NAME,
21
- height,
22
- imageClassName = "h-full w-full object-cover",
23
- imageUrl,
24
- priority = false,
25
- width,
26
- }: FeatureImageHeroProps) {
27
- return (
28
- <div
29
- className={`overflow-hidden rounded-[2rem] border border-slate-200/80 shadow-[0_28px_80px_-32px_rgba(15,23,42,0.45)] dark:border-white/10 ${frameClassName}`}
30
- >
31
- <div className="relative aspect-[16/10] md:aspect-[2.35/1]">
32
- <Image
33
- src={imageUrl}
34
- alt={alt}
35
- width={width && width > 0 ? width : 1600}
36
- height={height && height > 0 ? height : 900}
37
- placeholder={blurDataURL ? "blur" : "empty"}
38
- blurDataURL={blurDataURL ?? undefined}
39
- sizes="(max-width: 768px) 100vw, 1200px"
40
- className={imageClassName}
41
- priority={priority}
42
- />
43
- <div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-slate-950/70 via-slate-950/10 to-transparent" />
44
- </div>
45
- </div>
46
- );
47
- }
1
+ import Image from "next/image";
2
+
3
+ const DEFAULT_FRAME_CLASS_NAME =
4
+ "bg-[radial-gradient(circle_at_top,_rgba(59,130,246,0.18),_transparent_45%),linear-gradient(135deg,#020617,#111827_55%,#1f2937)]";
5
+
6
+ interface FeatureImageHeroProps {
7
+ alt: string;
8
+ blurDataURL?: string | null;
9
+ frameClassName?: string;
10
+ height?: number | null;
11
+ imageClassName?: string;
12
+ imageUrl: string;
13
+ priority?: boolean;
14
+ width?: number | null;
15
+ }
16
+
17
+ export default function FeatureImageHero({
18
+ alt,
19
+ blurDataURL,
20
+ frameClassName = DEFAULT_FRAME_CLASS_NAME,
21
+ height,
22
+ imageClassName = "h-full w-full object-cover",
23
+ imageUrl,
24
+ priority = false,
25
+ width,
26
+ }: FeatureImageHeroProps) {
27
+ return (
28
+ <div
29
+ className={`overflow-hidden rounded-[2rem] border border-slate-200/80 shadow-[0_28px_80px_-32px_rgba(15,23,42,0.45)] dark:border-white/10 ${frameClassName}`}
30
+ >
31
+ <div className="relative aspect-[16/10] md:aspect-[2.35/1]">
32
+ <Image
33
+ src={imageUrl}
34
+ alt={alt}
35
+ width={width && width > 0 ? width : 1600}
36
+ height={height && height > 0 ? height : 900}
37
+ placeholder={blurDataURL ? "blur" : "empty"}
38
+ blurDataURL={blurDataURL ?? undefined}
39
+ sizes="(max-width: 768px) 100vw, 1200px"
40
+ className={imageClassName}
41
+ priority={priority}
42
+ />
43
+ <div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-slate-950/70 via-slate-950/10 to-transparent" />
44
+ </div>
45
+ </div>
46
+ );
47
+ }