@djangocfg/layouts 2.1.9 → 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/McpChat/hooks/useAIChat.ts +16 -3
- 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,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hook for infinite scroll chat sessions
|
|
3
|
-
* Uses SWR Infinite for pagination
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import useSWRInfinite from 'swr/infinite';
|
|
7
|
-
import { api, Fetchers, CfgKnowbaseTypes } from '@djangocfg/api';
|
|
8
|
-
import type { API } from '@djangocfg/api';
|
|
9
|
-
|
|
10
|
-
type PaginatedChatSessionList = CfgKnowbaseTypes.PaginatedChatSessionList;
|
|
11
|
-
type ChatSession = CfgKnowbaseTypes.ChatSession;
|
|
12
|
-
|
|
13
|
-
const PAGE_SIZE = 20;
|
|
14
|
-
|
|
15
|
-
export function useInfiniteSessions() {
|
|
16
|
-
const getKey = (pageIndex: number, previousPageData: PaginatedChatSessionList | null) => {
|
|
17
|
-
// Reached the end
|
|
18
|
-
if (previousPageData && !previousPageData.has_next) return null;
|
|
19
|
-
|
|
20
|
-
// First page, no previous data
|
|
21
|
-
if (pageIndex === 0) return ['cfg-knowbase-admin-sessions-infinite', 1, PAGE_SIZE];
|
|
22
|
-
|
|
23
|
-
// Add the page number to the SWR key
|
|
24
|
-
return ['cfg-knowbase-admin-sessions-infinite', pageIndex + 1, PAGE_SIZE];
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const fetcher = async ([, page, pageSize]: [string, number, number]) => {
|
|
28
|
-
return Fetchers.getKnowbaseAdminSessionsList(
|
|
29
|
-
{ page, page_size: pageSize },
|
|
30
|
-
api as unknown as API
|
|
31
|
-
);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const {
|
|
35
|
-
data,
|
|
36
|
-
error,
|
|
37
|
-
isLoading,
|
|
38
|
-
isValidating,
|
|
39
|
-
size,
|
|
40
|
-
setSize,
|
|
41
|
-
mutate,
|
|
42
|
-
} = useSWRInfinite<PaginatedChatSessionList>(getKey, fetcher, {
|
|
43
|
-
revalidateFirstPage: false,
|
|
44
|
-
parallel: false,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// Flatten all pages into single array
|
|
48
|
-
const sessions: ChatSession[] = data ? data.flatMap((page) => page.results) : [];
|
|
49
|
-
|
|
50
|
-
// Check if there are more pages
|
|
51
|
-
const hasMore = data && data[data.length - 1]?.has_next;
|
|
52
|
-
|
|
53
|
-
// Total count from last page
|
|
54
|
-
const totalCount = data && data[data.length - 1]?.count;
|
|
55
|
-
|
|
56
|
-
// Loading more state
|
|
57
|
-
const isLoadingMore = isValidating && data && typeof data[size - 1] !== 'undefined';
|
|
58
|
-
|
|
59
|
-
// Function to load next page
|
|
60
|
-
const loadMore = () => {
|
|
61
|
-
if (hasMore && !isLoadingMore) {
|
|
62
|
-
setSize(size + 1);
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// Refresh all pages
|
|
67
|
-
const refresh = async () => {
|
|
68
|
-
await mutate();
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
sessions,
|
|
73
|
-
isLoading,
|
|
74
|
-
isLoadingMore: isLoadingMore || false,
|
|
75
|
-
error,
|
|
76
|
-
hasMore: hasMore || false,
|
|
77
|
-
totalCount: totalCount || 0,
|
|
78
|
-
loadMore,
|
|
79
|
-
refresh,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
/**
|
|
3
|
-
* Knowledge Chat Module
|
|
4
|
-
* Complete RAG-powered chat widget with context providers
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
'use client';
|
|
8
|
-
|
|
9
|
-
import React from 'react';
|
|
10
|
-
import { KnowbaseChatProvider, KnowbaseSessionsProvider } from '@djangocfg/layouts/contexts';
|
|
11
|
-
import { ChatUIProvider } from './ChatUIContext';
|
|
12
|
-
import { ChatWidget } from './ChatWidget';
|
|
13
|
-
import type { ChatWidgetProps } from './types';
|
|
14
|
-
|
|
15
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
16
|
-
// Main Component with Providers
|
|
17
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
18
|
-
|
|
19
|
-
export const KnowledgeChat: React.FC<ChatWidgetProps> = (props) => {
|
|
20
|
-
return (
|
|
21
|
-
<KnowbaseChatProvider>
|
|
22
|
-
<KnowbaseSessionsProvider>
|
|
23
|
-
<ChatUIProvider initialState={{ isOpen: false }}>
|
|
24
|
-
<ChatWidget {...props} />
|
|
25
|
-
</ChatUIProvider>
|
|
26
|
-
</KnowbaseSessionsProvider>
|
|
27
|
-
</KnowbaseChatProvider>
|
|
28
|
-
);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
32
|
-
// Named Exports
|
|
33
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
34
|
-
|
|
35
|
-
export { ChatWidget } from './ChatWidget';
|
|
36
|
-
export { ChatUIProvider, useChatUI } from './ChatUIContext';
|
|
37
|
-
export { MessageList, MessageInput, SessionList } from './components';
|
|
38
|
-
export * from './types';
|
|
39
|
-
|
|
40
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
41
|
-
// Default Export
|
|
42
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
43
|
-
|
|
44
|
-
export default KnowledgeChat;
|
|
45
|
-
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Chat Types
|
|
3
|
-
* Type definitions for RAG-powered chat widget
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// Import types from Schemas namespace - explicitly exported and works reliably
|
|
7
|
-
// Types are available via Schemas namespace even when direct exports have issues
|
|
8
|
-
import type { Schemas } from '@djangocfg/api';
|
|
9
|
-
|
|
10
|
-
// Extract types from Schemas namespace
|
|
11
|
-
export type ChatMessage = Schemas.ChatMessage;
|
|
12
|
-
export type ChatSource = Schemas.ChatSource;
|
|
13
|
-
|
|
14
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
15
|
-
// Extended Message Type (for UI with sources)
|
|
16
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
17
|
-
|
|
18
|
-
export interface ChatMessageWithSources extends ChatMessage {
|
|
19
|
-
sources?: ChatSource[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
23
|
-
// Widget Props
|
|
24
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
export interface ChatWidgetProps {
|
|
27
|
-
/** Whether to auto-open on mount */
|
|
28
|
-
autoOpen?: boolean;
|
|
29
|
-
/** Render even when closed (for animations) */
|
|
30
|
-
persistent?: boolean;
|
|
31
|
-
/** Additional CSS classes */
|
|
32
|
-
className?: string;
|
|
33
|
-
/** Callback when toggle state changes */
|
|
34
|
-
onToggle?: (isOpen: boolean) => void;
|
|
35
|
-
/** Callback when message is sent */
|
|
36
|
-
onMessage?: (message: ChatMessageWithSources) => void;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
40
|
-
// UI State
|
|
41
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
42
|
-
|
|
43
|
-
export interface ChatUIState {
|
|
44
|
-
isOpen: boolean;
|
|
45
|
-
isExpanded: boolean;
|
|
46
|
-
isMinimized: boolean;
|
|
47
|
-
showSources: boolean;
|
|
48
|
-
showTimestamps: boolean;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
52
|
-
// Component Props
|
|
53
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
54
|
-
|
|
55
|
-
export interface MessageListProps {
|
|
56
|
-
messages: ChatMessageWithSources[];
|
|
57
|
-
isLoading?: boolean;
|
|
58
|
-
showSources?: boolean;
|
|
59
|
-
showTimestamps?: boolean;
|
|
60
|
-
autoScroll?: boolean;
|
|
61
|
-
className?: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface MessageInputProps {
|
|
65
|
-
onSend: (message: string) => Promise<void>;
|
|
66
|
-
isLoading?: boolean;
|
|
67
|
-
disabled?: boolean;
|
|
68
|
-
placeholder?: string;
|
|
69
|
-
className?: string;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface SessionListProps {
|
|
73
|
-
isOpen: boolean;
|
|
74
|
-
onClose: () => void;
|
|
75
|
-
onSelectSession: (sessionId: string) => void;
|
|
76
|
-
className?: string;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
'use client';
|
|
3
|
-
|
|
4
|
-
import React, { useEffect, useState } from 'react';
|
|
5
|
-
import { useForm, useWatch } from 'react-hook-form';
|
|
6
|
-
import { zodResolver } from '@hookform/resolvers/zod';
|
|
7
|
-
import { Schemas, type Schemas as SchemaTypes } from '@djangocfg/api';
|
|
8
|
-
import {
|
|
9
|
-
Form,
|
|
10
|
-
FormControl,
|
|
11
|
-
FormField,
|
|
12
|
-
FormItem,
|
|
13
|
-
FormLabel,
|
|
14
|
-
FormMessage,
|
|
15
|
-
Input,
|
|
16
|
-
Textarea,
|
|
17
|
-
Button,
|
|
18
|
-
useToast,
|
|
19
|
-
Card,
|
|
20
|
-
CardHeader,
|
|
21
|
-
CardTitle,
|
|
22
|
-
CardDescription,
|
|
23
|
-
CardContent,
|
|
24
|
-
cn,
|
|
25
|
-
useLocalStorage,
|
|
26
|
-
} from '@djangocfg/ui-nextjs';
|
|
27
|
-
import { Send, CheckCircle2, ArrowLeft } from 'lucide-react';
|
|
28
|
-
import type { ContactFormTexts, LeadSubmissionResult } from './types';
|
|
29
|
-
import { DEFAULT_FORM_TEXTS } from './types';
|
|
30
|
-
import { ContactFormProvider, useContactForm } from './ContactFormProvider';
|
|
31
|
-
|
|
32
|
-
type FormData = SchemaTypes.LeadSubmissionRequest;
|
|
33
|
-
|
|
34
|
-
// ============================================================================
|
|
35
|
-
// Props
|
|
36
|
-
// ============================================================================
|
|
37
|
-
|
|
38
|
-
export interface ContactFormProps {
|
|
39
|
-
/** API base URL for lead submission */
|
|
40
|
-
apiUrl: string;
|
|
41
|
-
/** Customizable texts */
|
|
42
|
-
texts?: Partial<ContactFormTexts>;
|
|
43
|
-
/** Show card wrapper (default: true) */
|
|
44
|
-
showCard?: boolean;
|
|
45
|
-
/** Submit button icon */
|
|
46
|
-
submitIcon?: React.ReactNode;
|
|
47
|
-
/** Additional className for form */
|
|
48
|
-
className?: string;
|
|
49
|
-
/** Reset form after successful submit (default: true) */
|
|
50
|
-
resetOnSuccess?: boolean;
|
|
51
|
-
/** Callback after successful submit */
|
|
52
|
-
onSuccess?: (result: LeadSubmissionResult) => void;
|
|
53
|
-
/** Callback on error */
|
|
54
|
-
onError?: (error: Error) => void;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// ============================================================================
|
|
58
|
-
// Component
|
|
59
|
-
// ============================================================================
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* ContactFormBase - Contact form using Django-CFG Lead API
|
|
63
|
-
*
|
|
64
|
-
* NOTE: Use ContactForm from index.ts which is dynamically imported (ssr: false)
|
|
65
|
-
*
|
|
66
|
-
* @example
|
|
67
|
-
* ```tsx
|
|
68
|
-
* import { ContactForm } from '@djangocfg/layouts';
|
|
69
|
-
* <ContactForm apiUrl="https://api.example.com" />
|
|
70
|
-
* ```
|
|
71
|
-
*/
|
|
72
|
-
export function ContactFormBase({ apiUrl, ...props }: ContactFormProps) {
|
|
73
|
-
return (
|
|
74
|
-
<ContactFormProvider apiUrl={apiUrl}>
|
|
75
|
-
<ContactFormInner {...props} />
|
|
76
|
-
</ContactFormProvider>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ============================================================================
|
|
81
|
-
// Inner Form Component
|
|
82
|
-
// ============================================================================
|
|
83
|
-
|
|
84
|
-
const STORAGE_KEY = 'contact-form-draft';
|
|
85
|
-
|
|
86
|
-
type FormDraft = {
|
|
87
|
-
name: string;
|
|
88
|
-
email: string;
|
|
89
|
-
company: string;
|
|
90
|
-
subject: string;
|
|
91
|
-
message: string;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const emptyDraft: FormDraft = {
|
|
95
|
-
name: '',
|
|
96
|
-
email: '',
|
|
97
|
-
company: '',
|
|
98
|
-
subject: '',
|
|
99
|
-
message: '',
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
function ContactFormInner({
|
|
103
|
-
texts = {},
|
|
104
|
-
showCard = true,
|
|
105
|
-
submitIcon,
|
|
106
|
-
className,
|
|
107
|
-
resetOnSuccess = true,
|
|
108
|
-
onSuccess,
|
|
109
|
-
onError,
|
|
110
|
-
}: Omit<ContactFormProps, 'apiUrl'>) {
|
|
111
|
-
const { toast } = useToast();
|
|
112
|
-
const { submit, isSubmitting } = useContactForm();
|
|
113
|
-
const t = { ...DEFAULT_FORM_TEXTS, ...texts };
|
|
114
|
-
const [draft, setDraft, clearDraft] = useLocalStorage<FormDraft>(STORAGE_KEY, emptyDraft);
|
|
115
|
-
const [isSuccess, setIsSuccess] = useState(false);
|
|
116
|
-
const [isHydrated, setIsHydrated] = useState(false);
|
|
117
|
-
|
|
118
|
-
const form = useForm<FormData>({
|
|
119
|
-
resolver: zodResolver(Schemas.LeadSubmissionRequestSchema),
|
|
120
|
-
// Start with empty values to match SSR
|
|
121
|
-
defaultValues: {
|
|
122
|
-
...emptyDraft,
|
|
123
|
-
site_url: '',
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// Hydrate form with localStorage draft and site_url after mount
|
|
128
|
-
useEffect(() => {
|
|
129
|
-
if (isHydrated) return;
|
|
130
|
-
setIsHydrated(true);
|
|
131
|
-
|
|
132
|
-
// Apply draft from localStorage and set site_url
|
|
133
|
-
const currentValues = form.getValues();
|
|
134
|
-
const hasDraftData = draft.name || draft.email || draft.company || draft.subject || draft.message;
|
|
135
|
-
const currentSiteUrl = typeof window !== 'undefined' ? window.location.href : '';
|
|
136
|
-
|
|
137
|
-
if (hasDraftData || !currentValues.site_url) {
|
|
138
|
-
form.reset({
|
|
139
|
-
...emptyDraft,
|
|
140
|
-
...draft,
|
|
141
|
-
site_url: currentSiteUrl,
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}, [draft, form, isHydrated]);
|
|
145
|
-
|
|
146
|
-
// Watch form values and save to localStorage
|
|
147
|
-
const watchedValues = useWatch({ control: form.control });
|
|
148
|
-
const prevDraftRef = React.useRef<FormDraft>(draft);
|
|
149
|
-
|
|
150
|
-
useEffect(() => {
|
|
151
|
-
// Only save to localStorage after hydration to avoid unnecessary writes
|
|
152
|
-
if (!isHydrated) return;
|
|
153
|
-
|
|
154
|
-
const { name, email, company, subject, message } = watchedValues;
|
|
155
|
-
const newDraft: FormDraft = {
|
|
156
|
-
name: name || '',
|
|
157
|
-
email: email || '',
|
|
158
|
-
company: company || '',
|
|
159
|
-
subject: subject || '',
|
|
160
|
-
message: message || '',
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
// Only update if draft actually changed to prevent infinite loop
|
|
164
|
-
const hasChanges =
|
|
165
|
-
prevDraftRef.current.name !== newDraft.name ||
|
|
166
|
-
prevDraftRef.current.email !== newDraft.email ||
|
|
167
|
-
prevDraftRef.current.company !== newDraft.company ||
|
|
168
|
-
prevDraftRef.current.subject !== newDraft.subject ||
|
|
169
|
-
prevDraftRef.current.message !== newDraft.message;
|
|
170
|
-
|
|
171
|
-
if (hasChanges && (name || email || company || subject || message)) {
|
|
172
|
-
prevDraftRef.current = newDraft;
|
|
173
|
-
setDraft(newDraft);
|
|
174
|
-
}
|
|
175
|
-
}, [watchedValues, setDraft, isHydrated]);
|
|
176
|
-
|
|
177
|
-
const handleSubmit = async (data: FormData) => {
|
|
178
|
-
try {
|
|
179
|
-
const result = await submit(data);
|
|
180
|
-
if (resetOnSuccess) {
|
|
181
|
-
// Keep contact info (name, email, company), clear only message content
|
|
182
|
-
// Store current site_url before reset to avoid re-reading window.location
|
|
183
|
-
const currentValues = form.getValues();
|
|
184
|
-
const currentSiteUrl = currentValues.site_url || (typeof window !== 'undefined' ? window.location.href : '');
|
|
185
|
-
|
|
186
|
-
form.reset({
|
|
187
|
-
name: currentValues.name,
|
|
188
|
-
email: currentValues.email,
|
|
189
|
-
company: currentValues.company,
|
|
190
|
-
subject: '',
|
|
191
|
-
message: '',
|
|
192
|
-
site_url: currentSiteUrl, // Keep the original site_url
|
|
193
|
-
});
|
|
194
|
-
// Update draft to keep contact info
|
|
195
|
-
setDraft({
|
|
196
|
-
name: currentValues.name || '',
|
|
197
|
-
email: currentValues.email || '',
|
|
198
|
-
company: currentValues.company || '',
|
|
199
|
-
subject: '',
|
|
200
|
-
message: '',
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
setIsSuccess(true);
|
|
204
|
-
onSuccess?.(result);
|
|
205
|
-
} catch (error) {
|
|
206
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
207
|
-
toast({ title: t.errorTitle, description: err.message || t.errorMessage, variant: 'destructive' });
|
|
208
|
-
onError?.(err);
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
const handleReset = () => {
|
|
213
|
-
setIsSuccess(false);
|
|
214
|
-
// Keep contact info when returning to form
|
|
215
|
-
const currentDraft = draft;
|
|
216
|
-
form.reset({
|
|
217
|
-
name: currentDraft.name,
|
|
218
|
-
email: currentDraft.email,
|
|
219
|
-
company: currentDraft.company,
|
|
220
|
-
subject: '',
|
|
221
|
-
message: '',
|
|
222
|
-
site_url: typeof window !== 'undefined' ? window.location.href : '',
|
|
223
|
-
});
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
// Success state
|
|
227
|
-
if (isSuccess) {
|
|
228
|
-
const successContent = (
|
|
229
|
-
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
230
|
-
<div className="rounded-full bg-green-100 p-4 mb-6">
|
|
231
|
-
<CheckCircle2 className="h-12 w-12 text-green-600" />
|
|
232
|
-
</div>
|
|
233
|
-
<h3 className="text-2xl font-semibold mb-2">{t.successTitle}</h3>
|
|
234
|
-
<p className="text-muted-foreground mb-6 max-w-sm">{t.successMessage}</p>
|
|
235
|
-
<Button variant="outline" onClick={handleReset}>
|
|
236
|
-
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
237
|
-
Send another message
|
|
238
|
-
</Button>
|
|
239
|
-
</div>
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
if (!showCard) return successContent;
|
|
243
|
-
|
|
244
|
-
return (
|
|
245
|
-
<Card>
|
|
246
|
-
<CardContent className="pt-6">{successContent}</CardContent>
|
|
247
|
-
</Card>
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Form
|
|
252
|
-
const formContent = (
|
|
253
|
-
<Form {...form}>
|
|
254
|
-
<form onSubmit={form.handleSubmit(handleSubmit)} className={cn('space-y-4', className)}>
|
|
255
|
-
<FormField
|
|
256
|
-
control={form.control}
|
|
257
|
-
name="name"
|
|
258
|
-
render={({ field }) => (
|
|
259
|
-
<FormItem>
|
|
260
|
-
<FormLabel>Name *</FormLabel>
|
|
261
|
-
<FormControl>
|
|
262
|
-
<Input placeholder="Your name" {...field} />
|
|
263
|
-
</FormControl>
|
|
264
|
-
<FormMessage />
|
|
265
|
-
</FormItem>
|
|
266
|
-
)}
|
|
267
|
-
/>
|
|
268
|
-
|
|
269
|
-
<FormField
|
|
270
|
-
control={form.control}
|
|
271
|
-
name="email"
|
|
272
|
-
render={({ field }) => (
|
|
273
|
-
<FormItem>
|
|
274
|
-
<FormLabel>Email *</FormLabel>
|
|
275
|
-
<FormControl>
|
|
276
|
-
<Input type="email" placeholder="your@email.com" {...field} />
|
|
277
|
-
</FormControl>
|
|
278
|
-
<FormMessage />
|
|
279
|
-
</FormItem>
|
|
280
|
-
)}
|
|
281
|
-
/>
|
|
282
|
-
|
|
283
|
-
<FormField
|
|
284
|
-
control={form.control}
|
|
285
|
-
name="company"
|
|
286
|
-
render={({ field }) => (
|
|
287
|
-
<FormItem>
|
|
288
|
-
<FormLabel>Company</FormLabel>
|
|
289
|
-
<FormControl>
|
|
290
|
-
<Input placeholder="Your company (optional)" {...field} value={field.value ?? ''} />
|
|
291
|
-
</FormControl>
|
|
292
|
-
<FormMessage />
|
|
293
|
-
</FormItem>
|
|
294
|
-
)}
|
|
295
|
-
/>
|
|
296
|
-
|
|
297
|
-
<FormField
|
|
298
|
-
control={form.control}
|
|
299
|
-
name="subject"
|
|
300
|
-
render={({ field }) => (
|
|
301
|
-
<FormItem>
|
|
302
|
-
<FormLabel>Subject</FormLabel>
|
|
303
|
-
<FormControl>
|
|
304
|
-
<Input placeholder="What is this about?" {...field} value={field.value ?? ''} />
|
|
305
|
-
</FormControl>
|
|
306
|
-
<FormMessage />
|
|
307
|
-
</FormItem>
|
|
308
|
-
)}
|
|
309
|
-
/>
|
|
310
|
-
|
|
311
|
-
<FormField
|
|
312
|
-
control={form.control}
|
|
313
|
-
name="message"
|
|
314
|
-
render={({ field }) => (
|
|
315
|
-
<FormItem>
|
|
316
|
-
<FormLabel>Message *</FormLabel>
|
|
317
|
-
<FormControl>
|
|
318
|
-
<Textarea placeholder="Your message..." style={{ minHeight: '120px' }} {...field} />
|
|
319
|
-
</FormControl>
|
|
320
|
-
<FormMessage />
|
|
321
|
-
</FormItem>
|
|
322
|
-
)}
|
|
323
|
-
/>
|
|
324
|
-
|
|
325
|
-
<input type="hidden" {...form.register('site_url')} />
|
|
326
|
-
|
|
327
|
-
<Button type="submit" className="w-full" disabled={isSubmitting}>
|
|
328
|
-
{isSubmitting ? t.loadingText : t.submitText}
|
|
329
|
-
{!isSubmitting && (submitIcon || <Send className="ml-2 h-4 w-4" />)}
|
|
330
|
-
</Button>
|
|
331
|
-
</form>
|
|
332
|
-
</Form>
|
|
333
|
-
);
|
|
334
|
-
|
|
335
|
-
if (!showCard) return formContent;
|
|
336
|
-
|
|
337
|
-
return (
|
|
338
|
-
<Card>
|
|
339
|
-
<CardHeader>
|
|
340
|
-
<CardTitle>{t.title}</CardTitle>
|
|
341
|
-
<CardDescription>{t.description}</CardDescription>
|
|
342
|
-
</CardHeader>
|
|
343
|
-
<CardContent>{formContent}</CardContent>
|
|
344
|
-
</Card>
|
|
345
|
-
);
|
|
346
|
-
}
|