diff --git a/.env b/.env new file mode 100644 index 0000000..032ccae --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +NEXT_PUBLIC_API_BASE_URL=https://api.gridape.com/api/v1 +NEXT_PUBLIC_CSRF_COOKIE_URL=https://api.gridape.com/sanctum/csrf-cookie +# NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8000/api/v1 +# NEXT_PUBLIC_CSRF_COOKIE_URL=http://127.0.0.1:8000/sanctum/csrf-cookie \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 13fe88b..f0b581a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,9 +1,26 @@ { - "extends": ["next/core-web-vitals", "plugin:@typescript-eslint/recommended", "prettier"], - "plugins": ["@typescript-eslint", "prettier"], + "extends": [ + "next/core-web-vitals", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "root": true, + "parserOptions": { + "project": "./tsconfig.json" + }, + "overrides": [ + { + "files": ["*.ts", "*.tsx"], + "parserOptions": { + "project": ["./tsconfig.json"] + } + } + ], "rules": { - "prettier/prettier": "error", - "@typescript-eslint/no-unused-vars": "error", - "@typescript-eslint/no-explicit-any": "warn" + "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], + "@typescript-eslint/explicit-module-boundary-types": "off", + "react/react-in-jsx-scope": "off" } } + diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea7..b5c68e5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve title: '' labels: '' assignees: '' - --- **Describe the bug** @@ -12,6 +11,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -24,15 +24,17 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] + +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] +- Version [e.g. 22] **Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] + +- Device: [e.g. iPhone6] +- OS: [e.g. iOS8.1] +- Browser [e.g. stock browser, safari] +- Version [e.g. 22] **Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md index 48d5f81..96a4735 100644 --- a/.github/ISSUE_TEMPLATE/custom.md +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -4,7 +4,4 @@ about: Describe this issue template's purpose here. title: '' labels: '' assignees: '' - --- - - diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7..2f28cea 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an idea for this project title: '' labels: '' assignees: '' - --- **Is your feature request related to a problem? Please describe.** diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts index 15b47d3..c25bb4d 100644 --- a/app/api/auth/login/route.ts +++ b/app/api/auth/login/route.ts @@ -1,7 +1,6 @@ -import { handleApiRequest } from '@/lib/api-utils' -import { NextRequest } from 'next/server' +import { handleApiRequest } from '@/lib/api-utils'; +import { NextRequest } from 'next/server'; export async function POST(request: NextRequest) { - return handleApiRequest(request, '/auth/login', 'POST') + return handleApiRequest(request, '/auth/login', 'POST'); } - diff --git a/app/api/auth/register/route.ts b/app/api/auth/register/route.ts index 2e2bd37..89c7e32 100644 --- a/app/api/auth/register/route.ts +++ b/app/api/auth/register/route.ts @@ -1,6 +1,6 @@ -import { handleApiRequest } from '@/lib/api-utils' -import { NextRequest } from 'next/server' +import { handleApiRequest } from '@/lib/api-utils'; +import { NextRequest } from 'next/server'; export async function POST(request: NextRequest) { - return handleApiRequest(request, '/auth/register', 'POST') -} \ No newline at end of file + return handleApiRequest(request, '/auth/register', 'POST'); +} diff --git a/app/api/auth/resend/route.ts b/app/api/auth/resend/route.ts index d1aafae..ec2ab28 100644 --- a/app/api/auth/resend/route.ts +++ b/app/api/auth/resend/route.ts @@ -1,6 +1,6 @@ -import { handleApiRequest } from '@/lib/api-utils' -import { NextRequest } from 'next/server' +import { handleApiRequest } from '@/lib/api-utils'; +import { NextRequest } from 'next/server'; export async function POST(request: NextRequest) { - return handleApiRequest(request, 'auth/resend-verification-mail', 'POST') -} \ No newline at end of file + return handleApiRequest(request, 'auth/resend-verification-mail', 'POST'); +} diff --git a/app/api/auth/set-cookie/route.ts b/app/api/auth/set-cookie/route.ts deleted file mode 100644 index c67d33c..0000000 --- a/app/api/auth/set-cookie/route.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; - -export async function POST(req: NextRequest) { - const { token } = await req.json(); - - return NextResponse.json( - { message: 'Cookie set' }, - { - headers: { - 'Set-Cookie': `token=${token}; HttpOnly; Secure; Path=/; SameSite=Strict;`, - }, - } - ); -} diff --git a/app/api/auth/verify/route.ts b/app/api/auth/verify/route.ts index 445914b..a86b412 100644 --- a/app/api/auth/verify/route.ts +++ b/app/api/auth/verify/route.ts @@ -1,6 +1,6 @@ -import { handleApiRequest } from '@/lib/api-utils' -import { NextRequest } from 'next/server' +import { handleApiRequest } from '@/lib/api-utils'; +import { NextRequest } from 'next/server'; export async function POST(request: NextRequest) { - return handleApiRequest(request, '/auth/email/verify', 'POST') -} \ No newline at end of file + return handleApiRequest(request, '/auth/email/verify', 'POST'); +} diff --git a/app/api/templates/route.ts b/app/api/templates/route.ts index fa5732e..502ddd8 100644 --- a/app/api/templates/route.ts +++ b/app/api/templates/route.ts @@ -3,4 +3,4 @@ import { NextRequest, NextResponse } from 'next/server'; export async function GET(request: NextRequest): Promise { return handleApiRequest(request, '/templates', 'GET'); -} \ No newline at end of file +} diff --git a/app/api/user/settings/add-domain/route.ts b/app/api/user/settings/add-domain/route.ts index c054272..74fc4e1 100644 --- a/app/api/user/settings/add-domain/route.ts +++ b/app/api/user/settings/add-domain/route.ts @@ -4,4 +4,3 @@ import { NextRequest, NextResponse } from 'next/server'; export async function POST(request: NextRequest): Promise { return handleApiRequest(request, '/user/domain-verification', 'POST'); } - diff --git a/app/auth/basic-info/page.tsx b/app/auth/basic-info/page.tsx deleted file mode 100644 index 5331939..0000000 --- a/app/auth/basic-info/page.tsx +++ /dev/null @@ -1,118 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { useRouter } from 'next/navigation'; -import { motion } from 'framer-motion'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { AuthCard } from '../auth-card'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; - -export default function BasicInfoPage(): JSX.Element { - const router = useRouter(); - const [loading, setLoading] = useState(false); - const [formData, setFormData] = useState({ - fullName: '', - phoneNumber: '', - country: '', - state: '', - }); - - // Explicitly define return type as Promise - const handleSubmit = async (e: React.FormEvent): Promise => { - e.preventDefault(); - setLoading(true); - - try { - // Simulated API call - await new Promise((resolve) => setTimeout(resolve, 1000)); - router.push('/auth/business-info'); - } catch (error) { - console.error(error); - } finally { - setLoading(false); - } - }; - - return ( - - -
handleSubmit(e)} className="space-y-4"> -
- - setFormData({ ...formData, fullName: e.target.value })} - required - /> -
-
- - setFormData({ ...formData, phoneNumber: e.target.value })} - required - /> -
-
-
- - -
-
- - -
-
- -
-
-
- ); -} diff --git a/app/auth/business-info/page.tsx b/app/auth/business-info/page.tsx deleted file mode 100644 index b2df004..0000000 --- a/app/auth/business-info/page.tsx +++ /dev/null @@ -1,98 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { useRouter } from 'next/navigation'; -import { motion } from 'framer-motion'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { AuthCard } from '../auth-card'; -import Head from 'next/head'; - -export default function BusinessInfoPage() { - const router = useRouter(); - const [loading, setLoading] = useState(false); - const [formData, setFormData] = useState({ - businessName: '', - businessEmail: '', - businessPhone: '', - businessAddress: '', - }); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setLoading(true); - - try { - // Simulated API call - await new Promise((resolve) => setTimeout(resolve, 1000)); - router.push('/dashboard'); - } catch (error) { - console.error(error); - } finally { - setLoading(false); - } - }; - - return ( - - -
-
- - setFormData({ ...formData, businessName: e.target.value })} - required - /> -
-
- - setFormData({ ...formData, businessEmail: e.target.value })} - required - /> -
-
- - setFormData({ ...formData, businessPhone: e.target.value })} - required - /> -
-
- - setFormData({ ...formData, businessAddress: e.target.value })} - required - /> -
- -
-
-
- ); -} diff --git a/app/auth/login/page.tsx b/app/auth/login/page.tsx index 02c2ee5..5fdf042 100644 --- a/app/auth/login/page.tsx +++ b/app/auth/login/page.tsx @@ -1,7 +1,6 @@ 'use client'; import { useState } from 'react'; -import { useRouter } from 'next/navigation'; import Link from 'next/link'; import { motion } from 'framer-motion'; import { Button } from '@/components/ui/button'; @@ -14,7 +13,6 @@ import { useAuth } from '@/hooks/useAuth'; import { useAuthStore } from '@/store/authStore'; export default function LoginPage() { - const router = useRouter(); const { toast } = useToast(); const [formData, setFormData] = useState({ @@ -23,11 +21,10 @@ export default function LoginPage() { rememberMe: false, }); const auth = useAuth(); - const {user, loading} = useAuthStore() + const { user, loading } = useAuthStore(); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - // setLoading(true); try { await auth.login(formData.email, formData.password); @@ -37,8 +34,6 @@ export default function LoginPage() { title: 'Error', description: error instanceof Error ? error.message : 'Failed to login', }); - } finally { - // setLoading(false); } }; @@ -50,7 +45,7 @@ export default function LoginPage() { transition={{ duration: 0.3 }} > -
+ void handleSubmit(e)} className="space-y-4"> {user?.email || 3}
diff --git a/app/auth/otp/otp-verification-form.tsx b/app/auth/otp/otp-verification-form.tsx new file mode 100644 index 0000000..e29bc2f --- /dev/null +++ b/app/auth/otp/otp-verification-form.tsx @@ -0,0 +1,114 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { motion } from 'framer-motion'; +import { Button } from '@/components/ui/button'; +import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp'; +import { useToast } from '@/hooks/use-toast'; +import { AuthCard } from '../auth-card'; +import { REGEXP_ONLY_DIGITS_AND_CHARS } from 'input-otp'; +import { useAuth } from '@/hooks/useAuth'; + +export default function OtpVerificationForm(): JSX.Element { + const router = useRouter(); + const searchParams = useSearchParams(); + const email = searchParams.get('email') || ''; + const { toast } = useToast(); + const [loading, setLoading] = useState(false); + const [otp, setOtp] = useState(''); + const auth = useAuth(); + + const handleSubmit = async (e: React.FormEvent): Promise => { + e.preventDefault(); + setLoading(true); + try { + await auth.verify(email, otp); + toast({ + title: 'Success', + description: 'Email verified successfully', + }); + router.push('/auth/login'); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Failed to verify OTP'; + + toast({ + variant: 'destructive', + title: 'Error', + description: errorMessage, + }); + } finally { + setLoading(false); + } + }; + + const handleResendOtp = async (e: React.FormEvent): Promise => { + e.preventDefault(); + try { + await auth.resend(email); + toast({ + title: 'Success', + description: 'OTP resent successfully', + }); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Failed to resend OTP'; + + toast({ + variant: 'destructive', + title: 'Error', + description: errorMessage, + }); + } + }; + + return ( + + + void handleSubmit(e)} className="space-y-4"> +

+ Enter OTP sent to {email} +

+
+ + + + + + + + + +
+ + + +
+
+ ); +} + diff --git a/app/auth/otp/page.tsx b/app/auth/otp/page.tsx index f3331f1..b4e37e9 100644 --- a/app/auth/otp/page.tsx +++ b/app/auth/otp/page.tsx @@ -1,110 +1,10 @@ -'use client'; - -import { useState } from 'react'; -import { useRouter, useSearchParams } from 'next/navigation'; -import { motion } from 'framer-motion'; -import { Button } from '@/components/ui/button'; -import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp'; -import { useToast } from '@/hooks/use-toast'; -import { AuthCard } from '../auth-card'; -import { REGEXP_ONLY_DIGITS_AND_CHARS } from 'input-otp'; -import { useAuth } from '@/hooks/useAuth'; - -export default function OtpPage() { - const router = useRouter(); - const searchParams = useSearchParams(); - const email = searchParams.get('email') || ''; - const { toast } = useToast(); - const [loading, setLoading] = useState(false); - const [otp, setOtp] = useState(''); - - const auth = useAuth(); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setLoading(true); - - try { - await auth.verify(email, otp); - toast({ - title: 'Success', - description: 'Email verified successfully', - }); - router.push('/auth/login'); - } catch (error: any) { - toast({ - variant: 'destructive', - title: 'Error', - description: error?.message || 'Failed to verify OTP', - }); - } finally { - setLoading(false); - } - }; - - const handleResendOtp = async () => { - try { - await auth.resend(email); - toast({ - title: 'Success', - description: 'OTP resent successfully', - }); - } catch (error: any) { - toast({ - variant: 'destructive', - title: 'Error', - description: error?.message || 'Failed to resend OTP', - }); - } - }; +import { Suspense } from 'react'; +import OtpVerificationForm from './otp-verification-form'; +export default function OtpPage(): JSX.Element { return ( - - -
-

