astro-tractstack 2.0.15 → 2.0.16
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/dist/index.js +27 -13
- package/package.json +1 -1
- package/templates/custom/with-examples/CodeHook.astro +4 -0
- package/templates/custom/with-examples/SandboxLauncher.tsx +65 -0
- package/templates/env.example +3 -0
- package/templates/src/components/codehooks/SandboxAuthWrapper.tsx +75 -0
- package/templates/src/components/codehooks/SandboxRegisterForm.tsx +202 -0
- package/templates/src/components/compositor/Compositor.tsx +2 -0
- package/templates/src/components/compositor/Node.tsx +6 -1
- package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +13 -11
- package/templates/src/components/compositor/nodes/Pane_layout.tsx +16 -14
- package/templates/src/components/edit/Header.tsx +8 -2
- package/templates/src/components/edit/PanelSwitch.tsx +4 -4
- package/templates/src/components/edit/pane/AddPanePanel.tsx +3 -0
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +28 -24
- package/templates/src/components/edit/panels/StyleImagePanel.tsx +10 -8
- package/templates/src/components/edit/state/SaveModal.tsx +41 -0
- package/templates/src/pages/api/sandbox.ts +86 -0
- package/templates/src/pages/sandbox.astro +137 -0
- package/templates/src/types/nodeProps.ts +1 -0
- package/templates/src/utils/profileStorage.ts +13 -0
- package/utils/inject-files.ts +27 -14
- package/templates/src/components/edit/pane/AiPaneGenerator.tsx +0 -512
- package/templates/src/components/edit/pane/AiPanePreview.tsx +0 -107
- package/templates/src/utils/aai/getTitleSlug.ts +0 -72
package/dist/index.js
CHANGED
|
@@ -138,6 +138,18 @@ async function w(t, e, c) {
|
|
|
138
138
|
),
|
|
139
139
|
dest: "src/components/compositor/nodes/Pane_layout.tsx"
|
|
140
140
|
},
|
|
141
|
+
{
|
|
142
|
+
src: t(
|
|
143
|
+
"../templates/src/components/codehooks/SandboxAuthWrapper.tsx"
|
|
144
|
+
),
|
|
145
|
+
dest: "src/components/codehooks/SandboxAuthWrapper.tsx"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
src: t(
|
|
149
|
+
"../templates/src/components/codehooks/SandboxRegisterForm.tsx"
|
|
150
|
+
),
|
|
151
|
+
dest: "src/components/codehooks/SandboxRegisterForm.tsx"
|
|
152
|
+
},
|
|
141
153
|
{
|
|
142
154
|
src: t("../templates/src/components/compositor/nodes/Markdown.tsx"),
|
|
143
155
|
dest: "src/components/compositor/nodes/Markdown.tsx"
|
|
@@ -440,14 +452,6 @@ async function w(t, e, c) {
|
|
|
440
452
|
),
|
|
441
453
|
dest: "src/components/edit/pane/RestylePaneModal.tsx"
|
|
442
454
|
},
|
|
443
|
-
{
|
|
444
|
-
src: t("../templates/src/components/edit/pane/AiPaneGenerator.tsx"),
|
|
445
|
-
dest: "src/components/edit/pane/AiPaneGenerator.tsx"
|
|
446
|
-
},
|
|
447
|
-
{
|
|
448
|
-
src: t("../templates/src/components/edit/pane/AiPanePreview.tsx"),
|
|
449
|
-
dest: "src/components/edit/pane/AiPanePreview.tsx"
|
|
450
|
-
},
|
|
451
455
|
{
|
|
452
456
|
src: t(
|
|
453
457
|
"../templates/src/components/edit/pane/steps/CopyInputStep.tsx"
|
|
@@ -599,11 +603,6 @@ async function w(t, e, c) {
|
|
|
599
603
|
src: t("../templates/src/stores/selection.ts"),
|
|
600
604
|
dest: "src/stores/selection.ts"
|
|
601
605
|
},
|
|
602
|
-
// AAI utils
|
|
603
|
-
{
|
|
604
|
-
src: t("../templates/src/utils/aai/getTitleSlug.ts"),
|
|
605
|
-
dest: "src/utils/aai/getTitleSlug.ts"
|
|
606
|
-
},
|
|
607
606
|
// Compositor utils - etl
|
|
608
607
|
{
|
|
609
608
|
src: t("../templates/src/utils/etl/index.ts"),
|
|
@@ -834,6 +833,10 @@ async function w(t, e, c) {
|
|
|
834
833
|
),
|
|
835
834
|
dest: "src/pages/context/[...contextSlug]/edit.astro"
|
|
836
835
|
},
|
|
836
|
+
{
|
|
837
|
+
src: t("../templates/src/pages/sandbox.astro"),
|
|
838
|
+
dest: "src/pages/sandbox.astro"
|
|
839
|
+
},
|
|
837
840
|
{
|
|
838
841
|
src: t("../templates/src/pages/storykeep.astro"),
|
|
839
842
|
dest: "src/pages/storykeep.astro"
|
|
@@ -878,6 +881,10 @@ async function w(t, e, c) {
|
|
|
878
881
|
src: t("../templates/src/pages/api/tailwind.ts"),
|
|
879
882
|
dest: "src/pages/api/tailwind.ts"
|
|
880
883
|
},
|
|
884
|
+
{
|
|
885
|
+
src: t("../templates/src/pages/api/sandbox.ts"),
|
|
886
|
+
dest: "src/pages/api/sandbox.ts"
|
|
887
|
+
},
|
|
881
888
|
// Authentication Pages
|
|
882
889
|
{
|
|
883
890
|
src: t("../templates/src/pages/storykeep/login.astro"),
|
|
@@ -2110,6 +2117,13 @@ async function w(t, e, c) {
|
|
|
2110
2117
|
},
|
|
2111
2118
|
// Example Components (Conditional)
|
|
2112
2119
|
...c?.includeExamples ? [
|
|
2120
|
+
{
|
|
2121
|
+
src: t(
|
|
2122
|
+
"../templates/custom/with-examples/SandboxLauncher.tsx"
|
|
2123
|
+
),
|
|
2124
|
+
dest: "src/custom/SandboxLauncher.tsx",
|
|
2125
|
+
protected: !0
|
|
2126
|
+
},
|
|
2113
2127
|
{
|
|
2114
2128
|
src: t("../templates/custom/with-examples/CustomHero.astro"),
|
|
2115
2129
|
dest: "src/custom/CustomHero.astro",
|
package/package.json
CHANGED
|
@@ -7,6 +7,7 @@ import BunnyVideoWrapper from '@/components/codehooks/BunnyVideoWrapper.astro';
|
|
|
7
7
|
import EpinetWrapper from '@/components/codehooks/EpinetWrapper';
|
|
8
8
|
import ProductCardWrapper from './ProductCardWrapper.astro';
|
|
9
9
|
import ProductGrid from './ProductGrid.astro';
|
|
10
|
+
import SandboxLauncher from './SandboxLauncher';
|
|
10
11
|
import type { FullContentMapItem } from '@/types/tractstack';
|
|
11
12
|
import type { ResourceNode } from '@/types/compositorTypes';
|
|
12
13
|
|
|
@@ -30,6 +31,7 @@ export const components = {
|
|
|
30
31
|
'search-widget': true,
|
|
31
32
|
'product-card': true,
|
|
32
33
|
'product-grid': true,
|
|
34
|
+
'get-crafting': true,
|
|
33
35
|
'bunny-video': import.meta.env.PUBLIC_ENABLE_BUNNY === 'true',
|
|
34
36
|
epinet: true,
|
|
35
37
|
};
|
|
@@ -48,6 +50,8 @@ export const components = {
|
|
|
48
50
|
<SearchWidget fullContentMap={fullContentMap} client:load />
|
|
49
51
|
) : target === 'bunny-video' && import.meta.env.PUBLIC_ENABLE_BUNNY ? (
|
|
50
52
|
<BunnyVideoWrapper options={options} />
|
|
53
|
+
) : target === 'get-crafting' ? (
|
|
54
|
+
<SandboxLauncher client:only="react" />
|
|
51
55
|
) : target === 'custom-hero' ? (
|
|
52
56
|
<CustomHero />
|
|
53
57
|
) : target === 'epinet' ? (
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { ProfileStorage } from '@/utils/profileStorage';
|
|
3
|
+
import SandboxRegisterForm from '@/components/codehooks/SandboxRegisterForm';
|
|
4
|
+
|
|
5
|
+
export default function SandboxLauncher() {
|
|
6
|
+
const [profileExists, setProfileExists] = useState<boolean | null>(null);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
// This check must run on the client to access localStorage
|
|
10
|
+
setProfileExists(ProfileStorage.hasProfile());
|
|
11
|
+
}, []);
|
|
12
|
+
|
|
13
|
+
const handleRegistrationSuccess = () => {
|
|
14
|
+
setProfileExists(true);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Avoid a flash of the wrong state during server-side rendering
|
|
18
|
+
if (profileExists === null) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="mx-auto my-16 max-w-6xl px-4 md:my-24">
|
|
24
|
+
<div className="flex flex-col items-center gap-12 md:flex-row md:gap-16">
|
|
25
|
+
{/* Column 1: The Pitch (Always Visible) */}
|
|
26
|
+
<div className="px-6 text-right md:w-1/2">
|
|
27
|
+
<h2 className="text-4xl font-bold text-gray-900 md:text-5xl">
|
|
28
|
+
Press <span className="italic text-blue-600">your own</span> Tract
|
|
29
|
+
Stack
|
|
30
|
+
</h2>
|
|
31
|
+
<p className="mt-4 text-lg text-gray-600">
|
|
32
|
+
Create an interactive webpage in a sandbox! No credit card required.
|
|
33
|
+
</p>
|
|
34
|
+
<p className="mt-8 text-sm text-gray-500">
|
|
35
|
+
Already connected?{' '}
|
|
36
|
+
<a
|
|
37
|
+
href="/storykeep/profile"
|
|
38
|
+
className="font-bold text-blue-600 underline hover:text-blue-500"
|
|
39
|
+
>
|
|
40
|
+
Unlock your profile
|
|
41
|
+
</a>
|
|
42
|
+
</p>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
{/* Column 2: The Action (Switches between Form and Button) */}
|
|
46
|
+
<div className="w-full max-w-xl md:w-1/2">
|
|
47
|
+
{!profileExists ? (
|
|
48
|
+
<SandboxRegisterForm onSuccess={handleRegistrationSuccess} />
|
|
49
|
+
) : (
|
|
50
|
+
<div className="flex justify-center md:justify-start">
|
|
51
|
+
<div className="flex justify-center md:justify-start">
|
|
52
|
+
<a
|
|
53
|
+
className="transform rounded-xl bg-blue-600 px-6 py-6 text-3xl font-bold text-white shadow-xl transition-transform hover:scale-105 md:text-5xl"
|
|
54
|
+
href="/sandbox"
|
|
55
|
+
>
|
|
56
|
+
Get Crafting
|
|
57
|
+
</a>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
package/templates/env.example
CHANGED
|
@@ -6,3 +6,6 @@ PUBLIC_GO_BACKEND=http://localhost:8080
|
|
|
6
6
|
PUBLIC_TENANTID=default
|
|
7
7
|
# OPTIONAL: Multi-Tenant Configuration
|
|
8
8
|
PUBLIC_ENABLE_MULTI_TENANT=false
|
|
9
|
+
# OPTIONAL: Enable Sandbox DEMO mode
|
|
10
|
+
# ***SPECIAL*** has security implications
|
|
11
|
+
#PRIVATE_SANDBOX_SECRET=strong-secret-password
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { Dialog } from '@ark-ui/react/dialog';
|
|
3
|
+
import { Portal } from '@ark-ui/react/portal';
|
|
4
|
+
import XMarkIcon from '@heroicons/react/24/solid/XMarkIcon';
|
|
5
|
+
import { ProfileStorage } from '@/utils/profileStorage';
|
|
6
|
+
import SandboxRegisterForm from '@/components/codehooks/SandboxRegisterForm';
|
|
7
|
+
|
|
8
|
+
export default function SandboxAuthWrapper() {
|
|
9
|
+
const [profileExists, setProfileExists] = useState<boolean | null>(null);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
setProfileExists(ProfileStorage.hasProfile());
|
|
13
|
+
}, []);
|
|
14
|
+
|
|
15
|
+
const handleRegistrationSuccess = () => {
|
|
16
|
+
setProfileExists(true);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const handleClose = () => {
|
|
20
|
+
window.location.href = '/';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
if (profileExists === null || profileExists === true) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Dialog.Root open={true} modal={true} trapFocus={false}>
|
|
29
|
+
<Portal>
|
|
30
|
+
<Dialog.Backdrop
|
|
31
|
+
className="fixed inset-0 bg-black bg-opacity-75"
|
|
32
|
+
style={{ zIndex: 9005 }}
|
|
33
|
+
/>
|
|
34
|
+
<Dialog.Positioner
|
|
35
|
+
className="fixed inset-0 flex items-center justify-center p-4"
|
|
36
|
+
style={{ zIndex: 9005 }}
|
|
37
|
+
>
|
|
38
|
+
<Dialog.Content className="relative grid w-full max-w-6xl grid-cols-1 overflow-hidden rounded-lg bg-white shadow-2xl md:grid-cols-2">
|
|
39
|
+
<button
|
|
40
|
+
onClick={handleClose}
|
|
41
|
+
className="absolute right-4 top-4 z-10 rounded-full bg-gray-100 p-2 text-gray-600 shadow-sm transition-colors hover:bg-gray-200"
|
|
42
|
+
title="Close and exit Sandbox"
|
|
43
|
+
>
|
|
44
|
+
<XMarkIcon className="h-5 w-5" />
|
|
45
|
+
</button>
|
|
46
|
+
|
|
47
|
+
<div className="flex flex-col justify-center bg-gray-50 p-8 text-right">
|
|
48
|
+
<h2 className="text-4xl font-bold text-gray-900 md:text-5xl">
|
|
49
|
+
Press <span className="italic text-blue-600">your own</span>{' '}
|
|
50
|
+
Tract Stack
|
|
51
|
+
</h2>
|
|
52
|
+
<p className="mt-4 text-lg text-gray-600">
|
|
53
|
+
Create an interactive webpage in a sandbox! No credit card
|
|
54
|
+
required.
|
|
55
|
+
</p>
|
|
56
|
+
<p className="mt-8 text-sm text-gray-500">
|
|
57
|
+
Already connected?{' '}
|
|
58
|
+
<a
|
|
59
|
+
href="/storykeep/profile"
|
|
60
|
+
className="font-bold text-blue-600 underline hover:text-blue-500"
|
|
61
|
+
>
|
|
62
|
+
Unlock your profile
|
|
63
|
+
</a>
|
|
64
|
+
</p>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div className="flex flex-col justify-center p-8">
|
|
68
|
+
<SandboxRegisterForm onSuccess={handleRegistrationSuccess} />
|
|
69
|
+
</div>
|
|
70
|
+
</Dialog.Content>
|
|
71
|
+
</Dialog.Positioner>
|
|
72
|
+
</Portal>
|
|
73
|
+
</Dialog.Root>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { ProfileStorage } from '@/utils/profileStorage';
|
|
3
|
+
import type { FormEvent } from 'react';
|
|
4
|
+
|
|
5
|
+
interface SandboxRegisterFormProps {
|
|
6
|
+
onSuccess: () => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function createProfile(payload: {
|
|
10
|
+
firstname: string;
|
|
11
|
+
email: string;
|
|
12
|
+
codeword: string;
|
|
13
|
+
persona: 'major' | 'none';
|
|
14
|
+
}) {
|
|
15
|
+
try {
|
|
16
|
+
const sessionData = ProfileStorage.prepareHandshakeData();
|
|
17
|
+
const currentConsent = payload.persona === 'major' ? '1' : '0';
|
|
18
|
+
|
|
19
|
+
const response = await fetch('/api/auth/profile', {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: { 'Content-Type': 'application/json' },
|
|
22
|
+
body: JSON.stringify({
|
|
23
|
+
firstname: payload.firstname,
|
|
24
|
+
email: payload.email,
|
|
25
|
+
codeword: payload.codeword,
|
|
26
|
+
contactPersona: payload.persona,
|
|
27
|
+
shortBio: '',
|
|
28
|
+
sessionId: sessionData.sessionId,
|
|
29
|
+
consent: currentConsent,
|
|
30
|
+
isUpdate: false,
|
|
31
|
+
}),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const result = await response.json();
|
|
35
|
+
|
|
36
|
+
if (!result.success) {
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
error: result.error || 'Profile creation failed',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (result.profile) {
|
|
44
|
+
ProfileStorage.setProfileData({
|
|
45
|
+
firstname: result.profile.firstname,
|
|
46
|
+
contactPersona: result.profile.contactPersona,
|
|
47
|
+
email: result.profile.email,
|
|
48
|
+
shortBio: result.profile.shortBio,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (result.token) {
|
|
52
|
+
ProfileStorage.storeProfileToken(result.token);
|
|
53
|
+
}
|
|
54
|
+
if (result.encryptedEmail && result.encryptedCode) {
|
|
55
|
+
ProfileStorage.storeEncryptedCredentials(
|
|
56
|
+
result.encryptedEmail,
|
|
57
|
+
result.encryptedCode
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (result.consent) {
|
|
61
|
+
ProfileStorage.storeConsent(result.consent);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { success: true };
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.error('Sandbox profile creation error:', e);
|
|
67
|
+
return { success: false, error: 'A network error occurred.' };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default function SandboxRegisterForm({
|
|
72
|
+
onSuccess,
|
|
73
|
+
}: SandboxRegisterFormProps) {
|
|
74
|
+
const [firstname, setFirstname] = useState('');
|
|
75
|
+
const [email, setEmail] = useState('');
|
|
76
|
+
const [codeword, setCodeword] = useState('');
|
|
77
|
+
const [consent, setConsent] = useState(true);
|
|
78
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
79
|
+
const [error, setError] = useState<string | null>(null);
|
|
80
|
+
|
|
81
|
+
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
if (!email || !codeword || !firstname) {
|
|
84
|
+
setError('Please fill out all required fields.');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
setIsLoading(true);
|
|
89
|
+
setError(null);
|
|
90
|
+
|
|
91
|
+
const persona = consent ? 'major' : 'none';
|
|
92
|
+
|
|
93
|
+
const result = await createProfile({
|
|
94
|
+
firstname,
|
|
95
|
+
email,
|
|
96
|
+
codeword,
|
|
97
|
+
persona,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (result.success) {
|
|
101
|
+
onSuccess();
|
|
102
|
+
} else {
|
|
103
|
+
setError(result.error || 'An unknown error occurred. Please try again.');
|
|
104
|
+
setIsLoading(false);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<form
|
|
110
|
+
onSubmit={handleSubmit}
|
|
111
|
+
className="rounded-lg border bg-white p-6 shadow-sm"
|
|
112
|
+
>
|
|
113
|
+
<div className="space-y-4">
|
|
114
|
+
<div>
|
|
115
|
+
<label htmlFor="firstname" className="block text-sm text-gray-700">
|
|
116
|
+
First Name
|
|
117
|
+
</label>
|
|
118
|
+
<input
|
|
119
|
+
type="text"
|
|
120
|
+
name="firstname"
|
|
121
|
+
id="firstname"
|
|
122
|
+
autoComplete="given-name"
|
|
123
|
+
value={firstname}
|
|
124
|
+
onChange={(e) => setFirstname(e.target.value)}
|
|
125
|
+
disabled={isLoading}
|
|
126
|
+
className="mt-1 block w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
127
|
+
required
|
|
128
|
+
/>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div>
|
|
132
|
+
<label htmlFor="email" className="block text-sm text-gray-700">
|
|
133
|
+
Email address
|
|
134
|
+
</label>
|
|
135
|
+
<input
|
|
136
|
+
type="email"
|
|
137
|
+
name="email"
|
|
138
|
+
id="email"
|
|
139
|
+
autoComplete="email"
|
|
140
|
+
value={email}
|
|
141
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
142
|
+
disabled={isLoading}
|
|
143
|
+
className="mt-1 block w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
144
|
+
required
|
|
145
|
+
/>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div>
|
|
149
|
+
<label htmlFor="codeword" className="block text-sm text-gray-700">
|
|
150
|
+
Create a Codeword (to unlock your account)
|
|
151
|
+
</label>
|
|
152
|
+
<input
|
|
153
|
+
type="password"
|
|
154
|
+
name="codeword"
|
|
155
|
+
id="codeword"
|
|
156
|
+
autoComplete="new-password"
|
|
157
|
+
value={codeword}
|
|
158
|
+
onChange={(e) => setCodeword(e.target.value)}
|
|
159
|
+
disabled={isLoading}
|
|
160
|
+
className="mt-1 block w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
161
|
+
required
|
|
162
|
+
/>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<div className="flex items-start">
|
|
166
|
+
<div className="flex h-5 items-center">
|
|
167
|
+
<input
|
|
168
|
+
id="consent"
|
|
169
|
+
name="consent"
|
|
170
|
+
type="checkbox"
|
|
171
|
+
checked={consent}
|
|
172
|
+
onChange={(e) => setConsent(e.target.checked)}
|
|
173
|
+
disabled={isLoading}
|
|
174
|
+
className="h-4 w-4 rounded border-gray-300 text-cyan-600 focus:ring-cyan-500"
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
<div className="ml-3 text-sm">
|
|
178
|
+
<label htmlFor="consent" className="text-gray-700">
|
|
179
|
+
Keep me in touch with major updates
|
|
180
|
+
</label>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
{error && (
|
|
185
|
+
<p className="text-sm text-red-600" role="alert">
|
|
186
|
+
{error}
|
|
187
|
+
</p>
|
|
188
|
+
)}
|
|
189
|
+
|
|
190
|
+
<div>
|
|
191
|
+
<button
|
|
192
|
+
type="submit"
|
|
193
|
+
disabled={isLoading}
|
|
194
|
+
className="flex w-full justify-center rounded-md border border-transparent bg-black px-4 py-2 text-sm font-bold text-white shadow-sm transition-colors hover:bg-orange-500 focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:bg-gray-400"
|
|
195
|
+
>
|
|
196
|
+
{isLoading ? 'Creating...' : 'Start Crafting'}
|
|
197
|
+
</button>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</form>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
@@ -50,6 +50,7 @@ export type CompositorProps = {
|
|
|
50
50
|
availableCodeHooks: string[];
|
|
51
51
|
urlParams: Record<string, string | boolean>;
|
|
52
52
|
fullCanonicalURL: string;
|
|
53
|
+
isSandboxMode?: boolean;
|
|
53
54
|
};
|
|
54
55
|
|
|
55
56
|
const VERBOSE = false;
|
|
@@ -464,6 +465,7 @@ export const Compositor = (props: CompositorProps) => {
|
|
|
464
465
|
key={`${props.id}-${updateCounter}`}
|
|
465
466
|
ctx={props.ctx}
|
|
466
467
|
config={props.config}
|
|
468
|
+
isSandboxMode={props.isSandboxMode}
|
|
467
469
|
onDragStart={handleDragStart}
|
|
468
470
|
/>
|
|
469
471
|
)}
|
|
@@ -104,6 +104,7 @@ const EmptyPageHandler = (props: NodeProps) => {
|
|
|
104
104
|
ctx={ctx}
|
|
105
105
|
isStoryFragment={true}
|
|
106
106
|
config={props.config!}
|
|
107
|
+
isSandboxMode={props.isSandboxMode}
|
|
107
108
|
/>
|
|
108
109
|
);
|
|
109
110
|
};
|
|
@@ -115,7 +116,11 @@ const getElement = (
|
|
|
115
116
|
if (node === undefined) return <></>;
|
|
116
117
|
const isPreview = getCtx(props).rootNodeId.get() === `tmp`;
|
|
117
118
|
const hasPanes = useStore(getCtx(props).hasPanes);
|
|
118
|
-
const sharedProps = {
|
|
119
|
+
const sharedProps = {
|
|
120
|
+
...props,
|
|
121
|
+
nodeId: node.id,
|
|
122
|
+
isSandboxMode: props.isSandboxMode,
|
|
123
|
+
};
|
|
119
124
|
const type = getType(node);
|
|
120
125
|
|
|
121
126
|
switch (type) {
|
|
@@ -14,10 +14,6 @@ import { selectionStore } from '@/stores/selection';
|
|
|
14
14
|
export const Pane_DesignLibrary = (props: NodeProps) => {
|
|
15
15
|
const ctx = getCtx(props);
|
|
16
16
|
|
|
17
|
-
if (!props.config || !props.config.TENANT_ID) {
|
|
18
|
-
return <></>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
17
|
const { isRestyleModalOpen } = useStore(selectionStore, {
|
|
22
18
|
keys: ['isRestyleModalOpen'],
|
|
23
19
|
});
|
|
@@ -56,6 +52,10 @@ export const Pane_DesignLibrary = (props: NodeProps) => {
|
|
|
56
52
|
setIsSaveModalOpen(true);
|
|
57
53
|
};
|
|
58
54
|
|
|
55
|
+
if (!props.config || !props.config.TENANT_ID) {
|
|
56
|
+
return <></>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
59
|
return (
|
|
60
60
|
<div id={getPaneId()} className="pane min-h-16">
|
|
61
61
|
<div id={ctx.getNodeSlug(props.nodeId)} className={wrapperClasses}>
|
|
@@ -68,13 +68,15 @@ export const Pane_DesignLibrary = (props: NodeProps) => {
|
|
|
68
68
|
}}
|
|
69
69
|
>
|
|
70
70
|
<div className="absolute left-2 top-2 z-10 flex flex-col gap-y-2">
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
{props.isSandboxMode && (
|
|
72
|
+
<button
|
|
73
|
+
title="Save Pane to Design Library"
|
|
74
|
+
onClick={handleSaveClick}
|
|
75
|
+
className="flex h-10 w-10 items-center justify-center rounded-full bg-cyan-600 p-1.5 shadow-lg hover:bg-cyan-700"
|
|
76
|
+
>
|
|
77
|
+
<ArchiveBoxArrowDownIcon className="h-7 w-7 text-white" />
|
|
78
|
+
</button>
|
|
79
|
+
)}
|
|
78
80
|
<button
|
|
79
81
|
title="Restyle Pane from Design Library"
|
|
80
82
|
onClick={handleRestyleClick}
|
|
@@ -51,20 +51,22 @@ export const PaneLayout = (props: NodeProps) => {
|
|
|
51
51
|
e.stopPropagation();
|
|
52
52
|
}}
|
|
53
53
|
>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
54
|
+
{!props.isSandboxMode && (
|
|
55
|
+
<button
|
|
56
|
+
title="Apply New Layout"
|
|
57
|
+
onClick={(e) => {
|
|
58
|
+
getCtx(props).setClickedNodeId(props.nodeId);
|
|
59
|
+
e.stopPropagation();
|
|
60
|
+
}}
|
|
61
|
+
onDoubleClick={(e) => {
|
|
62
|
+
getCtx(props).setClickedNodeId(props.nodeId, true);
|
|
63
|
+
e.stopPropagation();
|
|
64
|
+
}}
|
|
65
|
+
className="absolute right-2 top-2 z-10 rounded-full bg-cyan-700 p-1.5 hover:bg-black"
|
|
66
|
+
>
|
|
67
|
+
<PuzzlePieceIcon className="h-10 w-10 text-white" />
|
|
68
|
+
</button>
|
|
69
|
+
)}
|
|
68
70
|
{codeHookPayload ? (
|
|
69
71
|
<CodeHookContainer payload={codeHookPayload} />
|
|
70
72
|
) : (
|
|
@@ -21,9 +21,14 @@ import SaveModal from '@/components/edit/state/SaveModal';
|
|
|
21
21
|
interface StoryKeepHeaderProps {
|
|
22
22
|
slug: string;
|
|
23
23
|
isContext: boolean;
|
|
24
|
+
isSandboxMode?: boolean;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
const StoryKeepHeader = ({
|
|
27
|
+
const StoryKeepHeader = ({
|
|
28
|
+
slug,
|
|
29
|
+
isContext = false,
|
|
30
|
+
isSandboxMode = false,
|
|
31
|
+
}: StoryKeepHeaderProps) => {
|
|
27
32
|
const viewport = useStore(viewportModeStore);
|
|
28
33
|
const pendingHomePageSlug = useStore(pendingHomePageSlugStore);
|
|
29
34
|
const ctx = getCtx();
|
|
@@ -162,7 +167,7 @@ const StoryKeepHeader = ({ slug, isContext = false }: StoryKeepHeaderProps) => {
|
|
|
162
167
|
</div>
|
|
163
168
|
)}
|
|
164
169
|
|
|
165
|
-
{shouldShowSave && (
|
|
170
|
+
{shouldShowSave && !isSandboxMode && (
|
|
166
171
|
<div className="flex flex-wrap items-center justify-center gap-2 text-sm">
|
|
167
172
|
<button
|
|
168
173
|
onClick={handleSave}
|
|
@@ -179,6 +184,7 @@ const StoryKeepHeader = ({ slug, isContext = false }: StoryKeepHeaderProps) => {
|
|
|
179
184
|
isContext={isContext}
|
|
180
185
|
show={showSaveModal}
|
|
181
186
|
onClose={handleCloseSaveModal}
|
|
187
|
+
isSandboxMode={isSandboxMode}
|
|
182
188
|
/>
|
|
183
189
|
</>
|
|
184
190
|
);
|
|
@@ -52,10 +52,6 @@ const PanelSwitch = ({
|
|
|
52
52
|
}: SettingsPanelProps) => {
|
|
53
53
|
const signal = useStore(settingsPanelStore);
|
|
54
54
|
|
|
55
|
-
if (!signal) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
55
|
useEffect(() => {
|
|
60
56
|
if (signal?.action && onTitleChange) {
|
|
61
57
|
const title = getSettingsPanelTitle(signal.action);
|
|
@@ -63,6 +59,10 @@ const PanelSwitch = ({
|
|
|
63
59
|
}
|
|
64
60
|
}, [signal?.action, onTitleChange]);
|
|
65
61
|
|
|
62
|
+
if (!signal) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
66
|
const ctx = getCtx();
|
|
67
67
|
const allNodes = ctx.allNodes.get();
|
|
68
68
|
const clickedNode = allNodes.get(signal.nodeId) as FlatNode | undefined;
|
|
@@ -16,6 +16,7 @@ interface AddPanePanelProps {
|
|
|
16
16
|
isStoryFragment?: boolean;
|
|
17
17
|
isContextPane?: boolean;
|
|
18
18
|
config?: BrandConfig;
|
|
19
|
+
isSandboxMode?: boolean;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
const AddPanePanel = ({
|
|
@@ -25,6 +26,7 @@ const AddPanePanel = ({
|
|
|
25
26
|
isStoryFragment = false,
|
|
26
27
|
isContextPane = false,
|
|
27
28
|
config,
|
|
29
|
+
isSandboxMode = false,
|
|
28
30
|
}: AddPanePanelProps) => {
|
|
29
31
|
const [reset, setReset] = useState(false);
|
|
30
32
|
const lookup = first ? `${nodeId}-0` : nodeId;
|
|
@@ -66,6 +68,7 @@ const AddPanePanel = ({
|
|
|
66
68
|
isStoryFragment={isStoryFragment}
|
|
67
69
|
isContextPane={isContextPane}
|
|
68
70
|
config={config!}
|
|
71
|
+
isSandboxMode={isSandboxMode}
|
|
69
72
|
/>
|
|
70
73
|
) : mode === PaneAddMode.BREAK && !isContextPane ? (
|
|
71
74
|
<AddPaneBreakPanel
|