@djangocfg/nextjs 1.0.5 → 1.0.6
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/package.json +10 -7
- package/src/config/base-next-config.ts +67 -0
- package/src/contact/submit.ts +28 -35
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/nextjs",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nextjs",
|
|
@@ -95,17 +95,18 @@
|
|
|
95
95
|
"check-links": "tsx src/scripts/check-links.ts"
|
|
96
96
|
},
|
|
97
97
|
"peerDependencies": {
|
|
98
|
-
"@djangocfg/api": "^1.4.
|
|
98
|
+
"@djangocfg/api": "^1.4.36",
|
|
99
99
|
"next": "^15.4.4"
|
|
100
100
|
},
|
|
101
101
|
"devDependencies": {
|
|
102
|
-
"@djangocfg/imgai": "^1.0.
|
|
103
|
-
"@djangocfg/layouts": "^2.0.
|
|
104
|
-
"@djangocfg/typescript-config": "^1.4.
|
|
102
|
+
"@djangocfg/imgai": "^1.0.22",
|
|
103
|
+
"@djangocfg/layouts": "^2.0.6",
|
|
104
|
+
"@djangocfg/typescript-config": "^1.4.36",
|
|
105
105
|
"@types/node": "^24.7.2",
|
|
106
106
|
"@types/react": "19.2.2",
|
|
107
107
|
"@types/react-dom": "19.2.1",
|
|
108
108
|
"@types/webpack": "^5.28.5",
|
|
109
|
+
"@vercel/og": "^0.8.5",
|
|
109
110
|
"eslint": "^9.37.0",
|
|
110
111
|
"linkinator": "^7.5.0",
|
|
111
112
|
"lucide-react": "^0.469.0",
|
|
@@ -113,10 +114,12 @@
|
|
|
113
114
|
"prompts": "^2.4.2",
|
|
114
115
|
"tsup": "^8.0.1",
|
|
115
116
|
"tsx": "^4.19.2",
|
|
116
|
-
"typescript": "^5.9.3"
|
|
117
|
-
"@vercel/og": "^0.8.5"
|
|
117
|
+
"typescript": "^5.9.3"
|
|
118
118
|
},
|
|
119
119
|
"publishConfig": {
|
|
120
120
|
"access": "public"
|
|
121
|
+
},
|
|
122
|
+
"dependencies": {
|
|
123
|
+
"chalk": "^5.3.0"
|
|
121
124
|
}
|
|
122
125
|
}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
import type { NextConfig } from 'next';
|
|
25
25
|
import type { Configuration as WebpackConfig } from 'webpack';
|
|
26
|
+
import chalk from 'chalk';
|
|
26
27
|
import { deepMerge } from './deepMerge';
|
|
27
28
|
|
|
28
29
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -32,6 +33,25 @@ import { deepMerge } from './deepMerge';
|
|
|
32
33
|
const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';
|
|
33
34
|
const isDev = process.env.NODE_ENV === 'development';
|
|
34
35
|
|
|
36
|
+
// Global flag to track if browser was already opened (persists across hot reloads)
|
|
37
|
+
let browserOpened = false;
|
|
38
|
+
let bannerPrinted = false;
|
|
39
|
+
|
|
40
|
+
// ASCII Art Banner for Django CFG
|
|
41
|
+
const DJANGO_CFG_BANNER = `
|
|
42
|
+
888 d8b .d888
|
|
43
|
+
888 Y8P d88P"
|
|
44
|
+
888 888
|
|
45
|
+
.d88888 8888 8888b. 88888b. .d88b. .d88b. .d8888b 888888 .d88b.
|
|
46
|
+
d88" 888 "888 "88b 888 "88b d88P"88b d88""88b d88P" 888 d88P"88b
|
|
47
|
+
888 888 888 .d888888 888 888 888 888 888 888 888 888 888 888
|
|
48
|
+
Y88b 888 888 888 888 888 888 Y88b 888 Y88..88P Y88b. 888 Y88b 888
|
|
49
|
+
"Y88888 888 "Y888888 888 888 "Y88888 "Y88P" "Y8888P 888 "Y88888
|
|
50
|
+
888 888 888
|
|
51
|
+
d88P Y8b d88P Y8b d88P
|
|
52
|
+
888P" "Y88P" "Y88P"
|
|
53
|
+
`;
|
|
54
|
+
|
|
35
55
|
// ─────────────────────────────────────────────────────────────────────────
|
|
36
56
|
// Configuration Options
|
|
37
57
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -43,6 +63,8 @@ export interface BaseNextConfigOptions {
|
|
|
43
63
|
transpilePackages?: string[];
|
|
44
64
|
/** Additional optimize package imports (merged with defaults) */
|
|
45
65
|
optimizePackageImports?: string[];
|
|
66
|
+
/** Automatically open browser in dev mode (default: false) */
|
|
67
|
+
openBrowser?: boolean;
|
|
46
68
|
/** Custom webpack configuration function (called after base webpack logic) */
|
|
47
69
|
webpack?: (
|
|
48
70
|
config: WebpackConfig,
|
|
@@ -153,6 +175,50 @@ export function createBaseNextConfig(
|
|
|
153
175
|
webpack: (config: WebpackConfig, webpackOptions: { isServer: boolean; dev: boolean;[key: string]: any }) => {
|
|
154
176
|
const { isServer, dev } = webpackOptions;
|
|
155
177
|
|
|
178
|
+
// Print banner and auto-open browser in dev mode (client-side only)
|
|
179
|
+
if (dev && !isServer) {
|
|
180
|
+
// Create a simple plugin to print banner and open browser after first compilation
|
|
181
|
+
const DevStartupPlugin = class {
|
|
182
|
+
apply(compiler: any) {
|
|
183
|
+
compiler.hooks.done.tap('DevStartupPlugin', () => {
|
|
184
|
+
// Print banner only once
|
|
185
|
+
if (!bannerPrinted) {
|
|
186
|
+
bannerPrinted = true;
|
|
187
|
+
console.log('\n' + chalk.yellowBright.bold(DJANGO_CFG_BANNER) + '\n');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Auto-open browser if enabled
|
|
191
|
+
if (options.openBrowser && !browserOpened) {
|
|
192
|
+
browserOpened = true;
|
|
193
|
+
// Delay to ensure server is ready
|
|
194
|
+
setTimeout(() => {
|
|
195
|
+
const { exec } = require('child_process');
|
|
196
|
+
const port = process.env.PORT || '3000';
|
|
197
|
+
const url = `http://localhost:${port}`;
|
|
198
|
+
|
|
199
|
+
const command = process.platform === 'darwin'
|
|
200
|
+
? 'open'
|
|
201
|
+
: process.platform === 'win32'
|
|
202
|
+
? 'start'
|
|
203
|
+
: 'xdg-open';
|
|
204
|
+
|
|
205
|
+
exec(`${command} ${url}`, (error: Error | null) => {
|
|
206
|
+
if (error) {
|
|
207
|
+
console.warn(`Failed to open browser: ${error.message}`);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}, 2000); // Wait 2 seconds for server to be ready
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
if (!config.plugins) {
|
|
217
|
+
config.plugins = [];
|
|
218
|
+
}
|
|
219
|
+
config.plugins.push(new DevStartupPlugin());
|
|
220
|
+
}
|
|
221
|
+
|
|
156
222
|
// Dev mode optimizations
|
|
157
223
|
if (dev) {
|
|
158
224
|
config.optimization = {
|
|
@@ -227,6 +293,7 @@ export function createBaseNextConfig(
|
|
|
227
293
|
// These are internal to BaseNextConfigOptions and should not be in the final config
|
|
228
294
|
delete (finalConfig as any).optimizePackageImports;
|
|
229
295
|
delete (finalConfig as any).isDefaultCfgAdmin;
|
|
296
|
+
delete (finalConfig as any).openBrowser;
|
|
230
297
|
// Note: We don't delete transpilePackages, experimental, env, webpack
|
|
231
298
|
// as they are valid NextConfig keys and may have been overridden by user
|
|
232
299
|
|
package/src/contact/submit.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Server-side function to submit contact form data to backend API.
|
|
5
5
|
* Can be used in Next.js API routes to avoid CORS issues.
|
|
6
6
|
*
|
|
7
|
+
* Uses Fetchers with server-side API instance for type safety and Zod validation.
|
|
8
|
+
*
|
|
7
9
|
* @example
|
|
8
10
|
* ```ts
|
|
9
11
|
* import { submitContactForm } from '@djangocfg/nextjs/contact';
|
|
@@ -17,8 +19,9 @@
|
|
|
17
19
|
* ```
|
|
18
20
|
*/
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
import
|
|
22
|
+
// Use server-only exports to avoid loading React hooks (useSWRConfig)
|
|
23
|
+
import { API, MemoryStorageAdapter, Fetchers } from '@djangocfg/api/server';
|
|
24
|
+
import type { Schemas } from '@djangocfg/api/server';
|
|
22
25
|
|
|
23
26
|
export interface SubmitContactFormOptions {
|
|
24
27
|
/** Lead submission data */
|
|
@@ -35,31 +38,11 @@ export interface SubmitContactFormResult {
|
|
|
35
38
|
lead_id?: number;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
/**
|
|
39
|
-
* Simple server-side memory storage adapter.
|
|
40
|
-
* Implements StorageAdapter interface without importing from client package.
|
|
41
|
-
*/
|
|
42
|
-
class ServerMemoryStorageAdapter {
|
|
43
|
-
private storage: Map<string, string> = new Map();
|
|
44
|
-
|
|
45
|
-
getItem(key: string): string | null {
|
|
46
|
-
return this.storage.get(key) || null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
setItem(key: string, value: string): void {
|
|
50
|
-
this.storage.set(key, value);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
removeItem(key: string): void {
|
|
54
|
-
this.storage.delete(key);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
41
|
/**
|
|
59
42
|
* Submit contact form data to backend API
|
|
60
43
|
*
|
|
61
|
-
* Server-side function that uses
|
|
62
|
-
*
|
|
44
|
+
* Server-side function that uses Fetchers with server-side API instance.
|
|
45
|
+
* This provides type safety, Zod validation, and proper error handling.
|
|
63
46
|
*/
|
|
64
47
|
export async function submitContactForm({
|
|
65
48
|
data,
|
|
@@ -75,10 +58,10 @@ export async function submitContactForm({
|
|
|
75
58
|
throw new Error('API URL is required');
|
|
76
59
|
}
|
|
77
60
|
|
|
78
|
-
// Create server-side API instance with
|
|
79
|
-
// This
|
|
61
|
+
// Create server-side API instance with MemoryStorageAdapter
|
|
62
|
+
// This works on server-side and doesn't require browser APIs
|
|
80
63
|
const serverApi = new API(apiUrl, {
|
|
81
|
-
storage: new
|
|
64
|
+
storage: new MemoryStorageAdapter(),
|
|
82
65
|
});
|
|
83
66
|
|
|
84
67
|
// Prepare submission data with site_url
|
|
@@ -87,14 +70,24 @@ export async function submitContactForm({
|
|
|
87
70
|
site_url: data.site_url || siteUrl,
|
|
88
71
|
};
|
|
89
72
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
73
|
+
try {
|
|
74
|
+
// Use typed fetcher with server API instance
|
|
75
|
+
// This provides type safety and runtime validation via Zod
|
|
76
|
+
// Using Fetchers namespace to avoid loading hooks
|
|
77
|
+
const result = await Fetchers.createLeadsSubmitCreate(submissionData, serverApi);
|
|
93
78
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
79
|
+
// Return formatted result
|
|
80
|
+
return {
|
|
81
|
+
success: result.success ?? true,
|
|
82
|
+
message: result.message || 'Contact form submitted successfully',
|
|
83
|
+
lead_id: result.lead_id,
|
|
84
|
+
};
|
|
85
|
+
} catch (error) {
|
|
86
|
+
// Handle API errors (including validation errors from Zod)
|
|
87
|
+
if (error instanceof Error) {
|
|
88
|
+
throw new Error(`Failed to submit contact form: ${error.message}`);
|
|
89
|
+
}
|
|
90
|
+
throw new Error('An unexpected error occurred while submitting the contact form');
|
|
91
|
+
}
|
|
99
92
|
}
|
|
100
93
|
|