- Enter OTP sent to {email} -

-
- - - - - - - - - -
- - -
-
-
+ Loading...
}> + + ); } diff --git a/app/auth/page.tsx b/app/auth/page.tsx index da9c15f..420e11c 100644 --- a/app/auth/page.tsx +++ b/app/auth/page.tsx @@ -1,414 +1,7 @@ -'use client'; +import React from 'react'; -import { useState } from 'react'; -import { motion, AnimatePresence } from 'framer-motion'; -import { useRouter } from 'next/navigation'; -import Link from 'next/link'; -import { Check, Upload } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Checkbox } from '@/components/ui/checkbox'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; - -// Simulated API calls -const api = { - register: async (data: any) => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - return { success: true }; - }, - verifyEmail: async (email: string, otp: string) => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - return { success: true }; - }, - resendVerification: async (email: string) => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - return { success: true }; - }, - login: async (data: any) => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - return { success: true }; - }, +const page = () => { + return
page
; }; -export default function Component() { - const router = useRouter(); - const [step, setStep] = useState(1); - const [formData, setFormData] = useState({ - email: '', - password: '', - confirmPassword: '', - rememberMe: false, - fullName: '', - phoneNumber: '', - country: '', - state: '', - businessName: '', - businessEmail: '', - businessPhone: '', - businessAddress: '', - logo: null as File | null, - }); - const [otp, setOtp] = useState(['', '', '', '', '']); - const [loading, setLoading] = useState(false); - const [mode, setMode] = useState<'login' | 'register'>('login'); - - const handleOtpChange = (index: number, value: string) => { - if (value.length <= 1) { - const newOtp = [...otp]; - newOtp[index] = value; - setOtp(newOtp); - - // Auto-focus next input - if (value && index < 4) { - const nextInput = document.querySelector( - `input[name="otp-${index + 1}"]` - ) as HTMLInputElement; - nextInput?.focus(); - } - } - }; - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setLoading(true); - - try { - if (mode === 'login') { - const res = await api.login({ - email: formData.email, - password: formData.password, - }); - if (res.success) { - router.push('/dashboard'); - } - } else { - if (step === 1) { - const res = await api.register(formData); - if (res.success) setStep(2); - } else if (step === 2) { - const res = await api.verifyEmail(formData.email, otp.join('')); - if (res.success) setStep(3); - } else if (step === 3) { - setStep(4); - } else if (step === 4) { - router.push('/dashboard'); - } - } - } catch (error) { - console.error(error); - } finally { - setLoading(false); - } - }; - - const slideAnimation = { - initial: { x: 20, opacity: 0 }, - animate: { x: 0, opacity: 1 }, - exit: { x: -20, opacity: 0 }, - }; - - return ( -
- - - - -
- - - -
- - {mode === 'login' - ? 'Login' - : step === 1 - ? 'Sign up' - : step === 2 - ? 'Verify Email' - : step === 3 - ? 'Basic Information' - : 'Business Information'} - -
- -
- {mode === 'login' ? ( - <> -
- - setFormData({ ...formData, email: e.target.value })} - required - /> -
-
- - setFormData({ ...formData, password: e.target.value })} - required - /> -
-
-
- - setFormData({ ...formData, rememberMe: checked as boolean }) - } - /> - -
- - Forgot password? - -
- - ) : step === 1 ? ( - <> -
- - setFormData({ ...formData, email: e.target.value })} - required - /> -
-
- - setFormData({ ...formData, password: e.target.value })} - required - /> -
-
- - - setFormData({ ...formData, confirmPassword: e.target.value }) - } - required - /> -
- - ) : step === 2 ? ( -
-

- Enter OTP sent to {formData.email} -

-
- {otp.map((digit, index) => ( - handleOtpChange(index, e.target.value)} - required - /> - ))} -
- -
- ) : step === 3 ? ( - <> -
- - setFormData({ ...formData, fullName: e.target.value })} - required - /> -
-
- - setFormData({ ...formData, phoneNumber: e.target.value })} - required - /> -
-
-
- - -
-
- - -
-
- - ) : ( - <> -
- - setFormData({ ...formData, businessName: e.target.value })} - required - /> -
-
- - - setFormData({ ...formData, businessEmail: e.target.value }) - } - required - /> -
-
- - - setFormData({ ...formData, businessPhone: e.target.value }) - } - required - /> -
-
- - - setFormData({ ...formData, businessAddress: e.target.value }) - } - required - /> -
- - )} - - - -
- {mode === 'login' ? ( -

- Don't have an account?{' '} - -

- ) : ( -

- Already have an account?{' '} - -

- )} -
-
-
-
-
-
-
- ); -} +export default page; diff --git a/app/auth/register/page.tsx b/app/auth/register/page.tsx index 880af83..628edb8 100644 --- a/app/auth/register/page.tsx +++ b/app/auth/register/page.tsx @@ -23,7 +23,7 @@ export default function RegisterPage() { last_name: '', }); - const { register, loading } = useAuthStore(); + const { loading } = useAuthStore(); const auth = useAuth(); const handleSubmit = async (e: React.FormEvent) => { @@ -70,7 +70,12 @@ export default function RegisterPage() { transition={{ duration: 0.3 }} > -
+ { + void handleSubmit(e); + }} + className="space-y-4" + >
([]); - const [newContact, setNewContact] = useState< - Omit - >({ - first_name: '', - last_name: '', - email: '', - phone: '', - group: '', - address: '', - }); +// export default function AudiencePage() { +// const [activeTab, setActiveTab] = useState('all-contacts'); +// const [isAddContactOpen, setIsAddContactOpen] = useState(false); +// const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); +// const [searchTerm, setSearchTerm] = useState(''); +// const [selectedContacts, setSelectedContacts] = useState([]); +// const [newContact, setNewContact] = useState< +// Omit +// >({ +// first_name: '', +// last_name: '', +// email: '', +// phone: '', +// group: '', +// address: '', +// }); - const { contacts, loading, listAllContacts, addContact } = useContactStore(); +// const { contacts, loading, listAllContacts, addContact } = useContactStore(); - useEffect(() => { - listAllContacts(); - }, [listAllContacts]); +// useEffect(() => { +// listAllContacts().catch((error) => console.error(error)); +// }, [listAllContacts]); - const containerVariants = { - hidden: { opacity: 0 }, - visible: { opacity: 1, transition: { duration: 0.5 } }, - }; +// const containerVariants = { +// hidden: { opacity: 0 }, +// visible: { opacity: 1, transition: { duration: 0.5 } }, +// }; - const filteredContacts = - contacts?.filter( - (contact) => - `${contact.first_name} ${contact.last_name}` - .toLowerCase() - .includes(searchTerm.toLowerCase()) || - contact.email.toLowerCase().includes(searchTerm.toLowerCase()) || - contact.phone.includes(searchTerm) - ) || []; +// const filteredContacts = +// contacts?.filter( +// (contact) => +// `${contact.first_name} ${contact.last_name}` +// .toLowerCase() +// .includes(searchTerm.toLowerCase()) || +// contact.email.toLowerCase().includes(searchTerm.toLowerCase()) || +// contact.phone.includes(searchTerm) +// ) || []; - const handleAddContact = async () => { - if (newContact.first_name && newContact.last_name && newContact.email && newContact.phone) { - try { - await addContact( - newContact.first_name, - newContact.last_name, - newContact.email, - newContact.phone, - newContact.group - ); - setIsAddContactOpen(false); - toast({ - title: 'Contact Added', - description: 'Your new contact has been successfully added.', - variant: 'success', - }); +// const handleAddContact: () => Promise = async () => { +// if (newContact.first_name && newContact.last_name && newContact.email && newContact.phone) { +// try { +// await addContact( +// newContact.first_name, +// newContact.last_name, +// newContact.email, +// newContact.phone, +// newContact.group +// ); +// setIsAddContactOpen(false); +// toast({ +// title: 'Contact Added', +// description: 'Your new contact has been successfully added.', +// variant: 'default', +// }); - // Reset form - setNewContact({ - first_name: '', - last_name: '', - email: '', - phone: '', - group: '', - address: '', - }); - } catch (error) { - toast({ - title: 'Error', - description: 'Failed to add contact. Please try again.', - variant: 'destructive', - }); - } - } else { - toast({ - title: 'Incomplete Information', - description: 'Please fill all required fields.', - variant: 'warning', - }); - } - }; +// // Reset form +// setNewContact({ +// first_name: '', +// last_name: '', +// email: '', +// phone: '', +// group: '', +// address: '', +// }); +// } catch (error: unknown) { +// // Type guard to ensure error is an instance of Error +// if (error instanceof Error) { +// toast({ +// title: 'Error', +// description: error.message || 'Failed to add contact. Please try again.', +// variant: 'destructive', +// }); +// } else { +// // If error is not an instance of Error, handle it gracefully +// toast({ +// title: 'Error', +// description: 'Failed to add contact. Please try again.', +// variant: 'destructive', +// }); +// } +// } +// } else { +// toast({ +// title: 'Incomplete Information', +// description: 'Please fill all required fields.', +// variant: 'default', +// }); +// } +// }; - const handleSelectContact = (contactId: string) => { - setSelectedContacts((prev) => - prev.includes(contactId) ? prev.filter((id) => id !== contactId) : [...prev, contactId] - ); - }; +// const handleSelectContact = (contactId: string) => { +// setSelectedContacts((prev) => +// prev.includes(contactId) ? prev.filter((id) => id !== contactId) : [...prev, contactId] +// ); +// }; - const handleDeleteContacts = async () => { - try { - await deleteMultipleContacts(selectedContacts); - setSelectedContacts([]); - setIsDeleteModalOpen(false); - toast({ - title: 'Contacts Deleted', - description: 'Selected contacts have been successfully removed.', - variant: 'success', - }); - } catch (error) { - toast({ - title: 'Delete Failed', - description: 'Unable to delete contacts. Please try again.', - variant: 'destructive', - }); - } - }; +// const handleDeleteContacts: () => Promise = async () => { +// try { +// // await deleteMultipleContacts(selectedContacts); +// setSelectedContacts([]); +// setIsDeleteModalOpen(false); +// toast({ +// title: 'Contacts Deleted', +// description: 'Selected contacts have been successfully removed.', +// variant: 'default', +// }); +// } catch (error) { +// toast({ +// title: 'Delete Failed', +// description: 'Unable to delete contacts. Please try again.', +// variant: 'destructive', +// }); +// } +// }; - return ( - -
-

Audience Management

-
- - -
-
+// return ( +// +//
+//

Audience Management

+//
+// +// +//
+//
- {loading ? ( -
Loading contacts...
- ) : contacts && contacts.length > 0 ? ( - <> -
- - - - All Contacts - - - Groups - - - -
- - setSearchTerm(e.target.value)} - /> -
-
+// {loading ? ( +//
Loading contacts...
+// ) : contacts && contacts.length > 0 ? ( +// <> +//
+// +// +// +// All Contacts +// +// +// Groups +// +// +// +//
+// +// setSearchTerm(e.target.value)} +// /> +//
+//
- {selectedContacts.length > 0 && ( - - {selectedContacts.length} contacts selected - - - )} +// {selectedContacts.length > 0 && ( +// +// {selectedContacts.length} contacts selected +// +// +// )} -
- - - - - - setSelectedContacts( - selectedContacts.length === filteredContacts.length - ? [] - : filteredContacts.map((c) => c.id) - ) - } - /> - - Full Name - Email Address - Phone Number - Group - Actions - - - - {filteredContacts.map((contact) => ( - - - handleSelectContact(contact.id)} - /> - - {`${contact.first_name} ${contact.last_name}`} - {contact.email} - {contact.phone} - {contact.group || 'Unassigned'} - - - - - - - - Edit - - deleteContact(contact.id)} - > - Delete - - - - - - ))} - -
-
- - ) : ( - -
- -

Grow your Audience

-

- Here is where you will add and manage your contacts. Once your first contact is added, - you will be able to send your first campaign. -

- -
-
- )} +//
+// +// +// +// +// +// setSelectedContacts( +// selectedContacts.length === filteredContacts.length +// ? [] +// : filteredContacts.map((c) => c.id) +// ) +// } +// /> +// +// Full Name +// Email Address +// Phone Number +// Group +// Actions +// +// +// +// {filteredContacts.map((contact) => ( +// +// +// handleSelectContact(contact.id)} +// /> +// +// {`${contact.first_name} ${contact.last_name}`} +// {contact.email} +// {contact.phone} +// {contact.group || 'Unassigned'} +// +// +// +// +// +// +// +// Edit +// +// +// Delete +// +// +// +// +// +// ))} +// +//
+//
+// +// ) : ( +// +//
+// +//

