diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/AIAssistant.tsx b/AIAssistant.tsx index 24e19fe..c79dc88 100644 --- a/AIAssistant.tsx +++ b/AIAssistant.tsx @@ -1,7 +1,7 @@ import React, { useState, useRef, useEffect } from 'react'; -import { getSkincareAdvice } from '../geminiService'; -import { Message } from '../types'; +import { getSkincareAdvice } from './geminiService'; +import { Message } from './types'; interface AIAssistantProps { lang: 'tr' | 'en'; diff --git a/AdvantageModal.tsx b/AdvantageModal.tsx index 2d40c1f..535806c 100644 --- a/AdvantageModal.tsx +++ b/AdvantageModal.tsx @@ -1,9 +1,10 @@ import React, { useState, useRef, useEffect } from 'react'; -import { ADVANTAGE_PRODUCTS, INGREDIENTS_SERUM_TR, INGREDIENTS_CLEANSER_TR, INGREDIENTS_MOISTURIZER_TR, INGREDIENTS_VEGAN_SERUM_TR } from '../constants'; -import { AdvantageProduct, Ingredient, Language } from '../types'; +import { ADVANTAGE_PRODUCTS, INGREDIENTS_SERUM_TR, INGREDIENTS_CLEANSER_TR, INGREDIENTS_MOISTURIZER_TR, INGREDIENTS_VEGAN_SERUM_TR } from './constants'; +import { AdvantageProduct, Ingredient, Language } from './types'; +import WhatsAppBulkSender from './WhatsAppBulkSender'; -const TELEGRAM_BOT_TOKEN = '8268291221:AAGjOqG-nKzXxjbd4uuZ0A9OBnRtRnE7Lco'; -const TELEGRAM_CHAT_ID = '1205997493'; +const TELEGRAM_BOT_TOKEN = import.meta.env.VITE_TELEGRAM_BOT_TOKEN; +const TELEGRAM_CHAT_ID = import.meta.env.VITE_TELEGRAM_CHAT_ID; const UTS_IMAGE_URL = "https://lh3.googleusercontent.com/d/14KRnNHIhGgurbILLRbwl3wEm8jrTgcue"; interface AdvantageModalProps { @@ -20,6 +21,7 @@ const AdvantageModal: React.FC = ({ isOpen, onClose, t, lan const [isVerified, setIsVerified] = useState(false); const [showRegister, setShowRegister] = useState(false); const [error, setError] = useState(false); + const [view, setView] = useState<'products' | 'whatsapp'>('products'); const [pharmacyName, setPharmacyName] = useState(''); const [contactInfo, setContactInfo] = useState(''); @@ -319,23 +321,39 @@ const AdvantageModal: React.FC = ({ isOpen, onClose, t, lan ) : ( /* Dashboard View - Full Screen */ -
+
-
-
+
+
- {t('app_exclusive_badge')} ACCESS + {t('app_exclusive_badge')} ACCESS
-

+

Premier
Advantage

-
-
- PARTNER MARGIN: %60 +
+
+ MARGIN: %60
-
- OX LABORATORY STANDARDS V3.0 + +
+ + +
+ +
+ V3.0
@@ -366,103 +384,109 @@ const AdvantageModal: React.FC = ({ isOpen, onClose, t, lan
-
-
- {ADVANTAGE_PRODUCTS.map((prod, i) => ( +
+ {view === 'products' ? ( + <>
-
- {prod.name} -
- {prod.volume} -
-
{prod.emoji}
- -
- - -
-
- -
-
-

{prod.name}

-
-

- {expandedItems[i] ? (prod.detailedDescription || prod.description) : prod.description} -

- + {ADVANTAGE_PRODUCTS.map((prod, i) => ( +
+
+ {prod.name} +
+ {prod.volume} +
+
{prod.emoji}
+ +
+ + +
-
-
-
- {t('price')} -

{prod.price}

-
-
- {t('margin')} -

{prod.margin}

-
-
- {t('cost')} -

{prod.cost}

-
-
- {t('profit')} -

+{prod.profit}

-
+
+
+

{prod.name}

+
+

+ {expandedItems[i] ? (prod.detailedDescription || prod.description) : prod.description} +

+ +
+
+ +
+
+ {t('price')} +

{prod.price}

+
+
+ {t('margin')} +

{prod.margin}

+
+
+ {t('cost')} +

{prod.cost}

+
+
+ {t('profit')} +

+{prod.profit}

+
+
+
-
+ ))}
- ))} -
-
- - -
-
+
+ + +
-
-
+
+
+
+ + ) : ( + + )}
)} diff --git a/App.tsx b/App.tsx index 9e8116d..3b8a4b8 100644 --- a/App.tsx +++ b/App.tsx @@ -7,6 +7,8 @@ import Ingredients from './Ingredients'; import Ritual from './Ritual'; import ApplicationRequest from './ApplicationRequest'; import PharmacyLocator from './PharmacyLocator'; +import AIAssistant from './AIAssistant'; +import AdvantageModal from './AdvantageModal'; import Footer from './Footer'; const App: React.FC = () => { diff --git a/ApplicationRequest.tsx b/ApplicationRequest.tsx index 45846ad..30ffc4b 100644 --- a/ApplicationRequest.tsx +++ b/ApplicationRequest.tsx @@ -1,8 +1,8 @@ import React, { useState } from 'react'; -import { EXCELLENCE_V2_INGREDIENTS } from '../constants'; +import { EXCELLENCE_V2_INGREDIENTS } from './constants'; -const TELEGRAM_BOT_TOKEN = '8268291221:AAGjOqG-nKzXxjbd4uuZ0A9OBnRtRnE7Lco'; -const TELEGRAM_CHAT_ID = '1205997493'; +const TELEGRAM_BOT_TOKEN = import.meta.env.VITE_TELEGRAM_BOT_TOKEN; +const TELEGRAM_CHAT_ID = import.meta.env.VITE_TELEGRAM_CHAT_ID; interface ApplicationRequestProps { t: (key: any) => string; diff --git a/Hero.tsx b/Hero.tsx index e273f8e..74c8452 100644 --- a/Hero.tsx +++ b/Hero.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; -import { Language } from '../types'; +import { Language } from './types'; interface HeroProps { t: (key: any) => string; diff --git a/Ingredients.tsx b/Ingredients.tsx index 21e18a4..4f15a0c 100644 --- a/Ingredients.tsx +++ b/Ingredients.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; -import { INGREDIENTS_TR, INGREDIENTS_EN } from '../constants'; -import { Language } from '../types'; +import { INGREDIENTS_TR, INGREDIENTS_EN } from './constants'; +import { Language } from './types'; interface IngredientsProps { t: (key: any) => string; diff --git a/Navbar.tsx b/Navbar.tsx index 7dd1ca9..c64134a 100644 --- a/Navbar.tsx +++ b/Navbar.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { Language } from '../types'; +import { Language } from './types'; interface NavbarProps { lang: Language; diff --git a/PharmacyLocator.tsx b/PharmacyLocator.tsx index 34791e0..2ebd784 100644 --- a/PharmacyLocator.tsx +++ b/PharmacyLocator.tsx @@ -1,7 +1,6 @@ -import { TURKEY_CITIES } from './constants'; import React, { useState, useEffect, useRef } from 'react'; -import { Pharmacy, Language } from '../types'; -import { AUTHORIZED_PHARMACIES } from '../constants'; +import { Pharmacy, Language } from './types'; +import { AUTHORIZED_PHARMACIES } from './constants'; interface PharmacyLocatorProps { t: (key: any) => string; diff --git a/Ritual.tsx b/Ritual.tsx index 5832df3..b65f5fc 100644 --- a/Ritual.tsx +++ b/Ritual.tsx @@ -1,6 +1,6 @@ import React, { useRef, useState } from 'react'; -import { INGREDIENTS_SERUM_TR, INGREDIENTS_SERUM_EN } from '../constants'; +import { INGREDIENTS_SERUM_TR, INGREDIENTS_SERUM_EN } from './constants'; interface RitualProps { t: (key: any) => string; diff --git a/WhatsAppBulkSender.tsx b/WhatsAppBulkSender.tsx new file mode 100644 index 0000000..147de2e --- /dev/null +++ b/WhatsAppBulkSender.tsx @@ -0,0 +1,243 @@ + +import React, { useState, useEffect } from 'react'; +import { Contact } from './types'; + +interface WhatsAppBulkSenderProps { + t: (key: any) => string; +} + +const WhatsAppBulkSender: React.FC = ({ t }) => { + const [contacts, setContacts] = useState(() => { + const saved = localStorage.getItem('ox_contacts'); + try { + return saved ? JSON.parse(saved) : []; + } catch { + return []; + } + }); + + const [template, setTemplate] = useState(() => { + return localStorage.getItem('ox_wa_template') || ''; + }); + + const [activeTab, setActiveTab] = useState<'config' | 'list'>('config'); + + useEffect(() => { + localStorage.setItem('ox_contacts', JSON.stringify(contacts)); + }, [contacts]); + + useEffect(() => { + localStorage.setItem('ox_wa_template', template); + }, [template]); + + const handleFileUpload = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (event) => { + const content = event.target?.result as string; + const parsedContacts = parseVCF(content); + setContacts(prev => [...prev, ...parsedContacts]); + if (parsedContacts.length > 0) setActiveTab('list'); + }; + reader.readAsText(file); + }; + + const parseVCF = (content: string): Contact[] => { + const results: Contact[] = []; + const vcards = content.split(/BEGIN:VCARD/i); + + vcards.forEach(vcard => { + if (!vcard.trim()) return; + const fnMatch = vcard.match(/^FN(?:;[^:]*)?:(.*)$/im); + const telMatch = vcard.match(/^TEL(?:;[^:]*)?:(.*)$/im); + + if (fnMatch && telMatch) { + let name = fnMatch[1].trim(); + let phone = telMatch[1].replace(/[^0-9+]/g, '').trim(); + if (phone.length >= 7) { + results.push({ name, phone }); + } + } else if (!fnMatch) { + const nMatch = vcard.match(/^N(?:;[^:]*)?:([^;]*);([^;]*)/im); + if (nMatch && telMatch) { + const name = `${nMatch[2].trim()} ${nMatch[1].trim()}`.trim(); + const phone = telMatch[1].replace(/[^0-9+]/g, '').trim(); + if (phone.length >= 7) { + results.push({ name, phone }); + } + } + } + }); + return results; + }; + + const removeContact = (index: number) => { + setContacts(prev => prev.filter((_, i) => i !== index)); + }; + + const clearContacts = () => { + if (window.confirm(t('wa_btn_clear') + '?')) { + setContacts([]); + } + }; + + const sendWhatsApp = (contact: Contact) => { + const message = template.replace(/{name}/g, contact.name); + const encodedMessage = encodeURIComponent(message); + const url = `https://wa.me/${contact.phone}?text=${encodedMessage}`; + window.open(url, '_blank'); + }; + + return ( +
+ + {/* Configuration Panel - App view on mobile, Sidebar on Web */} +
+
+
+
+

{t('wa_title')}

+ V3.0 Web +
+ +
+ +
+
+ +
+
+ +
+
+
📤
+
+

VCF Dosyası Seç

+

Mobil rehber dışa aktarma dosyası

+
+
+ +
+
+ +
+
+ + {t('wa_template_hint')} +
+