@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.
Files changed (106) hide show
  1. package/README.md +53 -161
  2. package/package.json +6 -6
  3. package/src/components/RedirectPage/RedirectPage.tsx +1 -1
  4. package/src/index.ts +0 -6
  5. package/src/layouts/AppLayout/AppLayout.tsx +1 -1
  6. package/src/layouts/AppLayout/BaseApp.tsx +1 -1
  7. package/src/layouts/AuthLayout/AuthContext.tsx +1 -1
  8. package/src/layouts/AuthLayout/OAuthCallback.tsx +1 -1
  9. package/src/layouts/AuthLayout/OAuthProviders.tsx +1 -1
  10. package/src/layouts/PrivateLayout/PrivateLayout.tsx +1 -1
  11. package/src/layouts/PrivateLayout/components/PrivateHeader.tsx +1 -1
  12. package/src/layouts/ProfileLayout/components/AvatarSection.tsx +2 -2
  13. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +1 -1
  14. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +1 -1
  15. package/src/layouts/_components/UserMenu.tsx +1 -1
  16. package/src/layouts/index.ts +0 -2
  17. package/src/snippets/Analytics/useAnalytics.ts +1 -1
  18. package/src/snippets/McpChat/hooks/useAIChat.ts +16 -3
  19. package/src/snippets/index.ts +0 -3
  20. package/src/auth/README.md +0 -962
  21. package/src/auth/context/AccountsContext.tsx +0 -240
  22. package/src/auth/context/AuthContext.tsx +0 -604
  23. package/src/auth/context/index.ts +0 -4
  24. package/src/auth/context/types.ts +0 -68
  25. package/src/auth/hooks/index.ts +0 -17
  26. package/src/auth/hooks/useAuthForm.ts +0 -332
  27. package/src/auth/hooks/useAuthGuard.ts +0 -25
  28. package/src/auth/hooks/useAuthRedirect.ts +0 -51
  29. package/src/auth/hooks/useAutoAuth.ts +0 -49
  30. package/src/auth/hooks/useGithubAuth.ts +0 -184
  31. package/src/auth/hooks/useLocalStorage.ts +0 -214
  32. package/src/auth/hooks/useProfileCache.ts +0 -146
  33. package/src/auth/hooks/useSessionStorage.ts +0 -189
  34. package/src/auth/index.ts +0 -10
  35. package/src/auth/middlewares/index.ts +0 -1
  36. package/src/auth/middlewares/proxy.ts +0 -32
  37. package/src/auth/server.ts +0 -6
  38. package/src/auth/utils/errors.ts +0 -34
  39. package/src/auth/utils/index.ts +0 -2
  40. package/src/auth/utils/validation.ts +0 -14
  41. package/src/contexts/LeadsContext.tsx +0 -156
  42. package/src/contexts/NewsletterContext.tsx +0 -263
  43. package/src/contexts/SupportContext.tsx +0 -256
  44. package/src/contexts/index.ts +0 -59
  45. package/src/contexts/knowbase/ChatContext.tsx +0 -174
  46. package/src/contexts/knowbase/DocumentsContext.tsx +0 -304
  47. package/src/contexts/knowbase/SessionsContext.tsx +0 -174
  48. package/src/contexts/knowbase/index.ts +0 -61
  49. package/src/contexts/payments/BalancesContext.tsx +0 -65
  50. package/src/contexts/payments/CurrenciesContext.tsx +0 -66
  51. package/src/contexts/payments/OverviewContext.tsx +0 -174
  52. package/src/contexts/payments/PaymentsContext.tsx +0 -132
  53. package/src/contexts/payments/README.md +0 -201
  54. package/src/contexts/payments/RootPaymentsContext.tsx +0 -68
  55. package/src/contexts/payments/index.ts +0 -50
  56. package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +0 -92
  57. package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +0 -291
  58. package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +0 -290
  59. package/src/layouts/PaymentsLayout/components/index.ts +0 -2
  60. package/src/layouts/PaymentsLayout/events.ts +0 -47
  61. package/src/layouts/PaymentsLayout/index.ts +0 -16
  62. package/src/layouts/PaymentsLayout/types.ts +0 -6
  63. package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +0 -128
  64. package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +0 -142
  65. package/src/layouts/PaymentsLayout/views/overview/components/index.ts +0 -2
  66. package/src/layouts/PaymentsLayout/views/overview/index.tsx +0 -20
  67. package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +0 -276
  68. package/src/layouts/PaymentsLayout/views/payments/components/index.ts +0 -1
  69. package/src/layouts/PaymentsLayout/views/payments/index.tsx +0 -17
  70. package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +0 -273
  71. package/src/layouts/PaymentsLayout/views/transactions/components/index.ts +0 -1
  72. package/src/layouts/PaymentsLayout/views/transactions/index.tsx +0 -17
  73. package/src/layouts/SupportLayout/README.md +0 -91
  74. package/src/layouts/SupportLayout/SupportLayout.tsx +0 -179
  75. package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +0 -155
  76. package/src/layouts/SupportLayout/components/MessageInput.tsx +0 -92
  77. package/src/layouts/SupportLayout/components/MessageList.tsx +0 -314
  78. package/src/layouts/SupportLayout/components/TicketCard.tsx +0 -96
  79. package/src/layouts/SupportLayout/components/TicketList.tsx +0 -153
  80. package/src/layouts/SupportLayout/components/index.ts +0 -6
  81. package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +0 -263
  82. package/src/layouts/SupportLayout/context/index.ts +0 -2
  83. package/src/layouts/SupportLayout/events.ts +0 -33
  84. package/src/layouts/SupportLayout/hooks/index.ts +0 -2
  85. package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +0 -119
  86. package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +0 -92
  87. package/src/layouts/SupportLayout/index.ts +0 -8
  88. package/src/layouts/SupportLayout/types.ts +0 -21
  89. package/src/snippets/Chat/ChatUIContext.tsx +0 -110
  90. package/src/snippets/Chat/ChatWidget.tsx +0 -476
  91. package/src/snippets/Chat/README.md +0 -122
  92. package/src/snippets/Chat/components/MessageInput.tsx +0 -124
  93. package/src/snippets/Chat/components/MessageList.tsx +0 -169
  94. package/src/snippets/Chat/components/SessionList.tsx +0 -192
  95. package/src/snippets/Chat/components/index.ts +0 -9
  96. package/src/snippets/Chat/hooks/index.ts +0 -6
  97. package/src/snippets/Chat/hooks/useInfiniteSessions.ts +0 -82
  98. package/src/snippets/Chat/index.tsx +0 -45
  99. package/src/snippets/Chat/types.ts +0 -80
  100. package/src/snippets/ContactForm/ContactForm.tsx +0 -346
  101. package/src/snippets/ContactForm/ContactFormProvider.tsx +0 -153
  102. package/src/snippets/ContactForm/ContactInfo.tsx +0 -114
  103. package/src/snippets/ContactForm/ContactPage.tsx +0 -131
  104. package/src/snippets/ContactForm/dynamic.tsx +0 -55
  105. package/src/snippets/ContactForm/index.ts +0 -34
  106. 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
- }