astro-tractstack 2.0.14 → 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 +41 -9
- 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 +27 -9
- 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 +463 -561
- package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +140 -0
- package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +105 -0
- package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +395 -0
- package/templates/src/components/edit/panels/StyleImagePanel.tsx +10 -8
- package/templates/src/components/edit/state/SaveModal.tsx +41 -0
- package/templates/src/constants/prompts.json +3 -1
- 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/compositor/aiPaneParser.ts +32 -84
- package/templates/src/utils/compositor/designLibraryHelper.ts +87 -2
- package/templates/src/utils/profileStorage.ts +13 -0
- package/utils/inject-files.ts +41 -10
- package/templates/src/components/edit/pane/AiPaneGenerator.tsx +0 -575
- package/templates/src/components/edit/pane/AiPanePreview.tsx +0 -107
- package/templates/src/components/edit/pane/PageGen.tsx +0 -485
- package/templates/src/components/edit/pane/PageGenSelector.tsx +0 -245
- package/templates/src/components/edit/pane/PageGenSpecial.tsx +0 -339
- 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"
|
|
@@ -441,12 +453,22 @@ async function w(t, e, c) {
|
|
|
441
453
|
dest: "src/components/edit/pane/RestylePaneModal.tsx"
|
|
442
454
|
},
|
|
443
455
|
{
|
|
444
|
-
src: t(
|
|
445
|
-
|
|
456
|
+
src: t(
|
|
457
|
+
"../templates/src/components/edit/pane/steps/CopyInputStep.tsx"
|
|
458
|
+
),
|
|
459
|
+
dest: "src/components/edit/pane/steps/CopyInputStep.tsx"
|
|
446
460
|
},
|
|
447
461
|
{
|
|
448
|
-
src: t(
|
|
449
|
-
|
|
462
|
+
src: t(
|
|
463
|
+
"../templates/src/components/edit/pane/steps/DesignLibraryStep.tsx"
|
|
464
|
+
),
|
|
465
|
+
dest: "src/components/edit/pane/steps/DesignLibraryStep.tsx"
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
src: t(
|
|
469
|
+
"../templates/src/components/edit/pane/steps/AiDesignStep.tsx"
|
|
470
|
+
),
|
|
471
|
+
dest: "src/components/edit/pane/steps/AiDesignStep.tsx"
|
|
450
472
|
},
|
|
451
473
|
{
|
|
452
474
|
src: t(
|
|
@@ -581,11 +603,6 @@ async function w(t, e, c) {
|
|
|
581
603
|
src: t("../templates/src/stores/selection.ts"),
|
|
582
604
|
dest: "src/stores/selection.ts"
|
|
583
605
|
},
|
|
584
|
-
// AAI utils
|
|
585
|
-
{
|
|
586
|
-
src: t("../templates/src/utils/aai/getTitleSlug.ts"),
|
|
587
|
-
dest: "src/utils/aai/getTitleSlug.ts"
|
|
588
|
-
},
|
|
589
606
|
// Compositor utils - etl
|
|
590
607
|
{
|
|
591
608
|
src: t("../templates/src/utils/etl/index.ts"),
|
|
@@ -816,6 +833,10 @@ async function w(t, e, c) {
|
|
|
816
833
|
),
|
|
817
834
|
dest: "src/pages/context/[...contextSlug]/edit.astro"
|
|
818
835
|
},
|
|
836
|
+
{
|
|
837
|
+
src: t("../templates/src/pages/sandbox.astro"),
|
|
838
|
+
dest: "src/pages/sandbox.astro"
|
|
839
|
+
},
|
|
819
840
|
{
|
|
820
841
|
src: t("../templates/src/pages/storykeep.astro"),
|
|
821
842
|
dest: "src/pages/storykeep.astro"
|
|
@@ -860,6 +881,10 @@ async function w(t, e, c) {
|
|
|
860
881
|
src: t("../templates/src/pages/api/tailwind.ts"),
|
|
861
882
|
dest: "src/pages/api/tailwind.ts"
|
|
862
883
|
},
|
|
884
|
+
{
|
|
885
|
+
src: t("../templates/src/pages/api/sandbox.ts"),
|
|
886
|
+
dest: "src/pages/api/sandbox.ts"
|
|
887
|
+
},
|
|
863
888
|
// Authentication Pages
|
|
864
889
|
{
|
|
865
890
|
src: t("../templates/src/pages/storykeep/login.astro"),
|
|
@@ -2092,6 +2117,13 @@ async function w(t, e, c) {
|
|
|
2092
2117
|
},
|
|
2093
2118
|
// Example Components (Conditional)
|
|
2094
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
|
+
},
|
|
2095
2127
|
{
|
|
2096
2128
|
src: t("../templates/custom/with-examples/CustomHero.astro"),
|
|
2097
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
|
)}
|
|
@@ -32,7 +32,6 @@ import { NodeBasicTagEraser } from './nodes/tagElements/NodeBasicTag_eraser';
|
|
|
32
32
|
import { NodeBasicTagSettings } from './nodes/tagElements/NodeBasicTag_settings';
|
|
33
33
|
import { Pane_DesignLibrary } from './nodes/Pane_DesignLibrary';
|
|
34
34
|
import AddPanePanel from '@/components/edit/pane/AddPanePanel';
|
|
35
|
-
import PageCreationSelector from '@/components/edit/pane/PageGenSelector';
|
|
36
35
|
import ConfigPanePanel from '@/components/edit/pane/ConfigPanePanel';
|
|
37
36
|
import StoryFragmentConfigPanel from '@/components/edit/storyfragment/StoryFragmentConfigPanel';
|
|
38
37
|
import StoryFragmentTitlePanel from '@/components/edit/storyfragment/StoryFragmentPanel_title';
|
|
@@ -46,6 +45,7 @@ import type {
|
|
|
46
45
|
BaseNode,
|
|
47
46
|
FlatNode,
|
|
48
47
|
} from '@/types/compositorTypes';
|
|
48
|
+
import { PaneAddMode } from '@/types/compositorTypes';
|
|
49
49
|
import { handleClickEventDefault } from '@/utils/compositor/handleClickEvent';
|
|
50
50
|
import { selectionStore } from '@/stores/selection';
|
|
51
51
|
import type { NodeProps, SelectionOrigin } from '@/types/nodeProps';
|
|
@@ -89,6 +89,26 @@ function parseCodeHook(node: BaseNode | FlatNode) {
|
|
|
89
89
|
return null;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// Helper component to safely set the panel mode for an empty page
|
|
93
|
+
const EmptyPageHandler = (props: NodeProps) => {
|
|
94
|
+
const ctx = getCtx(props);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
ctx.setPaneAddMode(props.nodeId, PaneAddMode.NEW);
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
// Now that the mode is set, render the panel which will read it.
|
|
100
|
+
return (
|
|
101
|
+
<AddPanePanel
|
|
102
|
+
nodeId={props.nodeId}
|
|
103
|
+
first={true}
|
|
104
|
+
ctx={ctx}
|
|
105
|
+
isStoryFragment={true}
|
|
106
|
+
config={props.config!}
|
|
107
|
+
isSandboxMode={props.isSandboxMode}
|
|
108
|
+
/>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
92
112
|
const getElement = (
|
|
93
113
|
node: BaseNode | FlatNode,
|
|
94
114
|
props: NodeProps
|
|
@@ -96,8 +116,11 @@ const getElement = (
|
|
|
96
116
|
if (node === undefined) return <></>;
|
|
97
117
|
const isPreview = getCtx(props).rootNodeId.get() === `tmp`;
|
|
98
118
|
const hasPanes = useStore(getCtx(props).hasPanes);
|
|
99
|
-
const
|
|
100
|
-
|
|
119
|
+
const sharedProps = {
|
|
120
|
+
...props,
|
|
121
|
+
nodeId: node.id,
|
|
122
|
+
isSandboxMode: props.isSandboxMode,
|
|
123
|
+
};
|
|
101
124
|
const type = getType(node);
|
|
102
125
|
|
|
103
126
|
switch (type) {
|
|
@@ -152,12 +175,7 @@ const getElement = (
|
|
|
152
175
|
</div>
|
|
153
176
|
</div>
|
|
154
177
|
) : !hasPanes && sf.slug && sf.title && !isPreview ? (
|
|
155
|
-
<
|
|
156
|
-
nodeId={props.nodeId}
|
|
157
|
-
ctx={getCtx(props)}
|
|
158
|
-
isTemplate={isTemplate}
|
|
159
|
-
config={props.config!}
|
|
160
|
-
/>
|
|
178
|
+
<EmptyPageHandler {...sharedProps} />
|
|
161
179
|
) : (
|
|
162
180
|
<>
|
|
163
181
|
<PanelVisibilityWrapper
|
|
@@ -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
|
);
|