@jhits/plugin-website 0.0.4 → 0.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/dist/api/handler.d.ts +16 -0
- package/dist/api/handler.d.ts.map +1 -0
- package/dist/api/handler.js +124 -0
- package/dist/api/router.d.ts +11 -0
- package/dist/api/router.d.ts.map +1 -0
- package/dist/api/router.js +31 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.server.d.ts +10 -0
- package/dist/index.server.d.ts.map +1 -0
- package/dist/index.server.js +8 -0
- package/dist/types/settings.d.ts +35 -0
- package/dist/types/settings.d.ts.map +1 -0
- package/dist/types/settings.js +4 -0
- package/dist/views/SettingsView.d.ts +10 -0
- package/dist/views/SettingsView.d.ts.map +1 -0
- package/dist/views/SettingsView.js +219 -0
- package/package.json +22 -20
- package/src/views/SettingsView.tsx +41 -40
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Website Settings API Handler
|
|
3
|
+
* Handles GET and POST requests for website settings
|
|
4
|
+
*/
|
|
5
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
6
|
+
import { WebsiteApiConfig } from '../types/settings';
|
|
7
|
+
export type { WebsiteApiConfig } from '../types/settings';
|
|
8
|
+
/**
|
|
9
|
+
* GET /api/plugin-website/settings - Get website settings
|
|
10
|
+
*/
|
|
11
|
+
export declare function GET_SETTINGS(req: NextRequest, config: WebsiteApiConfig): Promise<NextResponse>;
|
|
12
|
+
/**
|
|
13
|
+
* POST /api/plugin-website/settings - Update website settings
|
|
14
|
+
*/
|
|
15
|
+
export declare function POST_SETTINGS(req: NextRequest, config: WebsiteApiConfig): Promise<NextResponse>;
|
|
16
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/api/handler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D;;GAEG;AACH,wBAAsB,YAAY,CAC9B,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,gBAAgB,GACzB,OAAO,CAAC,YAAY,CAAC,CA6CvB;AAED;;GAEG;AACH,wBAAsB,aAAa,CAC/B,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,gBAAgB,GACzB,OAAO,CAAC,YAAY,CAAC,CAmFvB"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Website Settings API Handler
|
|
3
|
+
* Handles GET and POST requests for website settings
|
|
4
|
+
*/
|
|
5
|
+
'use server';
|
|
6
|
+
import { NextResponse } from 'next/server';
|
|
7
|
+
/**
|
|
8
|
+
* GET /api/plugin-website/settings - Get website settings
|
|
9
|
+
*/
|
|
10
|
+
export async function GET_SETTINGS(req, config) {
|
|
11
|
+
try {
|
|
12
|
+
const dbConnection = await config.getDb();
|
|
13
|
+
const db = dbConnection.db();
|
|
14
|
+
const settings = db.collection('settings');
|
|
15
|
+
const siteConfig = await settings.findOne({ identifier: 'site_config' });
|
|
16
|
+
if (!siteConfig) {
|
|
17
|
+
// Return default settings if none exist
|
|
18
|
+
return NextResponse.json({
|
|
19
|
+
identifier: 'site_config',
|
|
20
|
+
siteName: '',
|
|
21
|
+
siteTagline: '',
|
|
22
|
+
siteDescription: '',
|
|
23
|
+
keywords: '',
|
|
24
|
+
contactEmail: '',
|
|
25
|
+
phoneNumber: '',
|
|
26
|
+
physicalAddress: '',
|
|
27
|
+
smtpUser: '',
|
|
28
|
+
maintenanceMode: false,
|
|
29
|
+
launch_date: '',
|
|
30
|
+
socials: [],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// Convert launch_date from Date to ISO string if it exists
|
|
34
|
+
const responseData = { ...siteConfig };
|
|
35
|
+
if (responseData.launch_date instanceof Date) {
|
|
36
|
+
responseData.launch_date = responseData.launch_date.toISOString();
|
|
37
|
+
}
|
|
38
|
+
else if (responseData.launch_date) {
|
|
39
|
+
// If it's already a string, keep it as is
|
|
40
|
+
responseData.launch_date = responseData.launch_date;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
responseData.launch_date = '';
|
|
44
|
+
}
|
|
45
|
+
return NextResponse.json(responseData);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error('[WebsiteAPI] GET_SETTINGS error:', error);
|
|
49
|
+
return NextResponse.json({ error: 'Failed to fetch settings', detail: error.message }, { status: 500 });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* POST /api/plugin-website/settings - Update website settings
|
|
54
|
+
*/
|
|
55
|
+
export async function POST_SETTINGS(req, config) {
|
|
56
|
+
try {
|
|
57
|
+
const userId = await config.getUserId?.(req);
|
|
58
|
+
if (!userId) {
|
|
59
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
60
|
+
}
|
|
61
|
+
const body = await req.json();
|
|
62
|
+
console.log('[WebsiteAPI] POST_SETTINGS received body:', {
|
|
63
|
+
launch_date: body.launch_date,
|
|
64
|
+
launch_date_type: typeof body.launch_date,
|
|
65
|
+
has_launch_date: 'launch_date' in body,
|
|
66
|
+
body_keys: Object.keys(body),
|
|
67
|
+
});
|
|
68
|
+
const dbConnection = await config.getDb();
|
|
69
|
+
const db = dbConnection.db();
|
|
70
|
+
const settings = db.collection('settings');
|
|
71
|
+
// Remove _id and launch_date from updateData (we'll handle launch_date separately)
|
|
72
|
+
const { _id, launch_date, ...updateData } = body;
|
|
73
|
+
// Prepare base update operations
|
|
74
|
+
const updateOps = {
|
|
75
|
+
$set: {
|
|
76
|
+
identifier: 'site_config',
|
|
77
|
+
...updateData,
|
|
78
|
+
updatedAt: new Date(),
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
// Handle launch_date separately
|
|
82
|
+
if (launch_date !== undefined && launch_date !== null && launch_date !== '') {
|
|
83
|
+
// Convert launch_date from datetime-local string to Date if provided
|
|
84
|
+
const date = new Date(launch_date);
|
|
85
|
+
console.log('[WebsiteAPI] Date conversion:', {
|
|
86
|
+
input: launch_date,
|
|
87
|
+
parsed: date,
|
|
88
|
+
isValid: !isNaN(date.getTime()),
|
|
89
|
+
iso: !isNaN(date.getTime()) ? date.toISOString() : 'invalid',
|
|
90
|
+
});
|
|
91
|
+
if (!isNaN(date.getTime())) {
|
|
92
|
+
updateOps.$set.launch_date = date;
|
|
93
|
+
console.log('[WebsiteAPI] Setting launch_date to:', date.toISOString());
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
console.warn('[WebsiteAPI] Invalid date, removing launch_date field');
|
|
97
|
+
updateOps.$unset = { launch_date: '' };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
// Remove launch_date field if it's empty, undefined, or null
|
|
102
|
+
console.log('[WebsiteAPI] launch_date is empty/undefined, removing field');
|
|
103
|
+
updateOps.$unset = { launch_date: '' };
|
|
104
|
+
}
|
|
105
|
+
console.log('[WebsiteAPI] Update operations:', JSON.stringify(updateOps, (key, value) => {
|
|
106
|
+
if (value instanceof Date) {
|
|
107
|
+
return value.toISOString();
|
|
108
|
+
}
|
|
109
|
+
return value;
|
|
110
|
+
}, 2));
|
|
111
|
+
// Upsert settings
|
|
112
|
+
const result = await settings.updateOne({ identifier: 'site_config' }, updateOps, { upsert: true });
|
|
113
|
+
console.log('[WebsiteAPI] Update result:', {
|
|
114
|
+
matchedCount: result.matchedCount,
|
|
115
|
+
modifiedCount: result.modifiedCount,
|
|
116
|
+
upsertedCount: result.upsertedCount,
|
|
117
|
+
});
|
|
118
|
+
return NextResponse.json({ success: true, message: 'Settings updated successfully' });
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error('[WebsiteAPI] POST_SETTINGS error:', error);
|
|
122
|
+
return NextResponse.json({ error: 'Failed to update settings', detail: error.message }, { status: 500 });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Website Plugin API Router
|
|
3
|
+
* Routes API requests to appropriate handlers
|
|
4
|
+
*/
|
|
5
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
6
|
+
import { WebsiteApiConfig } from '../types/settings';
|
|
7
|
+
/**
|
|
8
|
+
* Handle website API requests
|
|
9
|
+
*/
|
|
10
|
+
export declare function handleWebsiteApi(req: NextRequest, path: string[], config: WebsiteApiConfig): Promise<NextResponse>;
|
|
11
|
+
//# sourceMappingURL=router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/api/router.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD;;GAEG;AACH,wBAAsB,gBAAgB,CAClC,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,gBAAgB,GACzB,OAAO,CAAC,YAAY,CAAC,CA2BvB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Website Plugin API Router
|
|
3
|
+
* Routes API requests to appropriate handlers
|
|
4
|
+
*/
|
|
5
|
+
'use server';
|
|
6
|
+
import { NextResponse } from 'next/server';
|
|
7
|
+
import { GET_SETTINGS, POST_SETTINGS } from './handler';
|
|
8
|
+
/**
|
|
9
|
+
* Handle website API requests
|
|
10
|
+
*/
|
|
11
|
+
export async function handleWebsiteApi(req, path, config) {
|
|
12
|
+
const method = req.method;
|
|
13
|
+
const route = path[0] || '';
|
|
14
|
+
try {
|
|
15
|
+
// Route: /api/plugin-website/settings
|
|
16
|
+
if (route === 'settings') {
|
|
17
|
+
if (method === 'GET') {
|
|
18
|
+
return await GET_SETTINGS(req, config);
|
|
19
|
+
}
|
|
20
|
+
if (method === 'POST' || method === 'PUT') {
|
|
21
|
+
return await POST_SETTINGS(req, config);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Method not allowed
|
|
25
|
+
return NextResponse.json({ error: `Method ${method} not allowed for route: ${route || '/'}` }, { status: 405 });
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.error('[WebsiteApiRouter] Error:', error);
|
|
29
|
+
return NextResponse.json({ error: error.message || 'Internal server error' }, { status: 500 });
|
|
30
|
+
}
|
|
31
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Website - Client Entry Point
|
|
3
|
+
* Main settings management interface for website configuration
|
|
4
|
+
*/
|
|
5
|
+
export interface PluginProps {
|
|
6
|
+
subPath: string[];
|
|
7
|
+
siteId: string;
|
|
8
|
+
locale: string;
|
|
9
|
+
}
|
|
10
|
+
export default function WebsitePlugin({ subPath, siteId, locale }: PluginProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export { WebsitePlugin as Index };
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,WAAW,2CAS7E;AAGD,OAAO,EAAE,aAAa,IAAI,KAAK,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Website - Client Entry Point
|
|
3
|
+
* Main settings management interface for website configuration
|
|
4
|
+
*/
|
|
5
|
+
'use client';
|
|
6
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
7
|
+
import { SettingsView } from './views/SettingsView';
|
|
8
|
+
export default function WebsitePlugin({ subPath, siteId, locale }) {
|
|
9
|
+
const route = subPath[0] || 'settings';
|
|
10
|
+
switch (route) {
|
|
11
|
+
case 'settings':
|
|
12
|
+
return _jsx(SettingsView, { siteId: siteId, locale: locale });
|
|
13
|
+
default:
|
|
14
|
+
return _jsx(SettingsView, { siteId: siteId, locale: locale });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// Export for use as default
|
|
18
|
+
export { WebsitePlugin as Index };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
/**
|
|
3
|
+
* Plugin Website - Server-Only Entry Point
|
|
4
|
+
* This file exports only server-side API handlers
|
|
5
|
+
* Used by the dynamic plugin router via @jhits/plugin-website/server
|
|
6
|
+
*/
|
|
7
|
+
export { handleWebsiteApi as handleApi } from './api/router';
|
|
8
|
+
export { handleWebsiteApi } from './api/router';
|
|
9
|
+
export type { WebsiteApiConfig } from './types/settings';
|
|
10
|
+
//# sourceMappingURL=index.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.server.d.ts","sourceRoot":"","sources":["../src/index.server.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAErB;;;;GAIG;AAEH,OAAO,EAAE,gBAAgB,IAAI,SAAS,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
/**
|
|
3
|
+
* Plugin Website - Server-Only Entry Point
|
|
4
|
+
* This file exports only server-side API handlers
|
|
5
|
+
* Used by the dynamic plugin router via @jhits/plugin-website/server
|
|
6
|
+
*/
|
|
7
|
+
export { handleWebsiteApi as handleApi } from './api/router';
|
|
8
|
+
export { handleWebsiteApi } from './api/router'; // Keep original export for backward compatibility
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Website Settings Types
|
|
3
|
+
*/
|
|
4
|
+
export interface WebsiteSettings {
|
|
5
|
+
identifier: string;
|
|
6
|
+
siteName?: string;
|
|
7
|
+
siteTagline?: string;
|
|
8
|
+
siteDescription?: string;
|
|
9
|
+
keywords?: string;
|
|
10
|
+
contactEmail?: string;
|
|
11
|
+
phoneNumber?: string;
|
|
12
|
+
physicalAddress?: string;
|
|
13
|
+
smtpUser?: string;
|
|
14
|
+
maintenanceMode?: boolean;
|
|
15
|
+
launch_date?: string;
|
|
16
|
+
socials?: SocialLink[];
|
|
17
|
+
updatedAt?: Date;
|
|
18
|
+
createdAt?: Date;
|
|
19
|
+
}
|
|
20
|
+
export interface SocialLink {
|
|
21
|
+
id: number;
|
|
22
|
+
platform: string;
|
|
23
|
+
url: string;
|
|
24
|
+
}
|
|
25
|
+
export interface WebsiteApiConfig {
|
|
26
|
+
getDb: () => Promise<{
|
|
27
|
+
db: () => any;
|
|
28
|
+
}>;
|
|
29
|
+
getUserId?: (req: any) => Promise<string | null>;
|
|
30
|
+
mongoClient?: Promise<any>;
|
|
31
|
+
authOptions?: any;
|
|
32
|
+
jwtSecret?: string;
|
|
33
|
+
[key: string]: any;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/types/settings.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,eAAe;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,SAAS,CAAC,EAAE,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,GAAG,CAAA;KAAE,CAAC,CAAC;IACxC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACjD,WAAW,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3B,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Website Settings View
|
|
3
|
+
* Main interface for managing website settings
|
|
4
|
+
*/
|
|
5
|
+
export interface SettingsViewProps {
|
|
6
|
+
siteId: string;
|
|
7
|
+
locale: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function SettingsView({ siteId, locale }: SettingsViewProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
//# sourceMappingURL=SettingsView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SettingsView.d.ts","sourceRoot":"","sources":["../../src/views/SettingsView.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAmBH,MAAM,WAAW,iBAAiB;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,iBAAiB,2CA2ejE"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Website Settings View
|
|
3
|
+
* Main interface for managing website settings
|
|
4
|
+
*/
|
|
5
|
+
'use client';
|
|
6
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
7
|
+
import { useState, useEffect, useRef } from 'react';
|
|
8
|
+
import { Save, RefreshCw, Globe, Mail, Search, Settings2, Calendar } from 'lucide-react';
|
|
9
|
+
import { FaFacebook, FaInstagram, FaLinkedin, FaPinterest, FaTiktok } from 'react-icons/fa';
|
|
10
|
+
import { FaXTwitter } from 'react-icons/fa6';
|
|
11
|
+
const AVAILABLE_PLATFORMS = [
|
|
12
|
+
{ name: 'Instagram', icon: _jsx(FaInstagram, { size: 18 }) },
|
|
13
|
+
{ name: 'Facebook', icon: _jsx(FaFacebook, { size: 18 }) },
|
|
14
|
+
{ name: 'Twitter', icon: _jsx(FaXTwitter, { size: 18 }) },
|
|
15
|
+
{ name: 'Pinterest', icon: _jsx(FaPinterest, { size: 18 }) },
|
|
16
|
+
{ name: 'LinkedIn', icon: _jsx(FaLinkedin, { size: 18 }) },
|
|
17
|
+
{ name: 'TikTok', icon: _jsx(FaTiktok, { size: 18 }) },
|
|
18
|
+
];
|
|
19
|
+
export function SettingsView({ siteId, locale }) {
|
|
20
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
21
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
22
|
+
const [showSuccess, setShowSuccess] = useState(false);
|
|
23
|
+
const [settings, setSettings] = useState({
|
|
24
|
+
identifier: 'site_config',
|
|
25
|
+
siteName: '',
|
|
26
|
+
siteTagline: '',
|
|
27
|
+
siteDescription: '',
|
|
28
|
+
keywords: '',
|
|
29
|
+
contactEmail: '',
|
|
30
|
+
phoneNumber: '',
|
|
31
|
+
physicalAddress: '',
|
|
32
|
+
smtpUser: '',
|
|
33
|
+
maintenanceMode: false,
|
|
34
|
+
launch_date: '',
|
|
35
|
+
socials: [],
|
|
36
|
+
});
|
|
37
|
+
// Fetch settings on load
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const fetchSettings = async () => {
|
|
40
|
+
try {
|
|
41
|
+
setIsLoading(true);
|
|
42
|
+
const response = await fetch('/api/plugin-website/settings', {
|
|
43
|
+
credentials: 'include',
|
|
44
|
+
});
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
throw new Error('Failed to fetch settings');
|
|
47
|
+
}
|
|
48
|
+
const data = await response.json();
|
|
49
|
+
if (data.siteName !== undefined) {
|
|
50
|
+
// Convert launch_date to datetime-local format if it exists
|
|
51
|
+
let launchDate = '';
|
|
52
|
+
if (data.launch_date) {
|
|
53
|
+
// If it's a Date object or ISO string, convert to datetime-local format
|
|
54
|
+
const date = new Date(data.launch_date);
|
|
55
|
+
if (!isNaN(date.getTime())) {
|
|
56
|
+
// Format as YYYY-MM-DDTHH:mm for datetime-local input (24-hour format)
|
|
57
|
+
const year = date.getFullYear();
|
|
58
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
59
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
60
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
61
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
62
|
+
launchDate = `${year}-${month}-${day}T${hours}:${minutes}`;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// If it's already a string in the correct format, use it
|
|
66
|
+
// Ensure it has time component, default to 00:00 if missing
|
|
67
|
+
if (data.launch_date.includes('T') && data.launch_date.split('T')[1]) {
|
|
68
|
+
launchDate = data.launch_date;
|
|
69
|
+
}
|
|
70
|
+
else if (data.launch_date.includes('T')) {
|
|
71
|
+
launchDate = `${data.launch_date.split('T')[0]}T00:00`;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
launchDate = `${data.launch_date}T00:00`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
setSettings({
|
|
79
|
+
identifier: 'site_config',
|
|
80
|
+
siteName: data.siteName || '',
|
|
81
|
+
siteTagline: data.siteTagline || '',
|
|
82
|
+
siteDescription: data.siteDescription || '',
|
|
83
|
+
keywords: data.keywords || '',
|
|
84
|
+
contactEmail: data.contactEmail || '',
|
|
85
|
+
phoneNumber: data.phoneNumber || '',
|
|
86
|
+
physicalAddress: data.physicalAddress || '',
|
|
87
|
+
smtpUser: data.smtpUser || '',
|
|
88
|
+
maintenanceMode: data.maintenanceMode ?? false,
|
|
89
|
+
launch_date: launchDate,
|
|
90
|
+
socials: data.socials || [],
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error('Failed to load settings:', error);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
setIsLoading(false);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
fetchSettings();
|
|
102
|
+
}, []);
|
|
103
|
+
// Use ref to always get latest state
|
|
104
|
+
const settingsRef = useRef(settings);
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
settingsRef.current = settings;
|
|
107
|
+
}, [settings]);
|
|
108
|
+
// Save settings
|
|
109
|
+
const handleSave = async () => {
|
|
110
|
+
try {
|
|
111
|
+
setIsSaving(true);
|
|
112
|
+
// Get the latest state from ref
|
|
113
|
+
const currentSettings = settingsRef.current;
|
|
114
|
+
// Prepare settings for API - ensure launch_date is properly formatted
|
|
115
|
+
const settingsToSave = {
|
|
116
|
+
...currentSettings,
|
|
117
|
+
};
|
|
118
|
+
// Handle launch_date - keep it if it has a value, otherwise don't include it
|
|
119
|
+
if (currentSettings.launch_date && currentSettings.launch_date.trim() !== '') {
|
|
120
|
+
settingsToSave.launch_date = currentSettings.launch_date;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// Don't include launch_date if it's empty
|
|
124
|
+
delete settingsToSave.launch_date;
|
|
125
|
+
}
|
|
126
|
+
console.log('[SettingsView] Saving settings:', {
|
|
127
|
+
launch_date: settingsToSave.launch_date,
|
|
128
|
+
launch_date_type: typeof settingsToSave.launch_date,
|
|
129
|
+
has_launch_date: 'launch_date' in settingsToSave,
|
|
130
|
+
currentState_launch_date: currentSettings.launch_date,
|
|
131
|
+
all_keys: Object.keys(settingsToSave),
|
|
132
|
+
});
|
|
133
|
+
const response = await fetch('/api/plugin-website/settings', {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: { 'Content-Type': 'application/json' },
|
|
136
|
+
credentials: 'include',
|
|
137
|
+
body: JSON.stringify(settingsToSave),
|
|
138
|
+
});
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
const error = await response.json();
|
|
141
|
+
throw new Error(error.error || 'Failed to save settings');
|
|
142
|
+
}
|
|
143
|
+
setShowSuccess(true);
|
|
144
|
+
setTimeout(() => setShowSuccess(false), 3000);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
console.error('Failed to save settings:', error);
|
|
148
|
+
alert(error.message || 'Failed to save settings');
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
setIsSaving(false);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
// Add social link
|
|
155
|
+
const handleAddSocial = () => {
|
|
156
|
+
const newId = settings.socials?.length ? Math.max(...settings.socials.map(s => s.id)) + 1 : 1;
|
|
157
|
+
setSettings({
|
|
158
|
+
...settings,
|
|
159
|
+
socials: [...(settings.socials || []), { id: newId, platform: '', url: '' }],
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
// Update social link
|
|
163
|
+
const handleUpdateSocial = (id, field, value) => {
|
|
164
|
+
setSettings({
|
|
165
|
+
...settings,
|
|
166
|
+
socials: settings.socials?.map(social => social.id === id ? { ...social, [field]: value } : social) || [],
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
// Remove social link
|
|
170
|
+
const handleRemoveSocial = (id) => {
|
|
171
|
+
setSettings({
|
|
172
|
+
...settings,
|
|
173
|
+
socials: settings.socials?.filter(social => social.id !== id) || [],
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
if (isLoading) {
|
|
177
|
+
return (_jsx("div", { className: "h-full w-full bg-dashboard-card text-dashboard-text flex items-center justify-center", children: _jsxs("div", { className: "text-center", children: [_jsx(RefreshCw, { className: "w-8 h-8 animate-spin text-primary mx-auto mb-4" }), _jsx("p", { className: "text-sm text-dashboard-text-secondary", children: "Loading settings..." })] }) }));
|
|
178
|
+
}
|
|
179
|
+
return (_jsx("div", { className: "h-full w-full rounded-[2.5rem] bg-dashboard-card p-8 overflow-y-auto", children: _jsxs("div", { className: "max-w-6xl mx-auto", children: [_jsxs("div", { className: "flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-black text-dashboard-text uppercase tracking-tighter mb-2", children: "Website Settings" }), _jsx("p", { className: "text-sm text-dashboard-text-secondary", children: "Manage your website identity, contact information, and social links" })] }), _jsx("button", { onClick: handleSave, disabled: isSaving, className: `inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-all shadow-lg shadow-primary/20 ${isSaving
|
|
180
|
+
? 'bg-neutral-400 text-white cursor-not-allowed'
|
|
181
|
+
: showSuccess
|
|
182
|
+
? 'bg-green-600 text-white'
|
|
183
|
+
: 'bg-primary text-white hover:bg-primary/90'}`, children: isSaving ? (_jsxs(_Fragment, { children: [_jsx(RefreshCw, { className: "w-4 h-4 animate-spin" }), "Saving..."] })) : showSuccess ? (_jsxs(_Fragment, { children: [_jsx(Settings2, { className: "w-4 h-4" }), "Saved!"] })) : (_jsxs(_Fragment, { children: [_jsx(Save, { className: "w-4 h-4" }), "Save Settings"] })) })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-3 gap-8", children: [_jsxs("div", { className: "lg:col-span-2 space-y-8", children: [_jsxs("section", { className: "bg-dashboard-bg p-8 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6", children: [_jsx(Search, { size: 20, className: "text-primary" }), "Website Identity & SEO"] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Website Name" }), _jsx("input", { type: "text", value: settings.siteName || '', onChange: (e) => setSettings({ ...settings, siteName: e.target.value }), placeholder: "e.g., Botanics & You", className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Website Tagline" }), _jsx("input", { type: "text", value: settings.siteTagline || '', onChange: (e) => setSettings({ ...settings, siteTagline: e.target.value }), placeholder: "e.g., Sharing my knowledge with you", className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text" })] })] }), _jsxs("div", { className: "mt-6 space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Website Description" }), _jsx("textarea", { value: settings.siteDescription || '', onChange: (e) => setSettings({ ...settings, siteDescription: e.target.value }), placeholder: "A brief description of your website", rows: 3, className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text resize-none" })] }), _jsxs("div", { className: "mt-6 space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Keywords (comma-separated)" }), _jsx("input", { type: "text", value: settings.keywords || '', onChange: (e) => setSettings({ ...settings, keywords: e.target.value }), placeholder: "keyword1, keyword2, keyword3", className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text" })] })] }), _jsxs("section", { className: "bg-dashboard-bg p-8 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6", children: [_jsx(Mail, { size: 20, className: "text-primary" }), "Contact Information"] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Contact Email" }), _jsx("input", { type: "email", value: settings.contactEmail || '', onChange: (e) => setSettings({ ...settings, contactEmail: e.target.value }), placeholder: "contact@example.com", className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Phone Number" }), _jsx("input", { type: "tel", value: settings.phoneNumber || '', onChange: (e) => setSettings({ ...settings, phoneNumber: e.target.value }), placeholder: "+31 6 12345678", className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text" })] })] }), _jsxs("div", { className: "mt-6 space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Physical Address" }), _jsx("textarea", { value: settings.physicalAddress || '', onChange: (e) => setSettings({ ...settings, physicalAddress: e.target.value }), placeholder: "Street address, City, Country", rows: 2, className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text resize-none" })] })] }), _jsxs("section", { className: "bg-dashboard-bg p-8 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-dashboard-border pb-4 mb-6", children: [_jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text", children: [_jsx(Globe, { size: 20, className: "text-primary" }), "Social Links"] }), _jsx("button", { onClick: handleAddSocial, className: "px-4 py-2 text-xs font-bold uppercase tracking-widest bg-primary text-white rounded-full hover:bg-primary/90 transition-all", children: "Add Social" })] }), _jsx("div", { className: "space-y-4", children: settings.socials && settings.socials.length > 0 ? (settings.socials.map((social) => {
|
|
184
|
+
const platform = AVAILABLE_PLATFORMS.find(p => p.name === social.platform);
|
|
185
|
+
return (_jsxs("div", { className: "flex items-center gap-4 p-4 bg-dashboard-card rounded-2xl border border-dashboard-border", children: [_jsx("div", { className: "flex-shrink-0 text-dashboard-text-secondary", children: platform?.icon || _jsx(Globe, { size: 18 }) }), _jsxs("select", { value: social.platform, onChange: (e) => handleUpdateSocial(social.id, 'platform', e.target.value), className: "flex-1 px-4 py-2 bg-dashboard-bg border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text", children: [_jsx("option", { value: "", children: "Select Platform" }), AVAILABLE_PLATFORMS.map(p => (_jsx("option", { value: p.name, children: p.name }, p.name)))] }), _jsx("input", { type: "url", value: social.url, onChange: (e) => handleUpdateSocial(social.id, 'url', e.target.value), placeholder: "https://...", className: "flex-1 px-4 py-2 bg-dashboard-bg border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text" }), _jsx("button", { onClick: () => handleRemoveSocial(social.id), className: "px-4 py-2 text-xs font-bold text-red-500 hover:text-red-700 dark:hover:text-red-400 transition-colors", children: "Remove" })] }, social.id));
|
|
186
|
+
})) : (_jsx("p", { className: "text-sm text-dashboard-text-secondary text-center py-8", children: "No social links added yet. Click \"Add Social\" to get started." })) })] })] }), _jsxs("div", { className: "space-y-8", children: [_jsxs("section", { className: "bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6", children: [_jsx(Settings2, { size: 18, className: "text-primary" }), "Maintenance"] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text block mb-1", children: "Maintenance Mode" }), _jsx("p", { className: "text-[10px] text-dashboard-text-secondary", children: "Show maintenance page to visitors" })] }), _jsx("button", { onClick: () => setSettings({ ...settings, maintenanceMode: !settings.maintenanceMode }), className: `relative w-12 h-6 rounded-full transition-colors ${settings.maintenanceMode ? 'bg-primary' : 'bg-neutral-200 dark:bg-neutral-700'}`, children: _jsx("div", { className: `absolute top-1 left-1 w-4 h-4 bg-white rounded-full transition-transform duration-200 ${settings.maintenanceMode ? 'translate-x-6' : 'translate-x-0'}` }) })] })] }), _jsxs("section", { className: "bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6", children: [_jsx(Calendar, { size: 18, className: "text-primary" }), "Launch Date"] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text block", children: "Date" }), _jsx("input", { type: "date", value: settings.launch_date ? settings.launch_date.split('T')[0] : '', onChange: (e) => {
|
|
187
|
+
const dateValue = e.target.value;
|
|
188
|
+
const currentTime = settings.launch_date?.includes('T')
|
|
189
|
+
? settings.launch_date.split('T')[1]
|
|
190
|
+
: '00:00';
|
|
191
|
+
const newLaunchDate = dateValue ? `${dateValue}T${currentTime}` : '';
|
|
192
|
+
console.log('[SettingsView] Date changed:', {
|
|
193
|
+
dateValue,
|
|
194
|
+
currentTime,
|
|
195
|
+
newLaunchDate,
|
|
196
|
+
});
|
|
197
|
+
setSettings(prev => ({
|
|
198
|
+
...prev,
|
|
199
|
+
launch_date: newLaunchDate,
|
|
200
|
+
}));
|
|
201
|
+
}, className: "w-full px-4 py-2 bg-dashboard-card border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text block", children: "Time (24-hour format)" }), _jsx("input", { type: "time", step: "60", value: settings.launch_date?.includes('T')
|
|
202
|
+
? settings.launch_date.split('T')[1]
|
|
203
|
+
: '00:00', onChange: (e) => {
|
|
204
|
+
const timeValue = e.target.value;
|
|
205
|
+
const currentDate = settings.launch_date?.includes('T')
|
|
206
|
+
? settings.launch_date.split('T')[0]
|
|
207
|
+
: new Date().toISOString().split('T')[0];
|
|
208
|
+
const newLaunchDate = timeValue ? `${currentDate}T${timeValue}` : '';
|
|
209
|
+
console.log('[SettingsView] Time changed:', {
|
|
210
|
+
timeValue,
|
|
211
|
+
currentDate,
|
|
212
|
+
newLaunchDate,
|
|
213
|
+
});
|
|
214
|
+
setSettings(prev => ({
|
|
215
|
+
...prev,
|
|
216
|
+
launch_date: newLaunchDate,
|
|
217
|
+
}));
|
|
218
|
+
}, className: "w-full px-4 py-2 bg-dashboard-card border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text" })] }), _jsx("p", { className: "text-[10px] text-dashboard-text-secondary", children: "Used for countdown timers. Time defaults to 00:00 if not specified." })] })] })] })] })] }) }));
|
|
219
|
+
}
|
package/package.json
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhits/plugin-website",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Website management and configuration plugin for the JHITS ecosystem",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
|
-
"main": "./
|
|
9
|
-
"types": "./
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
|
-
"types": "./
|
|
13
|
-
"default": "./
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
14
|
},
|
|
15
15
|
"./server": {
|
|
16
|
-
"types": "./
|
|
17
|
-
"default": "./
|
|
16
|
+
"types": "./dist/index.server.d.ts",
|
|
17
|
+
"default": "./dist/index.server.js"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc"
|
|
22
|
+
},
|
|
20
23
|
"dependencies": {
|
|
21
|
-
"@jhits/plugin-core": "^0.0.
|
|
22
|
-
"lucide-react": "^0.
|
|
23
|
-
"mongodb": "^7.
|
|
24
|
+
"@jhits/plugin-core": "^0.0.2",
|
|
25
|
+
"lucide-react": "^0.564.0",
|
|
26
|
+
"mongodb": "^7.1.0",
|
|
24
27
|
"next-auth": "^4.24.13",
|
|
25
28
|
"react-icons": "^5.5.0"
|
|
26
29
|
},
|
|
@@ -30,18 +33,17 @@
|
|
|
30
33
|
"react-dom": ">=18.0.0"
|
|
31
34
|
},
|
|
32
35
|
"devDependencies": {
|
|
33
|
-
"@types/node": "^
|
|
34
|
-
"@types/react": "^19",
|
|
35
|
-
"@types/react-dom": "^19",
|
|
36
|
-
"next": "16.1.
|
|
37
|
-
"react": "19.2.
|
|
38
|
-
"react-dom": "19.2.
|
|
39
|
-
"typescript": "^5"
|
|
36
|
+
"@types/node": "^25.2.3",
|
|
37
|
+
"@types/react": "^19.2.14",
|
|
38
|
+
"@types/react-dom": "^19.2.3",
|
|
39
|
+
"next": "16.1.6",
|
|
40
|
+
"react": "19.2.4",
|
|
41
|
+
"react-dom": "19.2.4",
|
|
42
|
+
"typescript": "^5.9.3"
|
|
40
43
|
},
|
|
41
44
|
"files": [
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"!src/**/README.md",
|
|
45
|
+
"dist",
|
|
46
|
+
"src",
|
|
45
47
|
"package.json"
|
|
46
48
|
]
|
|
47
49
|
}
|
|
@@ -196,33 +196,33 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
|
|
|
196
196
|
<div className="h-full w-full bg-dashboard-card text-dashboard-text flex items-center justify-center">
|
|
197
197
|
<div className="text-center">
|
|
198
198
|
<RefreshCw className="w-8 h-8 animate-spin text-primary mx-auto mb-4" />
|
|
199
|
-
<p className="text-sm text-
|
|
199
|
+
<p className="text-sm text-dashboard-text-secondary">Loading settings...</p>
|
|
200
200
|
</div>
|
|
201
201
|
</div>
|
|
202
202
|
);
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
return (
|
|
206
|
-
<div className="h-full rounded-[2.5rem]
|
|
207
|
-
<div className="max-w-6xl mx-auto
|
|
206
|
+
<div className="h-full w-full rounded-[2.5rem] bg-dashboard-card p-8 overflow-y-auto">
|
|
207
|
+
<div className="max-w-6xl mx-auto">
|
|
208
208
|
{/* Header */}
|
|
209
|
-
<div className="flex items-center justify-between mb-8">
|
|
209
|
+
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8">
|
|
210
210
|
<div>
|
|
211
|
-
<h1 className="text-3xl font-black text-
|
|
211
|
+
<h1 className="text-3xl font-black text-dashboard-text uppercase tracking-tighter mb-2">
|
|
212
212
|
Website Settings
|
|
213
213
|
</h1>
|
|
214
|
-
<p className="text-sm text-
|
|
214
|
+
<p className="text-sm text-dashboard-text-secondary">
|
|
215
215
|
Manage your website identity, contact information, and social links
|
|
216
216
|
</p>
|
|
217
217
|
</div>
|
|
218
218
|
<button
|
|
219
219
|
onClick={handleSave}
|
|
220
220
|
disabled={isSaving}
|
|
221
|
-
className={`inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-all shadow-lg ${isSaving
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
221
|
+
className={`inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-all shadow-lg shadow-primary/20 ${isSaving
|
|
222
|
+
? 'bg-neutral-400 text-white cursor-not-allowed'
|
|
223
|
+
: showSuccess
|
|
224
|
+
? 'bg-green-600 text-white'
|
|
225
|
+
: 'bg-primary text-white hover:bg-primary/90'
|
|
226
226
|
}`}
|
|
227
227
|
>
|
|
228
228
|
{isSaving ? (
|
|
@@ -248,14 +248,14 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
|
|
|
248
248
|
{/* Main Content */}
|
|
249
249
|
<div className="lg:col-span-2 space-y-8">
|
|
250
250
|
{/* SEO & Website Identity */}
|
|
251
|
-
<section className="bg-dashboard-
|
|
252
|
-
<div className="flex items-center gap-2 font-bold text-
|
|
251
|
+
<section className="bg-dashboard-bg p-8 rounded-3xl border border-dashboard-border">
|
|
252
|
+
<div className="flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6">
|
|
253
253
|
<Search size={20} className="text-primary" />
|
|
254
254
|
Website Identity & SEO
|
|
255
255
|
</div>
|
|
256
256
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
257
257
|
<div className="space-y-2">
|
|
258
|
-
<label className="text-xs font-bold text-
|
|
258
|
+
<label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
|
|
259
259
|
Website Name
|
|
260
260
|
</label>
|
|
261
261
|
<input
|
|
@@ -267,7 +267,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
|
|
|
267
267
|
/>
|
|
268
268
|
</div>
|
|
269
269
|
<div className="space-y-2">
|
|
270
|
-
<label className="text-xs font-bold text-
|
|
270
|
+
<label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
|
|
271
271
|
Website Tagline
|
|
272
272
|
</label>
|
|
273
273
|
<input
|
|
@@ -280,7 +280,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
|
|
|
280
280
|
</div>
|
|
281
281
|
</div>
|
|
282
282
|
<div className="mt-6 space-y-2">
|
|
283
|
-
<label className="text-xs font-bold text-
|
|
283
|
+
<label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
|
|
284
284
|
Website Description
|
|
285
285
|
</label>
|
|
286
286
|
<textarea
|
|
@@ -292,7 +292,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
|
|
|
292
292
|
/>
|
|
293
293
|
</div>
|
|
294
294
|
<div className="mt-6 space-y-2">
|
|
295
|
-
<label className="text-xs font-bold text-
|
|
295
|
+
<label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
|
|
296
296
|
Keywords (comma-separated)
|
|
297
297
|
</label>
|
|
298
298
|
<input
|
|
@@ -306,14 +306,14 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
|
|
|
306
306
|
</section>
|
|
307
307
|
|
|
308
308
|
{/* Contact Information */}
|
|
309
|
-
<section className="bg-dashboard-
|
|
310
|
-
<div className="flex items-center gap-2 font-bold text-
|
|
309
|
+
<section className="bg-dashboard-bg p-8 rounded-3xl border border-dashboard-border">
|
|
310
|
+
<div className="flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6">
|
|
311
311
|
<Mail size={20} className="text-primary" />
|
|
312
312
|
Contact Information
|
|
313
313
|
</div>
|
|
314
314
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
315
315
|
<div className="space-y-2">
|
|
316
|
-
<label className="text-xs font-bold text-
|
|
316
|
+
<label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
|
|
317
317
|
Contact Email
|
|
318
318
|
</label>
|
|
319
319
|
<input
|
|
@@ -325,7 +325,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
|
|
|
325
325
|
/>
|
|
326
326
|
</div>
|
|
327
327
|
<div className="space-y-2">
|
|
328
|
-
<label className="text-xs font-bold text-
|
|
328
|
+
<label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
|
|
329
329
|
Phone Number
|
|
330
330
|
</label>
|
|
331
331
|
<input
|
|
@@ -338,7 +338,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
|
|
|
338
338
|
</div>
|
|
339
339
|
</div>
|
|
340
340
|
<div className="mt-6 space-y-2">
|
|
341
|
-
<label className="text-xs font-bold text-
|
|
341
|
+
<label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
|
|
342
342
|
Physical Address
|
|
343
343
|
</label>
|
|
344
344
|
<textarea
|
|
@@ -352,9 +352,9 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
|
|
|
352
352
|
</section>
|
|
353
353
|
|
|
354
354
|
{/* Social Links */}
|
|
355
|
-
<section className="bg-dashboard-
|
|
355
|
+
<section className="bg-dashboard-bg p-8 rounded-3xl border border-dashboard-border">
|
|
356
356
|
<div className="flex items-center justify-between border-b border-dashboard-border pb-4 mb-6">
|
|
357
|
-
<div className="flex items-center gap-2 font-bold text-
|
|
357
|
+
<div className="flex items-center gap-2 font-bold text-dashboard-text">
|
|
358
358
|
<Globe size={20} className="text-primary" />
|
|
359
359
|
Social Links
|
|
360
360
|
</div>
|
|
@@ -371,13 +371,13 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
|
|
|
371
371
|
const platform = AVAILABLE_PLATFORMS.find(p => p.name === social.platform);
|
|
372
372
|
return (
|
|
373
373
|
<div key={social.id} className="flex items-center gap-4 p-4 bg-dashboard-card rounded-2xl border border-dashboard-border">
|
|
374
|
-
<div className="flex-shrink-0 text-
|
|
374
|
+
<div className="flex-shrink-0 text-dashboard-text-secondary">
|
|
375
375
|
{platform?.icon || <Globe size={18} />}
|
|
376
376
|
</div>
|
|
377
377
|
<select
|
|
378
378
|
value={social.platform}
|
|
379
379
|
onChange={(e) => handleUpdateSocial(social.id, 'platform', e.target.value)}
|
|
380
|
-
className="flex-1 px-4 py-2 bg-dashboard-
|
|
380
|
+
className="flex-1 px-4 py-2 bg-dashboard-bg border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text"
|
|
381
381
|
>
|
|
382
382
|
<option value="">Select Platform</option>
|
|
383
383
|
{AVAILABLE_PLATFORMS.map(p => (
|
|
@@ -389,7 +389,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
|
|
|
389
389
|
value={social.url}
|
|
390
390
|
onChange={(e) => handleUpdateSocial(social.id, 'url', e.target.value)}
|
|
391
391
|
placeholder="https://..."
|
|
392
|
-
className="flex-1 px-4 py-2 bg-dashboard-
|
|
392
|
+
className="flex-1 px-4 py-2 bg-dashboard-bg border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text"
|
|
393
393
|
/>
|
|
394
394
|
<button
|
|
395
395
|
onClick={() => handleRemoveSocial(social.id)}
|
|
@@ -401,7 +401,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
|
|
|
401
401
|
);
|
|
402
402
|
})
|
|
403
403
|
) : (
|
|
404
|
-
<p className="text-sm text-
|
|
404
|
+
<p className="text-sm text-dashboard-text-secondary text-center py-8">
|
|
405
405
|
No social links added yet. Click "Add Social" to get started.
|
|
406
406
|
</p>
|
|
407
407
|
)}
|
|
@@ -412,40 +412,41 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
|
|
|
412
412
|
{/* Sidebar */}
|
|
413
413
|
<div className="space-y-8">
|
|
414
414
|
{/* Maintenance Mode */}
|
|
415
|
-
<section className="bg-dashboard-
|
|
416
|
-
<div className="flex items-center gap-2 font-bold text-
|
|
415
|
+
<section className="bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border">
|
|
416
|
+
<div className="flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6">
|
|
417
417
|
<Settings2 size={18} className="text-primary" />
|
|
418
418
|
Maintenance
|
|
419
419
|
</div>
|
|
420
420
|
<div className="flex items-center justify-between">
|
|
421
421
|
<div>
|
|
422
|
-
<label className="text-xs font-bold text-
|
|
422
|
+
<label className="text-xs font-bold text-dashboard-text block mb-1">
|
|
423
423
|
Maintenance Mode
|
|
424
424
|
</label>
|
|
425
|
-
<p className="text-[10px] text-
|
|
425
|
+
<p className="text-[10px] text-dashboard-text-secondary">
|
|
426
426
|
Show maintenance page to visitors
|
|
427
427
|
</p>
|
|
428
428
|
</div>
|
|
429
429
|
<button
|
|
430
430
|
onClick={() => setSettings({ ...settings, maintenanceMode: !settings.maintenanceMode })}
|
|
431
|
-
className={`relative w-12 h-6 rounded-full transition-colors ${settings.maintenanceMode ? 'bg-primary' : 'bg-neutral-
|
|
431
|
+
className={`relative w-12 h-6 rounded-full transition-colors ${settings.maintenanceMode ? 'bg-primary' : 'bg-neutral-200 dark:bg-neutral-700'
|
|
432
432
|
}`}
|
|
433
433
|
>
|
|
434
|
-
<div
|
|
435
|
-
}`}
|
|
434
|
+
<div
|
|
435
|
+
className={`absolute top-1 left-1 w-4 h-4 bg-white rounded-full transition-transform duration-200 ${settings.maintenanceMode ? 'translate-x-6' : 'translate-x-0'}`}
|
|
436
|
+
/>
|
|
436
437
|
</button>
|
|
437
438
|
</div>
|
|
438
439
|
</section>
|
|
439
440
|
|
|
440
441
|
{/* Launch Date */}
|
|
441
|
-
<section className="bg-dashboard-
|
|
442
|
-
<div className="flex items-center gap-2 font-bold text-
|
|
442
|
+
<section className="bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border">
|
|
443
|
+
<div className="flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6">
|
|
443
444
|
<Calendar size={18} className="text-primary" />
|
|
444
445
|
Launch Date
|
|
445
446
|
</div>
|
|
446
447
|
<div className="space-y-4">
|
|
447
448
|
<div className="space-y-2">
|
|
448
|
-
<label className="text-xs font-bold text-
|
|
449
|
+
<label className="text-xs font-bold text-dashboard-text block">
|
|
449
450
|
Date
|
|
450
451
|
</label>
|
|
451
452
|
<input
|
|
@@ -474,7 +475,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
|
|
|
474
475
|
/>
|
|
475
476
|
</div>
|
|
476
477
|
<div className="space-y-2">
|
|
477
|
-
<label className="text-xs font-bold text-
|
|
478
|
+
<label className="text-xs font-bold text-dashboard-text block">
|
|
478
479
|
Time (24-hour format)
|
|
479
480
|
</label>
|
|
480
481
|
<input
|
|
@@ -505,7 +506,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
|
|
|
505
506
|
className="w-full px-4 py-2 bg-dashboard-card border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text"
|
|
506
507
|
/>
|
|
507
508
|
</div>
|
|
508
|
-
<p className="text-[10px] text-
|
|
509
|
+
<p className="text-[10px] text-dashboard-text-secondary">
|
|
509
510
|
Used for countdown timers. Time defaults to 00:00 if not specified.
|
|
510
511
|
</p>
|
|
511
512
|
</div>
|