@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
|
@@ -455,6 +455,14 @@ function PreviewApp() {
|
|
|
455
455
|
>("disconnected");
|
|
456
456
|
const [error, setError] = useState<string | null>(null);
|
|
457
457
|
const wsRef = useRef<WebSocket | null>(null);
|
|
458
|
+
const [isEditing, setIsEditing] = useState(() => {
|
|
459
|
+
if (typeof window !== "undefined") {
|
|
460
|
+
return (
|
|
461
|
+
new URLSearchParams(window.location.search).get("editing") === "true"
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
return false;
|
|
465
|
+
});
|
|
458
466
|
|
|
459
467
|
// Load theme bundle
|
|
460
468
|
const loadTheme = useCallback(async (timestamp?: number) => {
|
|
@@ -701,7 +709,7 @@ function PreviewApp() {
|
|
|
701
709
|
section={section}
|
|
702
710
|
schema={schema}
|
|
703
711
|
template={template}
|
|
704
|
-
isEditing={
|
|
712
|
+
isEditing={isEditing}
|
|
705
713
|
data={sectionData}
|
|
706
714
|
/>
|
|
707
715
|
);
|
|
@@ -712,16 +720,16 @@ function PreviewApp() {
|
|
|
712
720
|
{/* Inject theme CSS variables (Gap 2) */}
|
|
713
721
|
{themeCSS && <style dangerouslySetInnerHTML={{ __html: themeCSS }} />}
|
|
714
722
|
|
|
715
|
-
{/* Header
|
|
716
|
-
{headerSections.length > 0 && (
|
|
723
|
+
{/* Header — hidden if page config sets hideHeader: true */}
|
|
724
|
+
{headerSections.length > 0 && !currentPage.config?.hideHeader && (
|
|
717
725
|
<header>{headerSections.map(renderSection)}</header>
|
|
718
726
|
)}
|
|
719
727
|
|
|
720
728
|
{/* Page sections */}
|
|
721
729
|
<main>{pageSections.map(renderSection)}</main>
|
|
722
730
|
|
|
723
|
-
{/* Footer
|
|
724
|
-
{footerSections.length > 0 && (
|
|
731
|
+
{/* Footer — hidden if page config sets hideFooter: true */}
|
|
732
|
+
{footerSections.length > 0 && !currentPage.config?.hideFooter && (
|
|
725
733
|
<footer>{footerSections.map(renderSection)}</footer>
|
|
726
734
|
)}
|
|
727
735
|
</>
|
package/package.json
CHANGED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Authentication & Profile — Default Theme
|
|
2
|
+
|
|
3
|
+
This document covers the built-in authentication and profile sections included in the default theme template.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
When you initialize a theme with `onexthm init`, it comes with 5 auth/profile sections and their corresponding hooks ready to use:
|
|
8
|
+
|
|
9
|
+
| Section | Path | Description |
|
|
10
|
+
| -------------------- | ------------------ | -------------------------------------------- |
|
|
11
|
+
| Auth Login | `/login` | Username/email + password sign-in |
|
|
12
|
+
| Auth Register | `/register` | Email, username, password, confirm password |
|
|
13
|
+
| Auth Forgot Password | `/forgot-password` | Request verification code via email/username |
|
|
14
|
+
| Auth Verify Code | `/verify-code` | 6-digit OTP input with resend + countdown |
|
|
15
|
+
| Profile | `/profile` | View/edit profile + change password |
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Theme Section (UI only)
|
|
21
|
+
└── Theme Hook (form state, validation)
|
|
22
|
+
└── useAuth() from @onexapis/core/hooks (Zustand store)
|
|
23
|
+
└── AuthService (API calls)
|
|
24
|
+
└── ApiClient (HTTP client with token management)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Hooks
|
|
28
|
+
|
|
29
|
+
All hooks live in `hooks/` and use `useAuth` from `@onexapis/core/hooks`:
|
|
30
|
+
|
|
31
|
+
| Hook | Uses | Manages |
|
|
32
|
+
| ----------------------- | ------------------------------------------------------------- | ---------------------------------------------------- |
|
|
33
|
+
| `useLoginForm` | `useAuth().login()` | Username, password, show/hide toggle, validation |
|
|
34
|
+
| `useRegisterForm` | `useAuth().signup()` | Email, username, password, confirm, validation |
|
|
35
|
+
| `useForgotPasswordForm` | `useAuth().forgotPassword()` | Username/email input, validation |
|
|
36
|
+
| `useVerifyCodeForm` | `useAuth().verifyCode()`, `verifyCodeReset()`, `resendCode()` | 6-digit OTP, auto-focus, paste, countdown |
|
|
37
|
+
| `useProfileForm` | `useAuth().updateProfile()`, `changePassword()`, `logout()` | Profile fields, dirty tracking, change password form |
|
|
38
|
+
|
|
39
|
+
### API Endpoints
|
|
40
|
+
|
|
41
|
+
The `AuthService` in `@onexapis/core` handles all API calls:
|
|
42
|
+
|
|
43
|
+
| Action | Method | Endpoint |
|
|
44
|
+
| ------------------- | ------ | ----------------------------------- |
|
|
45
|
+
| Login | POST | `/auth/login` |
|
|
46
|
+
| Register | POST | `/auth/register` |
|
|
47
|
+
| Forgot Password | POST | `/auth/forgot` |
|
|
48
|
+
| Verify Code | POST | `/auth/confirm` |
|
|
49
|
+
| Verify Code (reset) | POST | `/auth/confirm_reset_password_code` |
|
|
50
|
+
| Resend Code | POST | `/auth/resend_code` |
|
|
51
|
+
| Reset Password | POST | `/auth/new_password` |
|
|
52
|
+
| Change Password | POST | `/auth/change_password` |
|
|
53
|
+
| Get User Info | GET | `/auth/user_info` |
|
|
54
|
+
| Update Profile | POST | `/auth/user_info` |
|
|
55
|
+
|
|
56
|
+
### Token Handling
|
|
57
|
+
|
|
58
|
+
- **IdToken** → `Authorization: Bearer {IdToken}` header
|
|
59
|
+
- **AccessToken** → `?access_token={AccessToken}` query parameter
|
|
60
|
+
- **Tokens stored** in `localStorage` under key `auth_tokens`
|
|
61
|
+
- **Signup** auto-injects `company_id` (from `initializeOnex()` config) and `is_customer: true`
|
|
62
|
+
|
|
63
|
+
## Editable Settings
|
|
64
|
+
|
|
65
|
+
All sections expose these layout settings via the editor sidebar:
|
|
66
|
+
|
|
67
|
+
| Setting | Type | Default | Description |
|
|
68
|
+
| ------------------ | ------ | --------- | ------------------------------------ |
|
|
69
|
+
| `backgroundColor` | color | `#F9FAFB` | Page background |
|
|
70
|
+
| `cardBackground` | color | `#FFFFFF` | Card/form container background |
|
|
71
|
+
| `primaryColor` | color | `#2563EB` | Buttons and links |
|
|
72
|
+
| `textColor` | color | `#111827` | Heading text color |
|
|
73
|
+
| `cardBorderRadius` | select | `2xl` | Card corner rounding (lg/xl/2xl/3xl) |
|
|
74
|
+
|
|
75
|
+
Plus all text labels, placeholders, button text, and URLs are customizable per section.
|
|
76
|
+
|
|
77
|
+
## Auth Flow
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Register → Verify Code → Login → Profile
|
|
81
|
+
↑ │
|
|
82
|
+
Forgot Password → Verify Code (reset) → Reset Password
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
1. **Register**: User fills email, username, password → `signup()` → redirects to `/verify-code?username=...`
|
|
86
|
+
2. **Verify Code**: User enters 6-digit OTP → `verifyCode()` → redirects to `/login`
|
|
87
|
+
3. **Login**: User signs in → `login()` → tokens stored → redirects to `/`
|
|
88
|
+
4. **Forgot Password**: User enters email → `forgotPassword()` → redirects to `/verify-code?mode=reset&username=...`
|
|
89
|
+
5. **Verify Code (reset)**: OTP → `verifyCodeReset()` → returns session → redirects to `/reset-password?session=...`
|
|
90
|
+
6. **Profile**: View/edit name, phone, address → `updateProfile()`. Change password → `changePassword()`
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
### Required Environment Variables
|
|
95
|
+
|
|
96
|
+
```env
|
|
97
|
+
NEXT_PUBLIC_API_URL=https://api-dev.onexeos.com
|
|
98
|
+
NEXT_PUBLIC_COMPANY_ID=your-company-id
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
These are read by `initializeOnex()` which creates the `AuthService` and registers it with the `useAuth` Zustand store.
|
|
102
|
+
|
|
103
|
+
## Testing Locally
|
|
104
|
+
|
|
105
|
+
### Preview mode (UI only)
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
onexthm dev
|
|
109
|
+
# Visit http://localhost:3456/login
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Preview with editing mode
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
onexthm dev
|
|
116
|
+
# Visit http://localhost:3456/login?editing=true
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Preview with real API (requires .env)
|
|
120
|
+
|
|
121
|
+
Create `.env` in your theme directory:
|
|
122
|
+
|
|
123
|
+
```env
|
|
124
|
+
NEXT_PUBLIC_API_URL=https://api-dev.onexeos.com
|
|
125
|
+
NEXT_PUBLIC_COMPANY_ID=your-company-id
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Then run `onexthm dev` — login/register/verify will make real API calls.
|
|
129
|
+
|
|
130
|
+
## File Structure
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
hooks/
|
|
134
|
+
index.ts
|
|
135
|
+
use-login-form.ts
|
|
136
|
+
use-register-form.ts
|
|
137
|
+
use-forgot-password-form.ts
|
|
138
|
+
use-verify-code-form.ts
|
|
139
|
+
use-profile-form.ts
|
|
140
|
+
sections/
|
|
141
|
+
auth-login/
|
|
142
|
+
auth-login-default.tsx # Login form UI
|
|
143
|
+
auth-login.schema.ts # Editable settings
|
|
144
|
+
index.ts
|
|
145
|
+
auth-register/
|
|
146
|
+
auth-register-default.tsx
|
|
147
|
+
auth-register.schema.ts
|
|
148
|
+
index.ts
|
|
149
|
+
auth-forgot-password/
|
|
150
|
+
auth-forgot-password-default.tsx
|
|
151
|
+
auth-forgot-password.schema.ts
|
|
152
|
+
index.ts
|
|
153
|
+
auth-verify-code/
|
|
154
|
+
auth-verify-code-default.tsx
|
|
155
|
+
auth-verify-code.schema.ts
|
|
156
|
+
index.ts
|
|
157
|
+
profile/
|
|
158
|
+
profile-default.tsx
|
|
159
|
+
profile.schema.ts
|
|
160
|
+
index.ts
|
|
161
|
+
pages/
|
|
162
|
+
login.ts
|
|
163
|
+
register.ts
|
|
164
|
+
forgot-password.ts
|
|
165
|
+
verify-code.ts
|
|
166
|
+
profile.ts
|
|
167
|
+
```
|
|
@@ -464,44 +464,36 @@ Without these, the editor cannot select sections/blocks for editing.
|
|
|
464
464
|
|
|
465
465
|
34 built-in components from `@onexapis/core` that render inside sections via `ComponentRenderer`:
|
|
466
466
|
|
|
467
|
-
| Component
|
|
468
|
-
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
| `progress` | Progress bar | value (0-100), label, color |
|
|
480
|
-
| `rating` | Star rating | value (1-5), readOnly, size |
|
|
481
|
-
| `social-links` | Social media links | Reads from WebsiteSettings context |
|
|
482
|
-
| `hotline-contacts` | Contact info | Reads from WebsiteSettings context |
|
|
483
|
-
| `company-info` | Company details | Reads from WebsiteSettings context |
|
|
484
|
-
| `product-card` | Product display | product (Product), showQuickAdd |
|
|
485
|
-
| `blog-card` | Blog post card | blog (Blog) |
|
|
467
|
+
| Component | Description | Key Settings |
|
|
468
|
+
| --------- | ----------- | ------------ |
|
|
469
|
+
|
|
470
|
+
**Text:** `heading` (H1-H6), `paragraph`, `quote`
|
|
471
|
+
**Interactive:** `button` (default/outline/ghost/link), `link`, `input`, `textarea`, `checkbox`, `select`
|
|
472
|
+
**Media:** `image`, `video` (youtube/vimeo/hosted), `icon` (Lucide icons), `gallery`
|
|
473
|
+
**Layout:** `divider`, `spacer`, `container`, `grid`, `columns`, `card`
|
|
474
|
+
**Display:** `badge`, `alert`, `progress`, `rating`, `timer`, `list`, `table`, `accordion`, `tabs`, `code`, `map`
|
|
475
|
+
**Special:** `product-card`, `blog-card`, `social-links`, `hotline-contacts`, `company-info`
|
|
476
|
+
**Decorative:** `torn-separator`
|
|
477
|
+
|
|
478
|
+
Use `onexthm://components` MCP resource for full details (content/style fields, slots, examples).
|
|
486
479
|
|
|
487
480
|
Components are rendered via `ComponentRenderer` — you don't import them directly.
|
|
488
481
|
|
|
489
482
|
## Component Slots
|
|
490
483
|
|
|
491
|
-
Components use `slot` to
|
|
484
|
+
Components use `slot` to identify their role:
|
|
492
485
|
|
|
493
486
|
```tsx
|
|
494
|
-
// In section component
|
|
495
487
|
const titleComp = components.find((c) => c.slot === "section-title");
|
|
496
488
|
const ctaButton = components.find((c) => c.slot === "cta-button");
|
|
497
|
-
const subtitle = components.find((c) => c.slot === "description");
|
|
498
489
|
```
|
|
499
490
|
|
|
500
|
-
Common
|
|
491
|
+
Common slots: `section-title`, `section-subtitle`, `description`, `cta-button`, `secondary-cta`, `badge`, `icon`, `image`, `block-title`, `block-description`, `feature-icon`
|
|
501
492
|
|
|
502
493
|
## Block System
|
|
503
494
|
|
|
504
|
-
Blocks are nested containers inside sections. They
|
|
495
|
+
Blocks are nested containers inside sections. They hold components and can nest other blocks.
|
|
496
|
+
Use `onexthm://blocks` MCP resource for full block patterns (features, testimonials, pricing, FAQ, team).
|
|
505
497
|
|
|
506
498
|
```tsx
|
|
507
499
|
// Section → Blocks → Components
|
|
@@ -521,6 +513,68 @@ Blocks are nested containers inside sections. They can contain components AND ot
|
|
|
521
513
|
|
|
522
514
|
Use `BlockRenderer` to render blocks — it handles recursive nesting automatically.
|
|
523
515
|
|
|
516
|
+
### Defining Blocks in Schema
|
|
517
|
+
|
|
518
|
+
```tsx
|
|
519
|
+
// In your-section.schema.ts
|
|
520
|
+
export const mySchema: SectionSchema = {
|
|
521
|
+
type: "features",
|
|
522
|
+
// ...settings, defaults...
|
|
523
|
+
blocks: [
|
|
524
|
+
{
|
|
525
|
+
type: "feature-item",
|
|
526
|
+
name: "Feature Item",
|
|
527
|
+
limit: 6, // Max 6 blocks of this type
|
|
528
|
+
settings: [
|
|
529
|
+
{
|
|
530
|
+
id: "backgroundColor",
|
|
531
|
+
type: "color",
|
|
532
|
+
label: "Background",
|
|
533
|
+
default: "#FFFFFF",
|
|
534
|
+
},
|
|
535
|
+
],
|
|
536
|
+
components: [
|
|
537
|
+
{ type: "icon", slot: "feature-icon", label: "Icon" },
|
|
538
|
+
{ type: "heading", slot: "block-title", label: "Title" },
|
|
539
|
+
{ type: "paragraph", slot: "block-description", label: "Description" },
|
|
540
|
+
],
|
|
541
|
+
},
|
|
542
|
+
],
|
|
543
|
+
};
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### Rendering Blocks
|
|
547
|
+
|
|
548
|
+
```tsx
|
|
549
|
+
const blocks = (section.blocks || []).filter((b) => b.enabled !== false);
|
|
550
|
+
|
|
551
|
+
{
|
|
552
|
+
blocks.map((block) => (
|
|
553
|
+
<div
|
|
554
|
+
key={block.id}
|
|
555
|
+
data-section-id={section.id} // REQUIRED
|
|
556
|
+
data-block-id={block.id} // REQUIRED
|
|
557
|
+
data-block-type={block.type} // REQUIRED
|
|
558
|
+
className="p-6 rounded-xl border"
|
|
559
|
+
>
|
|
560
|
+
<BlockRenderer
|
|
561
|
+
block={block}
|
|
562
|
+
sectionId={section.id}
|
|
563
|
+
isEditing={isEditing}
|
|
564
|
+
/>
|
|
565
|
+
</div>
|
|
566
|
+
));
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
{
|
|
570
|
+
blocks.length === 0 && isEditing && (
|
|
571
|
+
<div className="text-center py-12 border-2 border-dashed border-gray-300 rounded-lg">
|
|
572
|
+
<p className="text-gray-500">Add blocks to populate this section</p>
|
|
573
|
+
</div>
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
524
578
|
## Animation System
|
|
525
579
|
|
|
526
580
|
Sections, blocks, and components support animations via framer-motion:
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Header & Footer Layout — Default Theme
|
|
2
|
+
|
|
3
|
+
This document covers the built-in header and footer sections that render on every page via `theme.layout.ts`.
|
|
4
|
+
|
|
5
|
+
## How It Works
|
|
6
|
+
|
|
7
|
+
Header and footer are **layout-level sections** — they're defined once in `theme.layout.ts` and rendered on every page automatically. Individual pages can opt out using `hideHeader` / `hideFooter`.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
theme.layout.ts
|
|
11
|
+
├── headerSections[] ──→ rendered as <header> on every page
|
|
12
|
+
└── footerSections[] ──→ rendered as <footer> on every page
|
|
13
|
+
|
|
14
|
+
Page config
|
|
15
|
+
├── hideHeader: true ──→ skips header for this page
|
|
16
|
+
└── hideFooter: true ──→ skips footer for this page
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Header Section
|
|
20
|
+
|
|
21
|
+
### Smart Scroll Behavior (default: on)
|
|
22
|
+
|
|
23
|
+
The header auto-hides when scrolling down and reappears when scrolling up. This is fully customizable via schema settings:
|
|
24
|
+
|
|
25
|
+
| Setting | Type | Default | Description |
|
|
26
|
+
| -------------------- | -------- | ----------- | --------------------------------------------- |
|
|
27
|
+
| `sticky` | checkbox | `true` | Pin header to top of viewport |
|
|
28
|
+
| `autoHide` | checkbox | `true` | Hide on scroll down, show on scroll up |
|
|
29
|
+
| `scrollThreshold` | number | `10` | Pixels of scroll needed to trigger hide/show |
|
|
30
|
+
| `showShadowOnScroll` | checkbox | `true` | Add drop shadow when page is scrolled |
|
|
31
|
+
| `transparentOnTop` | checkbox | `false` | Transparent background when at top of page |
|
|
32
|
+
| `headerHeight` | select | `16` (64px) | Compact (48px) / Default (64px) / Tall (80px) |
|
|
33
|
+
|
|
34
|
+
### Behavior Presets
|
|
35
|
+
|
|
36
|
+
**Default (sticky + auto-hide):**
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
// theme.layout.ts
|
|
40
|
+
settings: {
|
|
41
|
+
sticky: true,
|
|
42
|
+
autoHide: true,
|
|
43
|
+
showShadowOnScroll: true,
|
|
44
|
+
transparentOnTop: false,
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Always visible (no auto-hide):**
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
settings: {
|
|
52
|
+
sticky: true,
|
|
53
|
+
autoHide: false, // ← stays visible at all times
|
|
54
|
+
showShadowOnScroll: true,
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Transparent hero overlay:**
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
settings: {
|
|
62
|
+
sticky: true,
|
|
63
|
+
autoHide: true,
|
|
64
|
+
transparentOnTop: true, // ← transparent at top, solid on scroll
|
|
65
|
+
showShadowOnScroll: true,
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Static (not sticky):**
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
settings: {
|
|
73
|
+
sticky: false, // ← scrolls away with the page
|
|
74
|
+
autoHide: false,
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Other Header Settings
|
|
79
|
+
|
|
80
|
+
| Setting | Type | Default | Description |
|
|
81
|
+
| ----------------- | ----- | --------------------- | ----------------------------------------- |
|
|
82
|
+
| `logoText` | text | `"My Site"` | Logo text (fallback if no logo component) |
|
|
83
|
+
| `navigationItems` | array | Home, About, Showcase | Navigation links `[{ label, href }]` |
|
|
84
|
+
| `ctaText` | text | `"Contact"` | CTA button text |
|
|
85
|
+
| `ctaLink` | url | `"#contact"` | CTA button link |
|
|
86
|
+
| `backgroundColor` | color | `#FFFFFF` | Header background |
|
|
87
|
+
| `textColor` | color | `#111827` | Navigation text color |
|
|
88
|
+
| `primaryColor` | color | `#2563EB` | CTA button color |
|
|
89
|
+
|
|
90
|
+
### How Auto-Hide Works
|
|
91
|
+
|
|
92
|
+
The header uses `requestAnimationFrame` for smooth 60fps scroll detection:
|
|
93
|
+
|
|
94
|
+
1. **Scroll down > threshold** (and past 80px from top) → header slides up (`translateY(-100%)`)
|
|
95
|
+
2. **Scroll up > threshold** → header slides back down (`translateY(0)`)
|
|
96
|
+
3. **At very top** (scrollY = 0) → always visible
|
|
97
|
+
4. **In editor** (`isEditing = true`) → auto-hide disabled, always visible
|
|
98
|
+
5. CSS transition: `duration-300 ease-in-out` for smooth animation
|
|
99
|
+
|
|
100
|
+
## Footer Section
|
|
101
|
+
|
|
102
|
+
### Settings
|
|
103
|
+
|
|
104
|
+
| Setting | Type | Default | Description |
|
|
105
|
+
| ------------------ | -------- | -------------------------- | ------------------------------- |
|
|
106
|
+
| `companyName` | text | `"My Site"` | Company name |
|
|
107
|
+
| `description` | textarea | `"A clean and minimal..."` | Company description |
|
|
108
|
+
| `showAboutColumn` | checkbox | `true` | Show/hide about links column |
|
|
109
|
+
| `aboutColumnTitle` | text | `"About"` | About column heading |
|
|
110
|
+
| `aboutLinks` | array | About Us, Contact | About links `[{ label, href }]` |
|
|
111
|
+
| `showLinksColumn` | checkbox | `true` | Show/hide quick links column |
|
|
112
|
+
| `linksColumnTitle` | text | `"Quick Links"` | Links column heading |
|
|
113
|
+
| `quickLinks` | array | Home, Showcase | Quick links `[{ label, href }]` |
|
|
114
|
+
| `copyrightText` | text | `"My Site. All rights..."` | Copyright text |
|
|
115
|
+
| `backgroundColor` | color | `#111827` | Footer background |
|
|
116
|
+
| `textColor` | color | `#9CA3AF` | Body text color |
|
|
117
|
+
| `primaryColor` | color | `#FFFFFF` | Heading color |
|
|
118
|
+
|
|
119
|
+
## Per-Page Visibility
|
|
120
|
+
|
|
121
|
+
Auth pages (login, register, forgot-password, verify-code) have `hideHeader: true` and `hideFooter: true` by default since they're full-screen.
|
|
122
|
+
|
|
123
|
+
### Hide header/footer on a page
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
// pages/my-page.ts
|
|
127
|
+
export const myPageConfig = {
|
|
128
|
+
title: "My Page",
|
|
129
|
+
handle: "my-page",
|
|
130
|
+
path: "/my-page",
|
|
131
|
+
hideHeader: true, // ← no header on this page
|
|
132
|
+
hideFooter: true, // ← no footer on this page
|
|
133
|
+
// ...
|
|
134
|
+
};
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Pages with header/footer (default)
|
|
138
|
+
|
|
139
|
+
Pages without `hideHeader`/`hideFooter` get both automatically:
|
|
140
|
+
|
|
141
|
+
| Page | Header | Footer |
|
|
142
|
+
| ------------------------------------ | ------ | ------ |
|
|
143
|
+
| Home (`/`) | Yes | Yes |
|
|
144
|
+
| About (`/about`) | Yes | Yes |
|
|
145
|
+
| Showcase (`/showcase`) | Yes | Yes |
|
|
146
|
+
| Profile (`/profile`) | Yes | Yes |
|
|
147
|
+
| Login (`/login`) | No | No |
|
|
148
|
+
| Register (`/register`) | No | No |
|
|
149
|
+
| Forgot Password (`/forgot-password`) | No | No |
|
|
150
|
+
| Verify Code (`/verify-code`) | No | No |
|
|
151
|
+
|
|
152
|
+
## Customizing in the Editor
|
|
153
|
+
|
|
154
|
+
In the visual editor:
|
|
155
|
+
|
|
156
|
+
1. Go to **Theme Settings** (gear icon)
|
|
157
|
+
2. Open **Header Sections** or **Footer Sections** accordion
|
|
158
|
+
3. Click on the section to edit its settings
|
|
159
|
+
4. Changes apply to all pages that show the header/footer
|
|
160
|
+
|
|
161
|
+
## File Structure
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
sections/
|
|
165
|
+
header/
|
|
166
|
+
header-default.tsx # Component with smart scroll behavior
|
|
167
|
+
header.schema.ts # All settings including scroll behavior
|
|
168
|
+
index.ts
|
|
169
|
+
footer/
|
|
170
|
+
footer-default.tsx # 3-column footer
|
|
171
|
+
footer.schema.ts # Company info, links, copyright settings
|
|
172
|
+
index.ts
|
|
173
|
+
theme.layout.ts # References header/footer with default settings
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Extending
|
|
177
|
+
|
|
178
|
+
### Adding a second header template
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
// sections/header/header-centered.tsx
|
|
182
|
+
export function HeaderCentered({ section, schema, isEditing }) { ... }
|
|
183
|
+
|
|
184
|
+
// sections/header/index.ts
|
|
185
|
+
export const headerComponents = {
|
|
186
|
+
default: HeaderDefault,
|
|
187
|
+
centered: HeaderCentered, // ← new template
|
|
188
|
+
};
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Then in `theme.layout.ts`, change `template: "centered"`.
|
|
192
|
+
|
|
193
|
+
### Custom scroll behavior
|
|
194
|
+
|
|
195
|
+
Override the `useHeaderScroll` hook logic in `header-default.tsx`, or create a new template with different behavior. All scroll settings are passed via schema — no code changes needed for basic customization.
|
|
@@ -16,3 +16,8 @@ export { default as layoutConfig } from "./theme.layout";
|
|
|
16
16
|
export { default as homePageConfig } from "./pages/home";
|
|
17
17
|
export { default as aboutPageConfig } from "./pages/about";
|
|
18
18
|
export { default as showcasePageConfig } from "./pages/showcase";
|
|
19
|
+
export { default as loginPageConfig } from "./pages/login";
|
|
20
|
+
export { default as registerPageConfig } from "./pages/register";
|
|
21
|
+
export { default as forgotPasswordPageConfig } from "./pages/forgot-password";
|
|
22
|
+
export { default as verifyCodePageConfig } from "./pages/verify-code";
|
|
23
|
+
export { default as profilePageConfig } from "./pages/profile";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Hooks
|
|
3
|
+
* Re-exports all authentication form hooks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { useLoginForm } from "./use-login-form";
|
|
7
|
+
export type { LoginFormData, UseLoginFormReturn } from "./use-login-form";
|
|
8
|
+
|
|
9
|
+
export { useRegisterForm } from "./use-register-form";
|
|
10
|
+
export type {
|
|
11
|
+
RegisterFormData,
|
|
12
|
+
UseRegisterFormReturn,
|
|
13
|
+
} from "./use-register-form";
|
|
14
|
+
|
|
15
|
+
export { useForgotPasswordForm } from "./use-forgot-password-form";
|
|
16
|
+
export type { UseForgotPasswordFormReturn } from "./use-forgot-password-form";
|
|
17
|
+
|
|
18
|
+
export { useVerifyCodeForm } from "./use-verify-code-form";
|
|
19
|
+
export type { UseVerifyCodeFormReturn } from "./use-verify-code-form";
|
|
20
|
+
|
|
21
|
+
export { useProfileForm } from "./use-profile-form";
|
|
22
|
+
export type {
|
|
23
|
+
ProfileFormData,
|
|
24
|
+
ChangePasswordData,
|
|
25
|
+
UseProfileFormReturn,
|
|
26
|
+
} from "./use-profile-form";
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forgot Password Form Hook
|
|
3
|
+
* Manages form state, validation, and submission via useAuth
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use client";
|
|
7
|
+
|
|
8
|
+
import { useState, useCallback } from "react";
|
|
9
|
+
import { useAuth } from "@onexapis/core/hooks";
|
|
10
|
+
|
|
11
|
+
export interface UseForgotPasswordFormReturn {
|
|
12
|
+
username: string;
|
|
13
|
+
error: string | null;
|
|
14
|
+
isPending: boolean;
|
|
15
|
+
setUsername: (value: string) => void;
|
|
16
|
+
handleUsernameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
17
|
+
handleSubmit: (e: React.FormEvent) => void;
|
|
18
|
+
clearError: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useForgotPasswordForm(): UseForgotPasswordFormReturn {
|
|
22
|
+
const [username, setUsername] = useState("");
|
|
23
|
+
const [error, setError] = useState<string | null>(null);
|
|
24
|
+
const [isPending, setIsPending] = useState(false);
|
|
25
|
+
|
|
26
|
+
const { forgotPassword } = useAuth();
|
|
27
|
+
|
|
28
|
+
const handleUsernameChange = useCallback(
|
|
29
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
30
|
+
setUsername(e.target.value);
|
|
31
|
+
setError(null);
|
|
32
|
+
},
|
|
33
|
+
[]
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const handleSubmit = useCallback(
|
|
37
|
+
async (e: React.FormEvent) => {
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
if (!username) {
|
|
40
|
+
setError("Please enter your email or username");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (
|
|
44
|
+
username.includes("@") &&
|
|
45
|
+
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(username)
|
|
46
|
+
) {
|
|
47
|
+
setError("Invalid email format");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setError(null);
|
|
52
|
+
setIsPending(true);
|
|
53
|
+
try {
|
|
54
|
+
await forgotPassword({ username });
|
|
55
|
+
// Store countdown and redirect to verify-code
|
|
56
|
+
if (typeof window !== "undefined") {
|
|
57
|
+
const expiryTimestamp = Date.now() + 60 * 1000;
|
|
58
|
+
localStorage.setItem(
|
|
59
|
+
`resend_countdown_${username}`,
|
|
60
|
+
expiryTimestamp.toString()
|
|
61
|
+
);
|
|
62
|
+
window.location.href = `/verify-code?mode=reset&username=${encodeURIComponent(username)}`;
|
|
63
|
+
}
|
|
64
|
+
} catch (err) {
|
|
65
|
+
const message =
|
|
66
|
+
err instanceof Error
|
|
67
|
+
? err.message
|
|
68
|
+
: "Failed to send verification code";
|
|
69
|
+
setError(message);
|
|
70
|
+
} finally {
|
|
71
|
+
setIsPending(false);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
[username, forgotPassword]
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const clearError = useCallback(() => {
|
|
78
|
+
setError(null);
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
username,
|
|
83
|
+
error,
|
|
84
|
+
isPending,
|
|
85
|
+
setUsername,
|
|
86
|
+
handleUsernameChange,
|
|
87
|
+
handleSubmit,
|
|
88
|
+
clearError,
|
|
89
|
+
};
|
|
90
|
+
}
|