@djangocfg/layouts 2.1.10 → 2.1.14
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/README.md +53 -161
- package/package.json +6 -6
- package/src/components/RedirectPage/RedirectPage.tsx +1 -1
- package/src/index.ts +0 -6
- package/src/layouts/AppLayout/AppLayout.tsx +1 -1
- package/src/layouts/AppLayout/BaseApp.tsx +1 -1
- package/src/layouts/AuthLayout/AuthContext.tsx +1 -1
- package/src/layouts/AuthLayout/OAuthCallback.tsx +1 -1
- package/src/layouts/AuthLayout/OAuthProviders.tsx +1 -1
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +1 -1
- package/src/layouts/PrivateLayout/components/PrivateHeader.tsx +1 -1
- package/src/layouts/ProfileLayout/components/AvatarSection.tsx +2 -2
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +1 -1
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +1 -1
- package/src/layouts/_components/UserMenu.tsx +1 -1
- package/src/layouts/index.ts +0 -2
- package/src/snippets/Analytics/useAnalytics.ts +1 -1
- package/src/snippets/index.ts +0 -3
- package/src/auth/README.md +0 -962
- package/src/auth/context/AccountsContext.tsx +0 -240
- package/src/auth/context/AuthContext.tsx +0 -604
- package/src/auth/context/index.ts +0 -4
- package/src/auth/context/types.ts +0 -68
- package/src/auth/hooks/index.ts +0 -17
- package/src/auth/hooks/useAuthForm.ts +0 -332
- package/src/auth/hooks/useAuthGuard.ts +0 -25
- package/src/auth/hooks/useAuthRedirect.ts +0 -51
- package/src/auth/hooks/useAutoAuth.ts +0 -49
- package/src/auth/hooks/useGithubAuth.ts +0 -184
- package/src/auth/hooks/useLocalStorage.ts +0 -214
- package/src/auth/hooks/useProfileCache.ts +0 -146
- package/src/auth/hooks/useSessionStorage.ts +0 -189
- package/src/auth/index.ts +0 -10
- package/src/auth/middlewares/index.ts +0 -1
- package/src/auth/middlewares/proxy.ts +0 -32
- package/src/auth/server.ts +0 -6
- package/src/auth/utils/errors.ts +0 -34
- package/src/auth/utils/index.ts +0 -2
- package/src/auth/utils/validation.ts +0 -14
- package/src/contexts/LeadsContext.tsx +0 -156
- package/src/contexts/NewsletterContext.tsx +0 -263
- package/src/contexts/SupportContext.tsx +0 -256
- package/src/contexts/index.ts +0 -59
- package/src/contexts/knowbase/ChatContext.tsx +0 -174
- package/src/contexts/knowbase/DocumentsContext.tsx +0 -304
- package/src/contexts/knowbase/SessionsContext.tsx +0 -174
- package/src/contexts/knowbase/index.ts +0 -61
- package/src/contexts/payments/BalancesContext.tsx +0 -65
- package/src/contexts/payments/CurrenciesContext.tsx +0 -66
- package/src/contexts/payments/OverviewContext.tsx +0 -174
- package/src/contexts/payments/PaymentsContext.tsx +0 -132
- package/src/contexts/payments/README.md +0 -201
- package/src/contexts/payments/RootPaymentsContext.tsx +0 -68
- package/src/contexts/payments/index.ts +0 -50
- package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +0 -92
- package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +0 -291
- package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +0 -290
- package/src/layouts/PaymentsLayout/components/index.ts +0 -2
- package/src/layouts/PaymentsLayout/events.ts +0 -47
- package/src/layouts/PaymentsLayout/index.ts +0 -16
- package/src/layouts/PaymentsLayout/types.ts +0 -6
- package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +0 -128
- package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +0 -142
- package/src/layouts/PaymentsLayout/views/overview/components/index.ts +0 -2
- package/src/layouts/PaymentsLayout/views/overview/index.tsx +0 -20
- package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +0 -276
- package/src/layouts/PaymentsLayout/views/payments/components/index.ts +0 -1
- package/src/layouts/PaymentsLayout/views/payments/index.tsx +0 -17
- package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +0 -273
- package/src/layouts/PaymentsLayout/views/transactions/components/index.ts +0 -1
- package/src/layouts/PaymentsLayout/views/transactions/index.tsx +0 -17
- package/src/layouts/SupportLayout/README.md +0 -91
- package/src/layouts/SupportLayout/SupportLayout.tsx +0 -179
- package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +0 -155
- package/src/layouts/SupportLayout/components/MessageInput.tsx +0 -92
- package/src/layouts/SupportLayout/components/MessageList.tsx +0 -314
- package/src/layouts/SupportLayout/components/TicketCard.tsx +0 -96
- package/src/layouts/SupportLayout/components/TicketList.tsx +0 -153
- package/src/layouts/SupportLayout/components/index.ts +0 -6
- package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +0 -263
- package/src/layouts/SupportLayout/context/index.ts +0 -2
- package/src/layouts/SupportLayout/events.ts +0 -33
- package/src/layouts/SupportLayout/hooks/index.ts +0 -2
- package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +0 -119
- package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +0 -92
- package/src/layouts/SupportLayout/index.ts +0 -8
- package/src/layouts/SupportLayout/types.ts +0 -21
- package/src/snippets/Chat/ChatUIContext.tsx +0 -110
- package/src/snippets/Chat/ChatWidget.tsx +0 -476
- package/src/snippets/Chat/README.md +0 -122
- package/src/snippets/Chat/components/MessageInput.tsx +0 -124
- package/src/snippets/Chat/components/MessageList.tsx +0 -169
- package/src/snippets/Chat/components/SessionList.tsx +0 -192
- package/src/snippets/Chat/components/index.ts +0 -9
- package/src/snippets/Chat/hooks/index.ts +0 -6
- package/src/snippets/Chat/hooks/useInfiniteSessions.ts +0 -82
- package/src/snippets/Chat/index.tsx +0 -45
- package/src/snippets/Chat/types.ts +0 -80
- package/src/snippets/ContactForm/ContactForm.tsx +0 -346
- package/src/snippets/ContactForm/ContactFormProvider.tsx +0 -153
- package/src/snippets/ContactForm/ContactInfo.tsx +0 -114
- package/src/snippets/ContactForm/ContactPage.tsx +0 -131
- package/src/snippets/ContactForm/dynamic.tsx +0 -55
- package/src/snippets/ContactForm/index.ts +0 -34
- package/src/snippets/ContactForm/types.ts +0 -110
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { createContext, useContext, useState, useCallback, useEffect } from 'react';
|
|
4
|
-
import { configureAPI, createLeadsSubmitCreate, Enums } from '@djangocfg/api';
|
|
5
|
-
import type {
|
|
6
|
-
ContactFormContextValue,
|
|
7
|
-
ContactFormProviderProps,
|
|
8
|
-
LeadSubmissionData,
|
|
9
|
-
LeadSubmissionResult,
|
|
10
|
-
} from './types';
|
|
11
|
-
|
|
12
|
-
// ============================================================================
|
|
13
|
-
// Context
|
|
14
|
-
// ============================================================================
|
|
15
|
-
|
|
16
|
-
const ContactFormContext = createContext<ContactFormContextValue | undefined>(undefined);
|
|
17
|
-
|
|
18
|
-
// ============================================================================
|
|
19
|
-
// Helpers
|
|
20
|
-
// ============================================================================
|
|
21
|
-
|
|
22
|
-
const CONTACT_TYPE_MAP: Record<string, Enums.LeadSubmissionRequestContactType> = {
|
|
23
|
-
email: Enums.LeadSubmissionRequestContactType.EMAIL,
|
|
24
|
-
telegram: Enums.LeadSubmissionRequestContactType.TELEGRAM,
|
|
25
|
-
whatsapp: Enums.LeadSubmissionRequestContactType.WHATSAPP,
|
|
26
|
-
phone: Enums.LeadSubmissionRequestContactType.PHONE,
|
|
27
|
-
other: Enums.LeadSubmissionRequestContactType.OTHER,
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
// ============================================================================
|
|
31
|
-
// Provider
|
|
32
|
-
// ============================================================================
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* ContactFormProvider - Configures API and provides submit functionality
|
|
36
|
-
*
|
|
37
|
-
* Uses the generated Django-CFG API client for lead submission.
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* ```tsx
|
|
41
|
-
* import { ContactFormProvider, ContactForm } from '@djangocfg/layouts';
|
|
42
|
-
*
|
|
43
|
-
* <ContactFormProvider apiUrl="https://api.example.com">
|
|
44
|
-
* <ContactForm fields={fields} />
|
|
45
|
-
* </ContactFormProvider>
|
|
46
|
-
* ```
|
|
47
|
-
*/
|
|
48
|
-
export function ContactFormProvider({ children, apiUrl }: ContactFormProviderProps) {
|
|
49
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
50
|
-
const [error, setError] = useState<Error | null>(null);
|
|
51
|
-
const [lastResponse, setLastResponse] = useState<LeadSubmissionResult | null>(null);
|
|
52
|
-
|
|
53
|
-
// Always use local API route to avoid CORS issues
|
|
54
|
-
// Pass apiUrl in request body so route.ts knows where to proxy
|
|
55
|
-
|
|
56
|
-
// Submit function - always uses local API route
|
|
57
|
-
const submit = useCallback(async (data: LeadSubmissionData): Promise<LeadSubmissionResult> => {
|
|
58
|
-
setIsSubmitting(true);
|
|
59
|
-
setError(null);
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
// Always use local Next.js API route to avoid CORS issues
|
|
63
|
-
// Pass apiUrl from ContactPage in the request body so route.ts knows where to proxy
|
|
64
|
-
const res = await fetch('/api/contact', {
|
|
65
|
-
method: 'POST',
|
|
66
|
-
headers: {
|
|
67
|
-
'Content-Type': 'application/json',
|
|
68
|
-
},
|
|
69
|
-
body: JSON.stringify({
|
|
70
|
-
_apiUrl: apiUrl || '', // Pass apiUrl to route.ts for proxying (can be empty for fallback)
|
|
71
|
-
name: data.name,
|
|
72
|
-
email: data.email,
|
|
73
|
-
message: data.message,
|
|
74
|
-
company: data.company,
|
|
75
|
-
company_site: data.company_site,
|
|
76
|
-
contact_type: data.contact_type,
|
|
77
|
-
contact_value: data.contact_value,
|
|
78
|
-
subject: data.subject,
|
|
79
|
-
extra: data.extra,
|
|
80
|
-
site_url: data.site_url || (typeof window !== 'undefined' ? window.location.origin : undefined),
|
|
81
|
-
}),
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
if (!res.ok) {
|
|
85
|
-
const errorData = await res.json().catch(() => ({ message: 'Failed to submit contact form' }));
|
|
86
|
-
throw new Error(errorData.message || `HTTP ${res.status}`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const response: LeadSubmissionResult = await res.json();
|
|
90
|
-
|
|
91
|
-
const result: LeadSubmissionResult = {
|
|
92
|
-
success: response.success,
|
|
93
|
-
message: response.message,
|
|
94
|
-
lead_id: response.lead_id,
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
setLastResponse(result);
|
|
98
|
-
return result;
|
|
99
|
-
} catch (err) {
|
|
100
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
101
|
-
setError(error);
|
|
102
|
-
throw error;
|
|
103
|
-
} finally {
|
|
104
|
-
setIsSubmitting(false);
|
|
105
|
-
}
|
|
106
|
-
}, [apiUrl]);
|
|
107
|
-
|
|
108
|
-
// Reset error state
|
|
109
|
-
const resetError = useCallback(() => {
|
|
110
|
-
setError(null);
|
|
111
|
-
}, []);
|
|
112
|
-
|
|
113
|
-
const value: ContactFormContextValue = {
|
|
114
|
-
apiUrl: '/api/contact', // Always use local endpoint
|
|
115
|
-
submit,
|
|
116
|
-
isSubmitting,
|
|
117
|
-
error,
|
|
118
|
-
lastResponse,
|
|
119
|
-
resetError,
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
return (
|
|
123
|
-
<ContactFormContext.Provider value={value}>
|
|
124
|
-
{children}
|
|
125
|
-
</ContactFormContext.Provider>
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// ============================================================================
|
|
130
|
-
// Hooks
|
|
131
|
-
// ============================================================================
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* useContactForm - Access contact form context
|
|
135
|
-
*
|
|
136
|
-
* Must be used within ContactFormProvider
|
|
137
|
-
*/
|
|
138
|
-
export function useContactForm(): ContactFormContextValue {
|
|
139
|
-
const context = useContext(ContactFormContext);
|
|
140
|
-
if (!context) {
|
|
141
|
-
throw new Error('useContactForm must be used within ContactFormProvider');
|
|
142
|
-
}
|
|
143
|
-
return context;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* useContactFormOptional - Access contact form context (optional)
|
|
148
|
-
*
|
|
149
|
-
* Returns null if not within ContactFormProvider
|
|
150
|
-
*/
|
|
151
|
-
export function useContactFormOptional(): ContactFormContextValue | null {
|
|
152
|
-
return useContext(ContactFormContext) ?? null;
|
|
153
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import {
|
|
5
|
-
Card,
|
|
6
|
-
CardHeader,
|
|
7
|
-
CardTitle,
|
|
8
|
-
CardContent,
|
|
9
|
-
Button,
|
|
10
|
-
cn,
|
|
11
|
-
} from '@djangocfg/ui-nextjs';
|
|
12
|
-
import type { ContactInfoProps, ContactDetail } from './types';
|
|
13
|
-
import { DEFAULT_INFO_TITLE } from './types';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* ContactInfo - Display contact information with optional action card
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```tsx
|
|
20
|
-
* import { ContactInfo } from '@djangocfg/layouts';
|
|
21
|
-
* import { Mail, Phone, MapPin, Calendar } from 'lucide-react';
|
|
22
|
-
*
|
|
23
|
-
* const details = [
|
|
24
|
-
* { icon: <Mail />, label: 'Email', value: 'hello@example.com', href: 'mailto:hello@example.com' },
|
|
25
|
-
* { icon: <Phone />, label: 'Phone', value: '+1 234 567 890', href: 'tel:+1234567890' },
|
|
26
|
-
* { icon: <MapPin />, label: 'Address', value: '123 Main St, City' },
|
|
27
|
-
* ];
|
|
28
|
-
*
|
|
29
|
-
* <ContactInfo
|
|
30
|
-
* details={details}
|
|
31
|
-
* action={{
|
|
32
|
-
* title: 'Schedule a Meeting',
|
|
33
|
-
* description: 'Book a 30-minute call',
|
|
34
|
-
* button: {
|
|
35
|
-
* icon: <Calendar />,
|
|
36
|
-
* label: 'Book Now',
|
|
37
|
-
* href: 'https://calendly.com/...',
|
|
38
|
-
* },
|
|
39
|
-
* }}
|
|
40
|
-
* />
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
export function ContactInfo({
|
|
44
|
-
details,
|
|
45
|
-
title = DEFAULT_INFO_TITLE,
|
|
46
|
-
action,
|
|
47
|
-
className,
|
|
48
|
-
}: ContactInfoProps) {
|
|
49
|
-
const renderDetail = (detail: ContactDetail, index: number) => {
|
|
50
|
-
const content = (
|
|
51
|
-
<div className="flex items-start gap-3">
|
|
52
|
-
<div className="text-muted-foreground mt-0.5">{detail.icon}</div>
|
|
53
|
-
<div>
|
|
54
|
-
<p className="text-sm text-muted-foreground">{detail.label}</p>
|
|
55
|
-
<p className="font-medium">{detail.value}</p>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
if (detail.href) {
|
|
61
|
-
return (
|
|
62
|
-
<a
|
|
63
|
-
key={index}
|
|
64
|
-
href={detail.href}
|
|
65
|
-
target={detail.external !== false ? '_blank' : undefined}
|
|
66
|
-
rel={detail.external !== false ? 'noopener noreferrer' : undefined}
|
|
67
|
-
className="block hover:bg-muted/50 rounded-lg p-2 -m-2 transition-colors"
|
|
68
|
-
>
|
|
69
|
-
{content}
|
|
70
|
-
</a>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return <div key={index}>{content}</div>;
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<div className={cn('space-y-6', className)}>
|
|
79
|
-
{/* Contact Details Card */}
|
|
80
|
-
<Card>
|
|
81
|
-
<CardHeader>
|
|
82
|
-
<CardTitle>{title}</CardTitle>
|
|
83
|
-
</CardHeader>
|
|
84
|
-
<CardContent className="space-y-4">
|
|
85
|
-
{details.map(renderDetail)}
|
|
86
|
-
</CardContent>
|
|
87
|
-
</Card>
|
|
88
|
-
|
|
89
|
-
{/* Action Card */}
|
|
90
|
-
{action && (
|
|
91
|
-
<Card className="bg-primary/5 border-primary/20">
|
|
92
|
-
<CardContent className="pt-6">
|
|
93
|
-
<h3 className="font-semibold mb-2">{action.title}</h3>
|
|
94
|
-
{action.description && (
|
|
95
|
-
<p className="text-sm text-muted-foreground mb-4">
|
|
96
|
-
{action.description}
|
|
97
|
-
</p>
|
|
98
|
-
)}
|
|
99
|
-
<Button asChild className="w-full">
|
|
100
|
-
<a
|
|
101
|
-
href={action.button.href}
|
|
102
|
-
target={action.button.external !== false ? '_blank' : undefined}
|
|
103
|
-
rel={action.button.external !== false ? 'noopener noreferrer' : undefined}
|
|
104
|
-
>
|
|
105
|
-
{action.button.icon}
|
|
106
|
-
<span className="ml-2">{action.button.label}</span>
|
|
107
|
-
</a>
|
|
108
|
-
</Button>
|
|
109
|
-
</CardContent>
|
|
110
|
-
</Card>
|
|
111
|
-
)}
|
|
112
|
-
</div>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { Mail, MapPin, Calendar, MessageCircle } from 'lucide-react';
|
|
5
|
-
import { ContactFormBase as ContactForm } from './ContactForm';
|
|
6
|
-
import { ContactInfo } from './ContactInfo';
|
|
7
|
-
import type { ContactDetail, LeadSubmissionResult } from './types';
|
|
8
|
-
|
|
9
|
-
// ============================================================================
|
|
10
|
-
// Config
|
|
11
|
-
// ============================================================================
|
|
12
|
-
|
|
13
|
-
const DEFAULT_CONFIG = {
|
|
14
|
-
apiUrl: 'https://api.reforms.ai',
|
|
15
|
-
email: 'markolofsen@gmail.com',
|
|
16
|
-
whatsapp: '+62 813 39646301',
|
|
17
|
-
calendly: 'https://calendly.com/markolofsen/meeting',
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
// ============================================================================
|
|
21
|
-
// Props
|
|
22
|
-
// ============================================================================
|
|
23
|
-
|
|
24
|
-
export interface ContactPageProps {
|
|
25
|
-
/** Override API URL (defaults to DEFAULT_CONFIG.apiUrl) */
|
|
26
|
-
apiUrl?: string;
|
|
27
|
-
/** Override email */
|
|
28
|
-
email?: string;
|
|
29
|
-
/** Override whatsapp */
|
|
30
|
-
whatsapp?: string;
|
|
31
|
-
/** Override calendly link */
|
|
32
|
-
calendlyUrl?: string;
|
|
33
|
-
/** Page title */
|
|
34
|
-
title?: React.ReactNode;
|
|
35
|
-
/** Page subtitle */
|
|
36
|
-
subtitle?: string;
|
|
37
|
-
/** Location text */
|
|
38
|
-
location?: string;
|
|
39
|
-
/** Hide calendly action card */
|
|
40
|
-
hideCalendly?: boolean;
|
|
41
|
-
/** Additional className */
|
|
42
|
-
className?: string;
|
|
43
|
-
/** Callback after successful submit */
|
|
44
|
-
onSuccess?: (result: LeadSubmissionResult) => void;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ============================================================================
|
|
48
|
-
// Component
|
|
49
|
-
// ============================================================================
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* ContactPageBase - Pre-configured contact page component
|
|
53
|
-
*
|
|
54
|
-
* NOTE: Use ContactPage from index.ts which is dynamically imported (ssr: false)
|
|
55
|
-
*
|
|
56
|
-
* @example
|
|
57
|
-
* ```tsx
|
|
58
|
-
* import { ContactPage } from '@djangocfg/layouts';
|
|
59
|
-
* <ContactPage />
|
|
60
|
-
* ```
|
|
61
|
-
*/
|
|
62
|
-
export function ContactPageBase({
|
|
63
|
-
apiUrl = DEFAULT_CONFIG.apiUrl,
|
|
64
|
-
email = DEFAULT_CONFIG.email,
|
|
65
|
-
whatsapp = DEFAULT_CONFIG.whatsapp,
|
|
66
|
-
calendlyUrl = DEFAULT_CONFIG.calendly,
|
|
67
|
-
title = 'Get in Touch',
|
|
68
|
-
subtitle = "Have a question or want to work together? We'd love to hear from you.",
|
|
69
|
-
location = 'Remote-first team',
|
|
70
|
-
hideCalendly = false,
|
|
71
|
-
className,
|
|
72
|
-
onSuccess,
|
|
73
|
-
}: ContactPageProps) {
|
|
74
|
-
// Format phone for WhatsApp link (remove spaces and special chars)
|
|
75
|
-
const whatsappPhone = whatsapp.replace(/[\s\-\(\)]/g, '');
|
|
76
|
-
|
|
77
|
-
const contactDetails: ContactDetail[] = [
|
|
78
|
-
{
|
|
79
|
-
icon: <Mail className="h-5 w-5" />,
|
|
80
|
-
label: 'Email',
|
|
81
|
-
value: email,
|
|
82
|
-
href: `mailto:${email}`,
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
icon: <MessageCircle className="h-5 w-5" />,
|
|
86
|
-
label: 'WhatsApp',
|
|
87
|
-
value: whatsapp,
|
|
88
|
-
href: `https://wa.me/${whatsappPhone}`,
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
icon: <MapPin className="h-5 w-5" />,
|
|
92
|
-
label: 'Location',
|
|
93
|
-
value: location,
|
|
94
|
-
},
|
|
95
|
-
];
|
|
96
|
-
|
|
97
|
-
return (
|
|
98
|
-
<div className={className}>
|
|
99
|
-
{/* Header */}
|
|
100
|
-
<div className="text-center mb-8 md:mb-12">
|
|
101
|
-
<h1 className="text-3xl sm:text-4xl md:text-5xl font-bold mb-4">
|
|
102
|
-
{title}
|
|
103
|
-
</h1>
|
|
104
|
-
<p className="text-base sm:text-lg text-muted-foreground max-w-2xl mx-auto px-4">
|
|
105
|
-
{subtitle}
|
|
106
|
-
</p>
|
|
107
|
-
</div>
|
|
108
|
-
|
|
109
|
-
{/* Content Grid */}
|
|
110
|
-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 w-full">
|
|
111
|
-
<div>
|
|
112
|
-
<ContactForm apiUrl={apiUrl} onSuccess={onSuccess} />
|
|
113
|
-
</div>
|
|
114
|
-
<div>
|
|
115
|
-
<ContactInfo
|
|
116
|
-
details={contactDetails}
|
|
117
|
-
action={hideCalendly ? undefined : {
|
|
118
|
-
title: 'Schedule a Meeting',
|
|
119
|
-
description: 'Book a time that works for you',
|
|
120
|
-
button: {
|
|
121
|
-
icon: <Calendar className="h-4 w-4" />,
|
|
122
|
-
label: 'Book a Call',
|
|
123
|
-
href: calendlyUrl,
|
|
124
|
-
},
|
|
125
|
-
}}
|
|
126
|
-
/>
|
|
127
|
-
</div>
|
|
128
|
-
</div>
|
|
129
|
-
</div>
|
|
130
|
-
);
|
|
131
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dynamic ContactForm components (ssr: false)
|
|
3
|
-
*
|
|
4
|
-
* Avoids hydration mismatch when using localStorage for form drafts.
|
|
5
|
-
* Components are loaded client-side only.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
'use client';
|
|
9
|
-
|
|
10
|
-
import dynamic from 'next/dynamic';
|
|
11
|
-
import { Skeleton } from '@djangocfg/ui-nextjs';
|
|
12
|
-
import type { ContactFormProps } from './ContactForm';
|
|
13
|
-
import type { ContactPageProps } from './ContactPage';
|
|
14
|
-
|
|
15
|
-
function ContactFormSkeleton() {
|
|
16
|
-
return (
|
|
17
|
-
<div className="space-y-4">
|
|
18
|
-
<Skeleton className="w-full h-10" />
|
|
19
|
-
<Skeleton className="w-full h-10" />
|
|
20
|
-
<Skeleton className="w-full h-10" />
|
|
21
|
-
<Skeleton className="w-full h-24" />
|
|
22
|
-
<Skeleton className="w-full h-10" />
|
|
23
|
-
</div>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function ContactPageSkeleton() {
|
|
28
|
-
return (
|
|
29
|
-
<div>
|
|
30
|
-
<div className="text-center mb-8 md:mb-12">
|
|
31
|
-
<Skeleton className="w-64 h-12 mx-auto mb-4" />
|
|
32
|
-
<Skeleton className="w-96 h-6 mx-auto" />
|
|
33
|
-
</div>
|
|
34
|
-
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
35
|
-
<div className="lg:col-span-2">
|
|
36
|
-
<ContactFormSkeleton />
|
|
37
|
-
</div>
|
|
38
|
-
<div className="space-y-4">
|
|
39
|
-
<Skeleton className="w-full h-32" />
|
|
40
|
-
<Skeleton className="w-full h-24" />
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export const ContactForm = dynamic<ContactFormProps>(
|
|
48
|
-
() => import('./ContactForm').then((mod) => mod.ContactFormBase),
|
|
49
|
-
{ ssr: false, loading: () => <ContactFormSkeleton /> }
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
export const ContactPage = dynamic<ContactPageProps>(
|
|
53
|
-
() => import('./ContactPage').then((mod) => mod.ContactPageBase),
|
|
54
|
-
{ ssr: false, loading: () => <ContactPageSkeleton /> }
|
|
55
|
-
);
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// ContactForm - Contact form using Django-CFG Lead API
|
|
3
|
-
// ============================================================================
|
|
4
|
-
|
|
5
|
-
// Components (dynamic import, ssr: false - no hydration issues)
|
|
6
|
-
export { ContactForm, ContactPage } from './dynamic';
|
|
7
|
-
export { ContactInfo } from './ContactInfo';
|
|
8
|
-
|
|
9
|
-
// Types
|
|
10
|
-
export type { ContactFormProps } from './ContactForm';
|
|
11
|
-
export type { ContactPageProps } from './ContactPage';
|
|
12
|
-
|
|
13
|
-
// Provider & Hooks
|
|
14
|
-
export {
|
|
15
|
-
ContactFormProvider,
|
|
16
|
-
useContactForm,
|
|
17
|
-
useContactFormOptional,
|
|
18
|
-
} from './ContactFormProvider';
|
|
19
|
-
|
|
20
|
-
// Types
|
|
21
|
-
export type {
|
|
22
|
-
ContactFormProviderProps,
|
|
23
|
-
ContactFormContextValue,
|
|
24
|
-
LeadSubmissionData,
|
|
25
|
-
LeadSubmissionResult,
|
|
26
|
-
UseContactFormReturn,
|
|
27
|
-
ContactFormTexts,
|
|
28
|
-
ContactDetail,
|
|
29
|
-
ContactAction,
|
|
30
|
-
ContactInfoProps,
|
|
31
|
-
} from './types';
|
|
32
|
-
|
|
33
|
-
// Constants
|
|
34
|
-
export { DEFAULT_FORM_TEXTS, DEFAULT_INFO_TITLE } from './types';
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
import type { Schemas } from '@djangocfg/api';
|
|
3
|
-
|
|
4
|
-
// ============================================================================
|
|
5
|
-
// Re-export API Types
|
|
6
|
-
// ============================================================================
|
|
7
|
-
|
|
8
|
-
/** Lead submission request data - uses generated API type */
|
|
9
|
-
export type LeadSubmissionData = Schemas.LeadSubmissionRequest;
|
|
10
|
-
|
|
11
|
-
/** Lead submission response - uses generated API type */
|
|
12
|
-
export type LeadSubmissionResult = Schemas.LeadSubmissionResponse;
|
|
13
|
-
|
|
14
|
-
// ============================================================================
|
|
15
|
-
// Provider Types
|
|
16
|
-
// ============================================================================
|
|
17
|
-
|
|
18
|
-
export interface ContactFormProviderProps {
|
|
19
|
-
children: ReactNode;
|
|
20
|
-
/**
|
|
21
|
-
* API base URL for lead submission
|
|
22
|
-
* If not provided or empty, will use local Next.js API route (/api/contact)
|
|
23
|
-
* @example "https://api.example.com" or undefined to use /api/contact
|
|
24
|
-
*/
|
|
25
|
-
apiUrl?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// ============================================================================
|
|
29
|
-
// Hook Types
|
|
30
|
-
// ============================================================================
|
|
31
|
-
|
|
32
|
-
export interface UseContactFormReturn {
|
|
33
|
-
/** Submit lead data */
|
|
34
|
-
submit: (data: LeadSubmissionData) => Promise<LeadSubmissionResult>;
|
|
35
|
-
/** Is currently submitting */
|
|
36
|
-
isSubmitting: boolean;
|
|
37
|
-
/** Last error */
|
|
38
|
-
error: Error | null;
|
|
39
|
-
/** Last successful response */
|
|
40
|
-
lastResponse: LeadSubmissionResult | null;
|
|
41
|
-
/** Reset error state */
|
|
42
|
-
resetError: () => void;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ============================================================================
|
|
46
|
-
// Context Types
|
|
47
|
-
// ============================================================================
|
|
48
|
-
|
|
49
|
-
export interface ContactFormContextValue extends UseContactFormReturn {
|
|
50
|
-
/** API base URL */
|
|
51
|
-
apiUrl: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ============================================================================
|
|
55
|
-
// Form Texts
|
|
56
|
-
// ============================================================================
|
|
57
|
-
|
|
58
|
-
export interface ContactFormTexts {
|
|
59
|
-
title?: string;
|
|
60
|
-
description?: string;
|
|
61
|
-
submitText?: string;
|
|
62
|
-
loadingText?: string;
|
|
63
|
-
successTitle?: string;
|
|
64
|
-
successMessage?: string;
|
|
65
|
-
errorTitle?: string;
|
|
66
|
-
errorMessage?: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export const DEFAULT_FORM_TEXTS: Required<ContactFormTexts> = {
|
|
70
|
-
title: 'Send us a message',
|
|
71
|
-
description: "Fill out the form below and we'll get back to you as soon as possible",
|
|
72
|
-
submitText: 'Send Message',
|
|
73
|
-
loadingText: 'Sending...',
|
|
74
|
-
successTitle: 'Message Sent!',
|
|
75
|
-
successMessage: "We'll get back to you within 24 hours.",
|
|
76
|
-
errorTitle: 'Error',
|
|
77
|
-
errorMessage: 'Failed to send message. Please try again.',
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
// ============================================================================
|
|
81
|
-
// Contact Info Types
|
|
82
|
-
// ============================================================================
|
|
83
|
-
|
|
84
|
-
export interface ContactDetail {
|
|
85
|
-
icon: ReactNode;
|
|
86
|
-
label: string;
|
|
87
|
-
value: string;
|
|
88
|
-
href?: string;
|
|
89
|
-
external?: boolean;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export interface ContactAction {
|
|
93
|
-
icon: ReactNode;
|
|
94
|
-
label: string;
|
|
95
|
-
href: string;
|
|
96
|
-
external?: boolean;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface ContactInfoProps {
|
|
100
|
-
details: ContactDetail[];
|
|
101
|
-
title?: string;
|
|
102
|
-
action?: {
|
|
103
|
-
title: string;
|
|
104
|
-
description?: string;
|
|
105
|
-
button: ContactAction;
|
|
106
|
-
};
|
|
107
|
-
className?: string;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export const DEFAULT_INFO_TITLE = 'Contact Information';
|