Grow your Audience

+//

+// Here is where you will add and manage your contacts. Once your first contact is added, +// you will be able to send your first campaign. +//

+// +//
+//
+// )} - {/* Add Contact Sheet */} - - - - Create New Contact - Fill in the contact details carefully. - -
-
- - setNewContact({ ...newContact, first_name: e.target.value })} - className="col-span-3" - /> -
-
- - setNewContact({ ...newContact, last_name: e.target.value })} - className="col-span-3" - /> -
-
- - setNewContact({ ...newContact, email: e.target.value })} - className="col-span-3" - /> -
-
- - setNewContact({ ...newContact, phone: e.target.value })} - className="col-span-3" - /> -
-
- - -
-
- - - - - - -
-
+// {/* Add Contact Sheet */} +// +// +// +// Create New Contact +// Fill in the contact details carefully. +// +//
+//
+// +// setNewContact({ ...newContact, first_name: e.target.value })} +// className="col-span-3" +// /> +//
+//
+// +// setNewContact({ ...newContact, last_name: e.target.value })} +// className="col-span-3" +// /> +//
+//
+// +// setNewContact({ ...newContact, email: e.target.value })} +// className="col-span-3" +// /> +//
+//
+// +// setNewContact({ ...newContact, phone: e.target.value })} +// className="col-span-3" +// /> +//
+//
+// +// +//
+//
+// +// +// +// +// +// +//
+//
- {/* Delete Confirmation Dialog */} - - - - Delete Contacts - - Are you sure you want to delete {selectedContacts.length} contact(s)? This action - cannot be undone. - - - - - - - - -
- ); -} +// {/* Delete Confirmation Dialog */} +// +// +// +// Delete Contacts +// +// Are you sure you want to delete {selectedContacts.length} contact(s)? This action +// cannot be undone. +// +// +// +// +// +// +// +// +//
+// ); +// } + +import React from 'react'; + +const page = () => { + return
page
; +}; + +export default page; diff --git a/app/dashboard/campaign/create/page.tsx b/app/dashboard/campaign/create/page.tsx index 6719cc6..ddd23b5 100644 --- a/app/dashboard/campaign/create/page.tsx +++ b/app/dashboard/campaign/create/page.tsx @@ -1,9 +1,7 @@ import EmailComposer from '@/shared/EmailCreate/EmailComposer'; -import { useAuthStore } from '@/store/authStore'; import React from 'react'; const Page = () => { - return ; }; diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index b26d3a4..a92813d 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,7 +1,6 @@ 'use client'; -import { motion } from 'framer-motion'; -import { Mail, MousePointerClick, MailOpenIcon, MailCheck } from 'lucide-react'; +import { MousePointerClick, MailOpenIcon, MailCheck } from 'lucide-react'; import { BarChart, Bar, XAxis, CartesianGrid } from 'recharts'; import { useAuthStore } from '@/store/authStore'; import { useEffect, useState } from 'react'; @@ -28,26 +27,27 @@ import { import { Button } from '@/components/ui/button'; import { useRouter } from 'next/navigation'; +interface DashboardResponse { + data: DashboardTypes; +} const Dashboard = () => { const { user } = useAuthStore(); const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); - const router = useRouter() + // const [loading, setLoading] = useState(true); + const router = useRouter(); useEffect(() => { const fetchData = async () => { try { const response = await fetch('/api/user/dashboard', { credentials: 'include' }); if (!response.ok) throw new Error('Failed to fetch data'); - const responseData = await response.json(); + const responseData = await response.json() as DashboardResponse; setData(responseData.data); } catch (error) { console.error(error); - } finally { - setLoading(false); } }; - fetchData(); + void fetchData(); }, []); const chartConfig = { @@ -69,15 +69,21 @@ const Dashboard = () => {
- - - + {/* {[ { icon: Mail, label: 'Create Email', color: 'text-blue-600' }, { icon: MousePointerClick, label: 'Create Campaigns', color: 'text-purple-600' }, @@ -163,8 +169,7 @@ const Dashboard = () => { tickLine={false} tickMargin={10} axisLine={false} - tickFormatter={(value) => value.slice(0, 3)} - /> + tickFormatter={(value: string): string => value.slice(0, 3)} /> } /> } /> diff --git a/app/dashboard/settings/page.tsx b/app/dashboard/settings/page.tsx index 2b02ceb..489845c 100644 --- a/app/dashboard/settings/page.tsx +++ b/app/dashboard/settings/page.tsx @@ -1,14 +1,3 @@ -import { Button } from '@/components/ui/button'; -import { - Card, - CardHeader, - CardTitle, - CardDescription, - CardContent, - CardFooter, -} from '@/components/ui/card'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; import DomainSettings from '@/shared/DomainSettings'; import ProfileSettings from '@/shared/ProfileSettings'; import { Tabs, TabsList, TabsTrigger, TabsContent } from '@radix-ui/react-tabs'; diff --git a/app/dashboard/templates/all/page.tsx b/app/dashboard/templates/all/page.tsx index 3b21170..25ae452 100644 --- a/app/dashboard/templates/all/page.tsx +++ b/app/dashboard/templates/all/page.tsx @@ -13,9 +13,8 @@ import { SelectValue, } from '@/components/ui/select'; import { Input } from '@/components/ui/input'; -import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'; import { TemplateTypes } from '@/types/interface'; -import { cn } from '@/lib/utils'; export default function TemplatesPage() { const [templates, setTemplates] = useState([]); @@ -27,7 +26,7 @@ export default function TemplatesPage() { const iframeRefs = useRef<{ [key: string]: HTMLIFrameElement | null }>({}); useEffect(() => { - async function fetchTemplates() { + async function fetchTemplates(): Promise { try { setLoading(true); const response = await fetch('/api/templates'); @@ -60,11 +59,13 @@ export default function TemplatesPage() { }, [templates, searchQuery, categoryFilter]); // Filtering logic - const filteredTemplates = templates.filter(template => - (searchQuery === '' || template.name.toLowerCase().includes(searchQuery.toLowerCase())) && - (categoryFilter === 'all' || template.category === categoryFilter) + const filteredTemplates = templates.filter( + (template) => + (searchQuery === '' || template.name.toLowerCase().includes(searchQuery.toLowerCase())) && + (categoryFilter === 'all' || template.category === categoryFilter) ); + return (
@@ -77,13 +78,16 @@ export default function TemplatesPage() {
- setSearchQuery(e.target.value)} /> - +
setTemplateName(e.target.value)} - className="col-span-3" - /> -
- -
- - - - ); -} diff --git a/app/dashboard/templates/create/page.tsx b/app/dashboard/templates/create/page.tsx index 326995f..aa098a8 100644 --- a/app/dashboard/templates/create/page.tsx +++ b/app/dashboard/templates/create/page.tsx @@ -1,431 +1,438 @@ -'use client'; +// 'use client'; -import { useState, useRef, useCallback, useEffect } from 'react'; -import { motion, AnimatePresence } from 'framer-motion'; -import EmailEditor, { EditorRef, EmailEditorProps } from 'react-email-editor'; -import { Button } from '@/components/ui/button'; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from '@/components/ui/card'; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '@/components/ui/dialog'; -import { Label } from '@/components/ui/label'; -import { Input } from '@/components/ui/input'; -import { Textarea } from '@/components/ui/textarea'; -import { Toast } from '@/components/ui/toast'; -import { - Loader2, - Save, - Eye, - Upload, - Download, - Undo, - Redo, - Image as ImageIcon, - Variable, -} from 'lucide-react'; -import { useToast } from '@/hooks/use-toast'; -import { useTemplateStore } from '@/store/templateStore'; -import { useRouter } from 'next/navigation'; +// import { useState, useRef, useCallback, useEffect } from 'react'; +// import { motion, AnimatePresence } from 'framer-motion'; +// import EmailEditor, { EditorRef, EmailEditorProps } from 'react-email-editor'; +// import { Button } from '@/components/ui/button'; +// import { +// Card, +// CardContent, +// CardDescription, +// CardFooter, +// CardHeader, +// CardTitle, +// } from '@/components/ui/card'; +// import { +// Dialog, +// DialogContent, +// DialogHeader, +// DialogTitle, +// DialogTrigger, +// } from '@/components/ui/dialog'; +// import { Label } from '@/components/ui/label'; +// import { Input } from '@/components/ui/input'; +// import { Textarea } from '@/components/ui/textarea'; +// import { Toast } from '@/components/ui/toast'; +// import { +// Loader2, +// Save, +// Eye, +// Upload, +// Download, +// Undo, +// Redo, +// Variable, +// } from 'lucide-react'; +// import { useToast } from '@/hooks/use-toast'; +// import { useTemplateStore } from '@/store/templateStore'; +// import { useRouter } from 'next/navigation'; -export default function EmailTemplateEditor() { - const emailEditorRef = useRef(null); - const [templateName, setTemplateName] = useState(''); - const [previewMode, setPreviewMode] = useState(false); - const [jsonData, setJsonData] = useState(''); - const [isLoading, setIsLoading] = useState(true); - const [canUndo, setCanUndo] = useState(false); - const [canRedo, setCanRedo] = useState(false); - const [showVariableDialog, setShowVariableDialog] = useState(false); - const [variableName, setVariableName] = useState(''); - const [variableDescription, setVariableDescription] = useState(''); - const [variables, setVariables] = useState<{ name: string; description: string }[]>([]); - const router = useRouter(); - const { toast } = useToast(); - const { templates, saveTemplate, listAllTemplates, loading } = useTemplateStore(); +// export default function EmailTemplateEditor() { +// const emailEditorRef = useRef(null); +// const [templateName, setTemplateName] = useState(''); +// const [previewMode, setPreviewMode] = useState(false); +// const [jsonData, setJsonData] = useState(''); +// const [isLoading, setIsLoading] = useState(true); +// const [canUndo, setCanUndo] = useState(false); +// const [canRedo, setCanRedo] = useState(false); +// const [showVariableDialog, setShowVariableDialog] = useState(false); +// const [variableName, setVariableName] = useState(''); +// const [variableDescription, setVariableDescription] = useState(''); +// const [variables, setVariables] = useState<{ name: string; description: string }[]>([]); +// const router = useRouter(); +// const { toast } = useToast(); +// const { saveTemplate } = useTemplateStore(); - const onReady: EmailEditorProps['onReady'] = useCallback( - (unlayer: any) => { - setIsLoading(false); - unlayer.addEventListener('design:updated', () => { - setCanUndo(unlayer.isUndoable()); - setCanRedo(unlayer.isRedoable()); - }); +// const onReady: EmailEditorProps['onReady'] = useCallback( +// (unlayer: any) => { +// setIsLoading(false); +// unlayer.addEventListener('design:updated', () => { +// setCanUndo(unlayer.isUndoable()); +// setCanRedo(unlayer.isRedoable()); +// }); - // Register custom variable tool - unlayer.registerTool({ - name: 'variable', - label: 'Variable', - icon: 'fa-tag', - supportedDisplayModes: ['web', 'email'], - options: { - default: { - title: null, - }, - text: { - title: 'Text', - position: 1, - options: { - variable: { - label: 'Variable', - defaultValue: '', - widget: 'dropdown', - data: { - options: variables.map((v) => ({ - value: `{{${v.name}}}`, - label: v.name, - })), - }, - }, - }, - }, - }, - values: {}, - renderer: { - Viewer: (props: any) => { - return {props.values.variable}; - }, - Export: (props: any) => { - return {props.values.variable}; - }, - }, - }); - }, - [variables] - ); +// // Register custom variable tool +// unlayer.registerTool({ +// name: 'variable', +// label: 'Variable', +// icon: 'fa-tag', +// supportedDisplayModes: ['web', 'email'], +// options: { +// default: { +// title: null, +// }, +// text: { +// title: 'Text', +// position: 1, +// options: { +// variable: { +// label: 'Variable', +// defaultValue: '', +// widget: 'dropdown', +// data: { +// options: variables.map((v) => ({ +// value: `{{${v.name}}}`, +// label: v.name, +// })), +// }, +// }, +// }, +// }, +// }, +// values: {}, +// renderer: { +// Viewer: (props: any) => { +// return {props.values.variable}; +// }, +// Export: (props: any) => { +// return {props.values.variable}; +// }, +// }, +// }); +// }, +// [variables] +// ); - const saveDesign = useCallback(() => { - emailEditorRef.current?.editor?.saveDesign((design: any) => { - setJsonData(JSON.stringify(design)); - saveTemplate({ - name: templateName, - content: jsonData, - }) - .then(() => { - toast({ - title: 'Design Saved', - description: 'Your email template design has been saved.', - }); - router.push('/dashboard/templates/saved'); - }) - .catch(() => { - toast({ - title: 'Error', - description: 'Failed to save template. Please try again later.', - variant: 'destructive', - }); - }); - }); - }, [toast, saveTemplate, templateName]); +// const saveDesign = useCallback(() => { +// emailEditorRef.current?.editor?.saveDesign((design: any) => { +// setJsonData(JSON.stringify(design)); +// saveTemplate({ +// name: templateName, +// content: jsonData, +// }) +// .then(() => { +// toast({ +// title: 'Design Saved', +// description: 'Your email template design has been saved.', +// }); +// router.push('/dashboard/templates/saved'); +// }) +// .catch(() => { +// toast({ +// title: 'Error', +// description: 'Failed to save template. Please try again later.', +// variant: 'destructive', +// }); +// }); +// }); +// }, [toast, saveTemplate, templateName]); - const exportHtml = useCallback(() => { - emailEditorRef.current?.editor?.exportHtml((data) => { - const { html } = data; - console.log('HTML Output:', html); - const blob = new Blob([html], { type: 'text/html' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `${templateName || 'email-template'}.html`; - a.click(); - URL.revokeObjectURL(url); - toast({ - title: 'Template Exported', - description: 'Your email template has been exported as HTML.', - }); - }); - }, [templateName, toast]); +// const exportHtml = useCallback(() => { +// emailEditorRef.current?.editor?.exportHtml((data) => { +// const { html } = data; +// console.log('HTML Output:', html); +// const blob = new Blob([html], { type: 'text/html' }); +// const url = URL.createObjectURL(blob); +// const a = document.createElement('a'); +// a.href = url; +// a.download = `${templateName || 'email-template'}.html`; +// a.click(); +// URL.revokeObjectURL(url); +// toast({ +// title: 'Template Exported', +// description: 'Your email template has been exported as HTML.', +// }); +// }); +// }, [templateName, toast]); - const togglePreview = useCallback(() => { - if (previewMode) { - emailEditorRef.current?.editor?.hidePreview(); - } else { - emailEditorRef.current?.editor?.showPreview('desktop'); - } - setPreviewMode((prev) => !prev); - }, [previewMode]); +// const togglePreview = useCallback(() => { +// if (previewMode) { +// emailEditorRef.current?.editor?.hidePreview(); +// } else { +// emailEditorRef.current?.editor?.showPreview('desktop'); +// } +// setPreviewMode((prev) => !prev); +// }, [previewMode]); - const loadDesign = useCallback(() => { - try { - const design = JSON.parse(jsonData); - emailEditorRef.current?.editor?.loadDesign(design); - toast({ - title: 'Design Loaded', - description: 'Your email template design has been loaded.', - }); - } catch (error) { - toast({ - title: 'Error', - description: `Failed to load design. Please check your JSON data. ${error}`, - variant: 'destructive', - }); - } - }, [jsonData, toast]); +// const loadDesign = useCallback(() => { +// try { +// const design = JSON.parse(jsonData); +// emailEditorRef.current?.editor?.loadDesign(design); +// toast({ +// title: 'Design Loaded', +// description: 'Your email template design has been loaded.', +// }); +// } catch (error) { +// toast({ +// title: 'Error', +// description: `Failed to load design. Please check your JSON data. ${error}`, +// variant: 'destructive', +// }); +// } +// }, [jsonData, toast]); - const undoAction = useCallback(() => { - emailEditorRef.current?.editor?.undo(); - }, []); +// const undoAction = useCallback(() => { +// emailEditorRef.current?.editor?.undo(); +// }, []); - const redoAction = useCallback(() => { - emailEditorRef.current?.editor?.redo(); - }, []); +// const redoAction = useCallback(() => { +// emailEditorRef.current?.editor?.redo(); +// }, []); - const handleImageUpload = useCallback( - (file: File, onSuccess: (url: string) => void) => { - // Here you would typically upload the file to your server or a file storage service - // For this example, we'll create a local object URL - const reader = new FileReader(); - reader.onload = (e) => { - const dataUrl = e.target?.result as string; - onSuccess(dataUrl); - toast({ - title: 'Image Uploaded', - description: 'Your image has been successfully uploaded.', - }); - }; - reader.readAsDataURL(file); - }, - [toast] - ); +// const handleImageUpload = useCallback( +// (file: File, onSuccess: (url: string) => void) => { +// // Here you would typically upload the file to your server or a file storage service +// // For this example, we'll create a local object URL +// const reader = new FileReader(); +// reader.onload = (e) => { +// const dataUrl = e.target?.result as string; +// onSuccess(dataUrl); +// toast({ +// title: 'Image Uploaded', +// description: 'Your image has been successfully uploaded.', +// }); +// }; +// reader.readAsDataURL(file); +// }, +// [toast] +// ); - const addVariable = useCallback(() => { - if (variableName) { - setVariables((prev) => [...prev, { name: variableName, description: variableDescription }]); - setVariableName(''); - setVariableDescription(''); - setShowVariableDialog(false); - toast({ - title: 'Variable Added', - description: `Variable {{${variableName}}} has been added to the template.`, - }); - } - }, [variableName, variableDescription, toast]); +// const addVariable = useCallback(() => { +// if (variableName) { +// setVariables((prev) => [...prev, { name: variableName, description: variableDescription }]); +// setVariableName(''); +// setVariableDescription(''); +// setShowVariableDialog(false); +// toast({ +// title: 'Variable Added', +// description: `Variable {{${variableName}}} has been added to the template.`, +// }); +// } +// }, [variableName, variableDescription, toast]); - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - if (e.ctrlKey || e.metaKey) { - if (e.key === 'z') { - e.preventDefault(); - undoAction(); - } else if (e.key === 'y') { - e.preventDefault(); - redoAction(); - } - } - }; +// useEffect(() => { +// const handleKeyDown = (e: KeyboardEvent) => { +// if (e.ctrlKey || e.metaKey) { +// if (e.key === 'z') { +// e.preventDefault(); +// undoAction(); +// } else if (e.key === 'y') { +// e.preventDefault(); +// redoAction(); +// } +// } +// }; - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [undoAction, redoAction]); +// window.addEventListener('keydown', handleKeyDown); +// return () => window.removeEventListener('keydown', handleKeyDown); +// }, [undoAction, redoAction]); - return ( - -

Email Template Editor

- - - Template Editor - Create your email template using drag and drop - - - - {isLoading && ( - - - - )} - - { - emailEditorRef.current?.editor?.addEventListener( - 'image:added', - (file: File, done: (arg0: { progress: number; url: string }) => void) => { - if (file) { - handleImageUpload(file, (url) => { - done({ progress: 100, url }); - }); - } - } - ); - }} - /> - - -
- - - - - - - Save Template - -
-
- - setTemplateName(e.target.value)} - className="col-span-3" - /> -
-
-
- - -
-
-
- - - -
-
- - - - - - - Load Design - -