@onexapis/cli 1.1.34 → 1.1.37
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/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +5 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/preview/preview-app.tsx +13 -5
- package/package.json +1 -1
- package/templates/default/AUTH_AND_PROFILE.md +167 -0
- package/templates/default/CLAUDE.md +78 -24
- package/templates/default/LAYOUT.md +195 -0
- package/templates/default/bundle-entry.ts +5 -0
- package/templates/default/hooks/index.ts +26 -0
- package/templates/default/hooks/use-forgot-password-form.ts +90 -0
- package/templates/default/hooks/use-login-form.ts +102 -0
- package/templates/default/hooks/use-profile-form.ts +255 -0
- package/templates/default/hooks/use-register-form.ts +154 -0
- package/templates/default/hooks/use-verify-code-form.ts +224 -0
- package/templates/default/index.ts +21 -1
- package/templates/default/pages/forgot-password.ts +41 -0
- package/templates/default/pages/login.ts +41 -0
- package/templates/default/pages/profile.ts +39 -0
- package/templates/default/pages/register.ts +41 -0
- package/templates/default/pages/verify-code.ts +41 -0
- package/templates/default/sections/auth-forgot-password/auth-forgot-password-default.tsx +192 -0
- package/templates/default/sections/auth-forgot-password/auth-forgot-password.schema.ts +150 -0
- package/templates/default/sections/auth-forgot-password/index.ts +14 -0
- package/templates/default/sections/auth-login/auth-login-default.tsx +238 -0
- package/templates/default/sections/auth-login/auth-login.schema.ts +171 -0
- package/templates/default/sections/auth-login/index.ts +14 -0
- package/templates/default/sections/auth-register/auth-register-default.tsx +327 -0
- package/templates/default/sections/auth-register/auth-register.schema.ts +188 -0
- package/templates/default/sections/auth-register/index.ts +14 -0
- package/templates/default/sections/auth-verify-code/auth-verify-code-default.tsx +209 -0
- package/templates/default/sections/auth-verify-code/auth-verify-code.schema.ts +150 -0
- package/templates/default/sections/auth-verify-code/index.ts +14 -0
- package/templates/default/sections/footer/footer-default.tsx +214 -0
- package/templates/default/sections/footer/footer.schema.ts +170 -0
- package/templates/default/sections/footer/index.ts +14 -0
- package/templates/default/sections/header/header-default.tsx +322 -0
- package/templates/default/sections/header/header.schema.ts +168 -0
- package/templates/default/sections/header/index.ts +14 -0
- package/templates/default/sections/profile/index.ts +14 -0
- package/templates/default/sections/profile/profile-default.tsx +522 -0
- package/templates/default/sections/profile/profile.schema.ts +228 -0
- package/templates/default/sections-registry.ts +28 -0
- package/templates/default/theme.layout.ts +53 -2
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Verify Code - Default Template
|
|
3
|
+
* OTP verification form with customizable settings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use client";
|
|
7
|
+
|
|
8
|
+
import type { SectionComponentProps } from "@onexapis/core/types";
|
|
9
|
+
import coreUtils from "@onexapis/core/utils";
|
|
10
|
+
import { useVerifyCodeForm } from "../../hooks/use-verify-code-form";
|
|
11
|
+
|
|
12
|
+
const { getSectionValues } = coreUtils;
|
|
13
|
+
|
|
14
|
+
export function AuthVerifyCodeDefault({
|
|
15
|
+
section,
|
|
16
|
+
schema,
|
|
17
|
+
isEditing,
|
|
18
|
+
}: SectionComponentProps) {
|
|
19
|
+
const { settings } = getSectionValues(section, schema);
|
|
20
|
+
const {
|
|
21
|
+
heading,
|
|
22
|
+
subheading,
|
|
23
|
+
submitButtonText,
|
|
24
|
+
loadingText,
|
|
25
|
+
resendText,
|
|
26
|
+
resendCountdownText,
|
|
27
|
+
didNotReceiveText,
|
|
28
|
+
loginPromptText,
|
|
29
|
+
loginLinkText,
|
|
30
|
+
loginUrl,
|
|
31
|
+
backgroundColor,
|
|
32
|
+
cardBackground,
|
|
33
|
+
primaryColor,
|
|
34
|
+
textColor,
|
|
35
|
+
cardBorderRadius,
|
|
36
|
+
} = settings;
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
code,
|
|
40
|
+
error,
|
|
41
|
+
countdown,
|
|
42
|
+
email,
|
|
43
|
+
isVerifying,
|
|
44
|
+
isResending,
|
|
45
|
+
isLoading,
|
|
46
|
+
inputRefs,
|
|
47
|
+
handleCodeChange,
|
|
48
|
+
handleKeyDown,
|
|
49
|
+
handlePaste,
|
|
50
|
+
handleSubmit,
|
|
51
|
+
handleResend,
|
|
52
|
+
} = useVerifyCodeForm();
|
|
53
|
+
|
|
54
|
+
const bgColor = String(backgroundColor || "#F9FAFB");
|
|
55
|
+
const cardBg = String(cardBackground || "#FFFFFF");
|
|
56
|
+
const btnColor = String(primaryColor || "#2563EB");
|
|
57
|
+
const headingColor = String(textColor || "#111827");
|
|
58
|
+
const radiusClass = `rounded-${String(cardBorderRadius || "2xl")}`;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<section
|
|
62
|
+
className="min-h-screen w-full flex items-center justify-center p-4"
|
|
63
|
+
style={{ backgroundColor: bgColor }}
|
|
64
|
+
data-section-id={section.id}
|
|
65
|
+
data-section-type={section.type}
|
|
66
|
+
data-section-template="default"
|
|
67
|
+
>
|
|
68
|
+
<div
|
|
69
|
+
className={`w-full max-w-md shadow-lg p-8 ${radiusClass}`}
|
|
70
|
+
style={{ backgroundColor: cardBg }}
|
|
71
|
+
>
|
|
72
|
+
{/* Header */}
|
|
73
|
+
<div className="text-center mb-8">
|
|
74
|
+
<h1 className="text-2xl font-bold" style={{ color: headingColor }}>
|
|
75
|
+
{String(heading)}
|
|
76
|
+
</h1>
|
|
77
|
+
<p className="mt-2 text-sm text-gray-500">
|
|
78
|
+
{String(subheading)}
|
|
79
|
+
{email && (
|
|
80
|
+
<span
|
|
81
|
+
className="block mt-1 font-medium"
|
|
82
|
+
style={{ color: btnColor }}
|
|
83
|
+
>
|
|
84
|
+
{email}
|
|
85
|
+
</span>
|
|
86
|
+
)}
|
|
87
|
+
</p>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
{/* Error Message */}
|
|
91
|
+
{error && (
|
|
92
|
+
<div className="mb-4 rounded-lg bg-red-50 border border-red-200 p-3 text-sm text-red-600 text-center">
|
|
93
|
+
{error}
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
{/* OTP Form */}
|
|
98
|
+
<form
|
|
99
|
+
onSubmit={handleSubmit}
|
|
100
|
+
className="flex flex-col items-center gap-6"
|
|
101
|
+
>
|
|
102
|
+
{/* OTP Inputs */}
|
|
103
|
+
<div className="flex items-center justify-center gap-2 md:gap-3">
|
|
104
|
+
{code.map((digit, index) => (
|
|
105
|
+
<input
|
|
106
|
+
key={index}
|
|
107
|
+
ref={(el) => {
|
|
108
|
+
inputRefs.current[index] = el;
|
|
109
|
+
}}
|
|
110
|
+
type="text"
|
|
111
|
+
inputMode="numeric"
|
|
112
|
+
maxLength={1}
|
|
113
|
+
value={digit}
|
|
114
|
+
onChange={(e) => handleCodeChange(index, e.target.value)}
|
|
115
|
+
onKeyDown={(e) => handleKeyDown(index, e)}
|
|
116
|
+
onPaste={handlePaste}
|
|
117
|
+
disabled={isVerifying || isEditing}
|
|
118
|
+
className={`h-12 w-10 md:w-12 rounded-lg border text-center text-xl font-medium text-gray-900 outline-none transition-colors focus:border-blue-500 focus:ring-1 focus:ring-blue-500 disabled:opacity-50 ${
|
|
119
|
+
error ? "border-red-400" : "border-gray-300"
|
|
120
|
+
}`}
|
|
121
|
+
aria-label={`Digit ${index + 1}`}
|
|
122
|
+
/>
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* Resend Code */}
|
|
127
|
+
<button
|
|
128
|
+
type="button"
|
|
129
|
+
disabled={isLoading || countdown > 0 || isEditing}
|
|
130
|
+
onClick={handleResend}
|
|
131
|
+
className="text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed hover:opacity-80"
|
|
132
|
+
style={{ color: btnColor }}
|
|
133
|
+
>
|
|
134
|
+
{countdown > 0 ? (
|
|
135
|
+
<span className="text-gray-400">
|
|
136
|
+
{String(resendCountdownText)} {countdown}s
|
|
137
|
+
</span>
|
|
138
|
+
) : isResending ? (
|
|
139
|
+
"Sending..."
|
|
140
|
+
) : (
|
|
141
|
+
<>
|
|
142
|
+
<span className="text-gray-500">
|
|
143
|
+
{String(didNotReceiveText)}{" "}
|
|
144
|
+
</span>
|
|
145
|
+
{String(resendText)}
|
|
146
|
+
</>
|
|
147
|
+
)}
|
|
148
|
+
</button>
|
|
149
|
+
|
|
150
|
+
{/* Submit Button */}
|
|
151
|
+
<button
|
|
152
|
+
type="submit"
|
|
153
|
+
disabled={isVerifying || code.join("").length !== 6 || isEditing}
|
|
154
|
+
className="h-11 w-full rounded-lg text-sm font-medium text-white transition-colors hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
|
155
|
+
style={{ backgroundColor: btnColor }}
|
|
156
|
+
>
|
|
157
|
+
{isVerifying && (
|
|
158
|
+
<svg
|
|
159
|
+
className="h-4 w-4 animate-spin"
|
|
160
|
+
fill="none"
|
|
161
|
+
viewBox="0 0 24 24"
|
|
162
|
+
>
|
|
163
|
+
<circle
|
|
164
|
+
className="opacity-25"
|
|
165
|
+
cx="12"
|
|
166
|
+
cy="12"
|
|
167
|
+
r="10"
|
|
168
|
+
stroke="currentColor"
|
|
169
|
+
strokeWidth="4"
|
|
170
|
+
/>
|
|
171
|
+
<path
|
|
172
|
+
className="opacity-75"
|
|
173
|
+
fill="currentColor"
|
|
174
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
175
|
+
/>
|
|
176
|
+
</svg>
|
|
177
|
+
)}
|
|
178
|
+
{isVerifying ? String(loadingText) : String(submitButtonText)}
|
|
179
|
+
</button>
|
|
180
|
+
</form>
|
|
181
|
+
|
|
182
|
+
{/* Login Link */}
|
|
183
|
+
<div className="mt-6 text-center">
|
|
184
|
+
<p className="text-sm text-gray-500">
|
|
185
|
+
{String(loginPromptText)}{" "}
|
|
186
|
+
{isEditing ? (
|
|
187
|
+
<span
|
|
188
|
+
className="font-medium hover:opacity-80"
|
|
189
|
+
style={{ color: btnColor }}
|
|
190
|
+
>
|
|
191
|
+
{String(loginLinkText)}
|
|
192
|
+
</span>
|
|
193
|
+
) : (
|
|
194
|
+
<a
|
|
195
|
+
href={String(loginUrl)}
|
|
196
|
+
className="font-medium hover:opacity-80 transition-colors"
|
|
197
|
+
style={{ color: btnColor }}
|
|
198
|
+
>
|
|
199
|
+
{String(loginLinkText)}
|
|
200
|
+
</a>
|
|
201
|
+
)}
|
|
202
|
+
</p>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</section>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export default AuthVerifyCodeDefault;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Verify Code Section Schema
|
|
3
|
+
* OTP verification form with editable labels
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { SectionSchema, FieldDefinition } from "@onexapis/core/types";
|
|
7
|
+
|
|
8
|
+
const commonSettings: FieldDefinition[] = [
|
|
9
|
+
{
|
|
10
|
+
id: "heading",
|
|
11
|
+
type: "text",
|
|
12
|
+
label: "Heading",
|
|
13
|
+
default: "Verify Your Account",
|
|
14
|
+
required: true,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: "subheading",
|
|
18
|
+
type: "text",
|
|
19
|
+
label: "Subheading",
|
|
20
|
+
default: "Enter the 6-digit code sent to your email",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "submitButtonText",
|
|
24
|
+
type: "text",
|
|
25
|
+
label: "Submit Button Text",
|
|
26
|
+
default: "Verify",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "loadingText",
|
|
30
|
+
type: "text",
|
|
31
|
+
label: "Loading Text",
|
|
32
|
+
default: "Verifying...",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "resendText",
|
|
36
|
+
type: "text",
|
|
37
|
+
label: "Resend Text",
|
|
38
|
+
default: "Resend code",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "resendCountdownText",
|
|
42
|
+
type: "text",
|
|
43
|
+
label: "Countdown Text",
|
|
44
|
+
default: "Resend in",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "didNotReceiveText",
|
|
48
|
+
type: "text",
|
|
49
|
+
label: "Did Not Receive Text",
|
|
50
|
+
default: "Didn't receive a code?",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "loginPromptText",
|
|
54
|
+
type: "text",
|
|
55
|
+
label: "Login Prompt Text",
|
|
56
|
+
default: "Already have an account?",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "loginLinkText",
|
|
60
|
+
type: "text",
|
|
61
|
+
label: "Login Link Text",
|
|
62
|
+
default: "Sign in",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "loginUrl",
|
|
66
|
+
type: "url",
|
|
67
|
+
label: "Login URL",
|
|
68
|
+
default: "/login",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "countdownSeconds",
|
|
72
|
+
type: "number",
|
|
73
|
+
label: "Countdown Duration (seconds)",
|
|
74
|
+
default: 60,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "backgroundColor",
|
|
78
|
+
type: "color",
|
|
79
|
+
label: "Background Color",
|
|
80
|
+
default: "#F9FAFB",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "cardBackground",
|
|
84
|
+
type: "color",
|
|
85
|
+
label: "Card Background",
|
|
86
|
+
default: "#FFFFFF",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "primaryColor",
|
|
90
|
+
type: "color",
|
|
91
|
+
label: "Primary Color (buttons)",
|
|
92
|
+
default: "#2563EB",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "textColor",
|
|
96
|
+
type: "color",
|
|
97
|
+
label: "Heading Text Color",
|
|
98
|
+
default: "#111827",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "cardBorderRadius",
|
|
102
|
+
type: "select",
|
|
103
|
+
label: "Card Border Radius",
|
|
104
|
+
default: "2xl",
|
|
105
|
+
options: [
|
|
106
|
+
{ label: "Small", value: "lg" },
|
|
107
|
+
{ label: "Medium", value: "xl" },
|
|
108
|
+
{ label: "Large", value: "2xl" },
|
|
109
|
+
{ label: "Extra Large", value: "3xl" },
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
export const authVerifyCodeSchema: SectionSchema = {
|
|
115
|
+
type: "my-simple-auth-verify-code",
|
|
116
|
+
name: "Auth Verify Code",
|
|
117
|
+
description: "OTP verification form with 6-digit code input",
|
|
118
|
+
category: "auth",
|
|
119
|
+
icon: "shield-check",
|
|
120
|
+
settings: commonSettings,
|
|
121
|
+
templates: [
|
|
122
|
+
{
|
|
123
|
+
id: "default",
|
|
124
|
+
name: "Default Verify Code",
|
|
125
|
+
description: "Clean OTP verification form",
|
|
126
|
+
isDefault: true,
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
defaults: {
|
|
130
|
+
settings: {
|
|
131
|
+
heading: "Verify Your Account",
|
|
132
|
+
subheading: "Enter the 6-digit code sent to your email",
|
|
133
|
+
submitButtonText: "Verify",
|
|
134
|
+
loadingText: "Verifying...",
|
|
135
|
+
resendText: "Resend code",
|
|
136
|
+
resendCountdownText: "Resend in",
|
|
137
|
+
didNotReceiveText: "Didn't receive a code?",
|
|
138
|
+
loginPromptText: "Already have an account?",
|
|
139
|
+
loginLinkText: "Sign in",
|
|
140
|
+
loginUrl: "/login",
|
|
141
|
+
countdownSeconds: 60,
|
|
142
|
+
backgroundColor: "#F9FAFB",
|
|
143
|
+
cardBackground: "#FFFFFF",
|
|
144
|
+
primaryColor: "#2563EB",
|
|
145
|
+
textColor: "#111827",
|
|
146
|
+
cardBorderRadius: "2xl",
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
tags: ["auth", "verify-code", "otp"],
|
|
150
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Verify Code Section
|
|
3
|
+
* Exports schema and component templates
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { SectionComponentProps } from "@onexapis/core/types";
|
|
7
|
+
import { AuthVerifyCodeDefault } from "./auth-verify-code-default";
|
|
8
|
+
|
|
9
|
+
export const authVerifyCodeComponents: Record<
|
|
10
|
+
string,
|
|
11
|
+
React.ComponentType<SectionComponentProps>
|
|
12
|
+
> = { default: AuthVerifyCodeDefault };
|
|
13
|
+
|
|
14
|
+
export { authVerifyCodeSchema } from "./auth-verify-code.schema";
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Footer - Default Template
|
|
3
|
+
* Clean responsive footer with company info, links, and copyright
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use client";
|
|
7
|
+
|
|
8
|
+
import type { SectionComponentProps } from "@onexapis/core/types";
|
|
9
|
+
import coreRenderers from "@onexapis/core/renderers";
|
|
10
|
+
import coreUtils from "@onexapis/core/utils";
|
|
11
|
+
|
|
12
|
+
const { ComponentRenderer } = coreRenderers;
|
|
13
|
+
const { toComponentInstance, getSectionValues, filterEnabledComponents } =
|
|
14
|
+
coreUtils;
|
|
15
|
+
|
|
16
|
+
export function FooterDefault({
|
|
17
|
+
section,
|
|
18
|
+
schema,
|
|
19
|
+
isEditing,
|
|
20
|
+
}: SectionComponentProps) {
|
|
21
|
+
const { settings } = getSectionValues(section, schema);
|
|
22
|
+
const {
|
|
23
|
+
companyName,
|
|
24
|
+
description,
|
|
25
|
+
copyrightText,
|
|
26
|
+
showAboutColumn,
|
|
27
|
+
aboutColumnTitle,
|
|
28
|
+
showLinksColumn,
|
|
29
|
+
linksColumnTitle,
|
|
30
|
+
backgroundColor,
|
|
31
|
+
textColor,
|
|
32
|
+
primaryColor,
|
|
33
|
+
} = settings;
|
|
34
|
+
|
|
35
|
+
const bgColor = String(backgroundColor || "#111827");
|
|
36
|
+
const txtColor = String(textColor || "#9CA3AF");
|
|
37
|
+
const headingColor = String(primaryColor || "#FFFFFF");
|
|
38
|
+
|
|
39
|
+
// Find components
|
|
40
|
+
const components = filterEnabledComponents(section.components || []);
|
|
41
|
+
const companyNameComp = components.find(
|
|
42
|
+
(c) => c.slot === "company-name" || c.id === "footer-company-name"
|
|
43
|
+
);
|
|
44
|
+
const descComp = components.find(
|
|
45
|
+
(c) => c.slot === "description" || c.id === "footer-description"
|
|
46
|
+
);
|
|
47
|
+
const copyrightComp = components.find(
|
|
48
|
+
(c) => c.slot === "copyright" || c.id === "footer-copyright"
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Parse link items
|
|
52
|
+
let aboutLinks: Array<{ label: string; href: string }> = [];
|
|
53
|
+
let quickLinks: Array<{ label: string; href: string }> = [];
|
|
54
|
+
try {
|
|
55
|
+
const rawAbout = settings.aboutLinks;
|
|
56
|
+
if (typeof rawAbout === "string") aboutLinks = JSON.parse(rawAbout);
|
|
57
|
+
else if (Array.isArray(rawAbout))
|
|
58
|
+
aboutLinks = rawAbout as Array<{ label: string; href: string }>;
|
|
59
|
+
} catch {
|
|
60
|
+
aboutLinks = [
|
|
61
|
+
{ label: "About Us", href: "/about" },
|
|
62
|
+
{ label: "Contact", href: "#contact" },
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const rawLinks = settings.quickLinks;
|
|
67
|
+
if (typeof rawLinks === "string") quickLinks = JSON.parse(rawLinks);
|
|
68
|
+
else if (Array.isArray(rawLinks))
|
|
69
|
+
quickLinks = rawLinks as Array<{ label: string; href: string }>;
|
|
70
|
+
} catch {
|
|
71
|
+
quickLinks = [
|
|
72
|
+
{ label: "Home", href: "/" },
|
|
73
|
+
{ label: "Showcase", href: "/showcase" },
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const showAbout = Boolean(showAboutColumn) !== false;
|
|
78
|
+
const showLinks = Boolean(showLinksColumn) !== false;
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<footer
|
|
82
|
+
className="w-full"
|
|
83
|
+
style={{ backgroundColor: bgColor }}
|
|
84
|
+
data-section-id={section.id}
|
|
85
|
+
data-section-type={section.type}
|
|
86
|
+
data-section-template="default"
|
|
87
|
+
>
|
|
88
|
+
<div className="container mx-auto px-4 py-12">
|
|
89
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
90
|
+
{/* Column 1: Company Info */}
|
|
91
|
+
<div className="space-y-4">
|
|
92
|
+
{companyNameComp ? (
|
|
93
|
+
<ComponentRenderer
|
|
94
|
+
instance={toComponentInstance(companyNameComp)}
|
|
95
|
+
sectionId={section.id}
|
|
96
|
+
isEditing={isEditing}
|
|
97
|
+
/>
|
|
98
|
+
) : (
|
|
99
|
+
<h3 className="text-xl font-bold" style={{ color: headingColor }}>
|
|
100
|
+
{String(companyName)}
|
|
101
|
+
</h3>
|
|
102
|
+
)}
|
|
103
|
+
{descComp ? (
|
|
104
|
+
<ComponentRenderer
|
|
105
|
+
instance={toComponentInstance(descComp)}
|
|
106
|
+
sectionId={section.id}
|
|
107
|
+
isEditing={isEditing}
|
|
108
|
+
/>
|
|
109
|
+
) : description ? (
|
|
110
|
+
<p
|
|
111
|
+
className="text-sm leading-relaxed"
|
|
112
|
+
style={{ color: txtColor }}
|
|
113
|
+
>
|
|
114
|
+
{String(description)}
|
|
115
|
+
</p>
|
|
116
|
+
) : null}
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
{/* Column 2: About Links */}
|
|
120
|
+
{showAbout && (
|
|
121
|
+
<div className="space-y-4">
|
|
122
|
+
<h4
|
|
123
|
+
className="text-base font-semibold"
|
|
124
|
+
style={{ color: headingColor }}
|
|
125
|
+
>
|
|
126
|
+
{String(aboutColumnTitle)}
|
|
127
|
+
</h4>
|
|
128
|
+
<nav className="flex flex-col gap-2">
|
|
129
|
+
{aboutLinks.map((link, i) =>
|
|
130
|
+
isEditing ? (
|
|
131
|
+
<span
|
|
132
|
+
key={i}
|
|
133
|
+
className="text-sm hover:opacity-80"
|
|
134
|
+
style={{ color: txtColor }}
|
|
135
|
+
>
|
|
136
|
+
{link.label}
|
|
137
|
+
</span>
|
|
138
|
+
) : (
|
|
139
|
+
<a
|
|
140
|
+
key={i}
|
|
141
|
+
href={link.href}
|
|
142
|
+
className="text-sm hover:opacity-80 transition-opacity"
|
|
143
|
+
style={{ color: txtColor }}
|
|
144
|
+
>
|
|
145
|
+
{link.label}
|
|
146
|
+
</a>
|
|
147
|
+
)
|
|
148
|
+
)}
|
|
149
|
+
</nav>
|
|
150
|
+
</div>
|
|
151
|
+
)}
|
|
152
|
+
|
|
153
|
+
{/* Column 3: Quick Links */}
|
|
154
|
+
{showLinks && (
|
|
155
|
+
<div className="space-y-4">
|
|
156
|
+
<h4
|
|
157
|
+
className="text-base font-semibold"
|
|
158
|
+
style={{ color: headingColor }}
|
|
159
|
+
>
|
|
160
|
+
{String(linksColumnTitle)}
|
|
161
|
+
</h4>
|
|
162
|
+
<nav className="flex flex-col gap-2">
|
|
163
|
+
{quickLinks.map((link, i) =>
|
|
164
|
+
isEditing ? (
|
|
165
|
+
<span
|
|
166
|
+
key={i}
|
|
167
|
+
className="text-sm hover:opacity-80"
|
|
168
|
+
style={{ color: txtColor }}
|
|
169
|
+
>
|
|
170
|
+
{link.label}
|
|
171
|
+
</span>
|
|
172
|
+
) : (
|
|
173
|
+
<a
|
|
174
|
+
key={i}
|
|
175
|
+
href={link.href}
|
|
176
|
+
className="text-sm hover:opacity-80 transition-opacity"
|
|
177
|
+
style={{ color: txtColor }}
|
|
178
|
+
>
|
|
179
|
+
{link.label}
|
|
180
|
+
</a>
|
|
181
|
+
)
|
|
182
|
+
)}
|
|
183
|
+
</nav>
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{/* Copyright */}
|
|
189
|
+
<div
|
|
190
|
+
className="mt-10 pt-8 text-center text-sm"
|
|
191
|
+
style={{
|
|
192
|
+
borderTopColor: `${txtColor}33`,
|
|
193
|
+
borderTopWidth: "1px",
|
|
194
|
+
color: txtColor,
|
|
195
|
+
}}
|
|
196
|
+
>
|
|
197
|
+
{copyrightComp ? (
|
|
198
|
+
<ComponentRenderer
|
|
199
|
+
instance={toComponentInstance(copyrightComp)}
|
|
200
|
+
sectionId={section.id}
|
|
201
|
+
isEditing={isEditing}
|
|
202
|
+
/>
|
|
203
|
+
) : (
|
|
204
|
+
<p>
|
|
205
|
+
© {new Date().getFullYear()} {String(copyrightText)}
|
|
206
|
+
</p>
|
|
207
|
+
)}
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
</footer>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export default FooterDefault;
|