+
+ {/* Dropdown Menu */}
+ {isOpen && (
+ <>
+ {/* Backdrop */}
+
+ );
+}
+
+// Compact version for navigation bars
+export function CompactLanguageSelector() {
+ const { language, changeLanguage, getAvailableLanguages } = useLanguage();
+ const { isDark } = useTheme();
+ const [isOpen, setIsOpen] = useState(false);
+
+ const availableLanguages = getAvailableLanguages();
+ const currentLang = availableLanguages.find((lang) => lang.code === language);
+
+ const handleLanguageChange = (langCode) => {
+ changeLanguage(langCode);
+ setIsOpen(false);
+ };
+
+ return (
+
+ {/* Compact Button */}
+
setIsOpen(!isOpen)}
+ className={`
+ p-2 rounded-full transition-colors
+ ${
+ isDark
+ ? "hover:bg-gray-700 text-gray-300"
+ : "hover:bg-gray-100 text-gray-600"
+ }
+ `}
+ title={currentLang?.nativeName}
+ >
+
+
+
+ {/* Dropdown Menu */}
+ {isOpen && (
+ <>
+ {/* Backdrop */}
+
setIsOpen(false)}
+ />
+
+ {/* Menu */}
+
+ {availableLanguages.map((lang) => (
+ handleLanguageChange(lang.code)}
+ className={`
+ w-full px-3 py-2 text-left text-sm transition-colors flex items-center justify-between
+ ${
+ isDark
+ ? "hover:bg-gray-700 text-white"
+ : "hover:bg-gray-50 text-gray-900"
+ }
+ `}
+ >
+ {lang.nativeName}
+ {language === lang.code && (
+
+ )}
+
+ ))}
+
+ >
+ )}
+
+ );
+}
+
+// Settings page version with full details - ALWAYS WHITE THEME
+export function DetailedLanguageSelector() {
+ const { language, changeLanguage, t, getAvailableLanguages } = useLanguage();
+ // Remove useTheme - always use light theme
+ const availableLanguages = getAvailableLanguages();
+
+ return (
+
+
{t("language")}
+
+
+ {availableLanguages.map((lang) => (
+
changeLanguage(lang.code)}
+ className={`
+ w-full p-3 rounded-lg border text-left transition-all
+ ${
+ language === lang.code
+ ? "border-blue-500 bg-blue-50 text-blue-600"
+ : "border-gray-300 hover:border-gray-400 text-gray-900 hover:bg-gray-50"
+ }
+ `}
+ >
+
+
+
{lang.nativeName}
+
+ {lang.name}
+
+
+
+ {language === lang.code && (
+
+ )}
+
+
+ ))}
+
+
+
+ {t("changeLanguage")} • {t("currentLanguage")}:{" "}
+ {availableLanguages.find((l) => l.code === language)?.nativeName}
+
+
+ );
+}
+
+// Inline Language Switcher (for headers/toolbars)
+export function InlineLanguageSelector() {
+ const { language, changeLanguage, getAvailableLanguages } = useLanguage();
+ const { isDark } = useTheme();
+ const availableLanguages = getAvailableLanguages();
+
+ return (
+
+ {availableLanguages.map((lang) => (
+ changeLanguage(lang.code)}
+ className={`
+ px-2 py-1 text-xs rounded font-medium transition-colors
+ ${
+ language === lang.code
+ ? "bg-blue-600 text-white"
+ : isDark
+ ? "text-gray-400 hover:text-white hover:bg-gray-700"
+ : "text-gray-600 hover:text-gray-900 hover:bg-gray-100"
+ }
+ `}
+ >
+ {lang.code.toUpperCase()}
+
+ ))}
+
+ );
+}
+
+// Modal Language Selector (for first-time setup)
+export function LanguageModal({ isOpen, onClose, onSelect }) {
+ const { getAvailableLanguages, t } = useLanguage();
+ const { isDark } = useTheme();
+ const availableLanguages = getAvailableLanguages();
+
+ const handleSelect = (langCode) => {
+ onSelect(langCode);
+ onClose();
+ };
+
+ if (!isOpen) return null;
+
+ return (
+
+
+ {/* Header */}
+
+
+ Choose Your Language
+
+
+ Select your preferred language for PastPick
+
+
+
+ {/* Language Options */}
+
+
+ {availableLanguages.map((lang) => (
+
handleSelect(lang.code)}
+ className={`
+ w-full p-4 rounded-lg border transition-all text-left
+ ${
+ isDark
+ ? "border-gray-600 hover:border-blue-500 text-white hover:bg-gray-700"
+ : "border-gray-300 hover:border-blue-500 text-gray-900 hover:bg-gray-50"
+ }
+ `}
+ >
+
+
+
+
{lang.nativeName}
+
+ {lang.name}
+
+
+
+
+ ))}
+
+
+
+
+ );
+}
+
+// Language Badge (shows current language)
+export function LanguageBadge() {
+ const { getCurrentLanguageInfo } = useLanguage();
+ const { isDark } = useTheme();
+ const currentLang = getCurrentLanguageInfo();
+
+ return (
+
+
+ {currentLang.code.toUpperCase()}
+
+ );
+}
diff --git a/src/components/Providers.js b/src/components/Providers.js
new file mode 100644
index 0000000..30efb88
--- /dev/null
+++ b/src/components/Providers.js
@@ -0,0 +1,15 @@
+"use client";
+
+import { ThemeProvider } from "../contexts/ThemeContext";
+import { LanguageProvider } from "../contexts/LanguageContext";
+import { ToastProvider } from "./ToastProvider";
+
+export default function Providers({ children }) {
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/components/ToastProvider.js b/src/components/ToastProvider.js
new file mode 100644
index 0000000..4de0d5d
--- /dev/null
+++ b/src/components/ToastProvider.js
@@ -0,0 +1,132 @@
+"use client";
+
+import { createContext, useContext, useState } from "react";
+import { CheckCircle, AlertTriangle, XCircle, Info, X } from "lucide-react";
+import { useTheme } from "../contexts/ThemeContext";
+
+const ToastContext = createContext();
+
+export const ToastProvider = ({ children }) => {
+ const [toasts, setToasts] = useState([]);
+ const { isDark } = useTheme();
+
+ const addToast = (message, type = "info", duration = 4000) => {
+ const id = Date.now() + Math.random();
+ const toast = { id, message, type, duration };
+
+ setToasts((prev) => [...prev, toast]);
+
+ if (duration > 0) {
+ setTimeout(() => {
+ removeToast(id);
+ }, duration);
+ }
+
+ return id;
+ };
+
+ const removeToast = (id) => {
+ setToasts((prev) => prev.filter((toast) => toast.id !== id));
+ };
+
+ const showSuccess = (message, duration) =>
+ addToast(message, "success", duration);
+ const showError = (message, duration) => addToast(message, "error", duration);
+ const showWarning = (message, duration) =>
+ addToast(message, "warning", duration);
+ const showInfo = (message, duration) => addToast(message, "info", duration);
+
+ return (
+
+ {children}
+
+ {/* Toast Container */}
+
+ {toasts.map((toast) => (
+ removeToast(toast.id)}
+ isDark={isDark}
+ />
+ ))}
+
+
+ );
+};
+
+const Toast = ({ toast, onClose, isDark }) => {
+ const getIcon = () => {
+ switch (toast.type) {
+ case "success":
+ return
;
+ case "error":
+ return
;
+ case "warning":
+ return
;
+ default:
+ return
;
+ }
+ };
+
+ const getColors = () => {
+ switch (toast.type) {
+ case "success":
+ return isDark
+ ? "bg-green-800 border-green-700 text-green-100"
+ : "bg-green-50 border-green-200 text-green-800";
+ case "error":
+ return isDark
+ ? "bg-red-800 border-red-700 text-red-100"
+ : "bg-red-50 border-red-200 text-red-800";
+ case "warning":
+ return isDark
+ ? "bg-amber-800 border-amber-700 text-amber-100"
+ : "bg-amber-50 border-amber-200 text-amber-800";
+ default:
+ return isDark
+ ? "bg-gray-800 border-gray-700 text-gray-100"
+ : "bg-white border-gray-200 text-gray-800";
+ }
+ };
+
+ return (
+
+ );
+};
+
+export const useToast = () => {
+ const context = useContext(ToastContext);
+ if (!context) {
+ throw new Error("useToast must be used within a ToastProvider");
+ }
+ return context;
+};
diff --git a/src/components/pages/HomePage.js b/src/components/pages/HomePage.js
new file mode 100644
index 0000000..b040aac
--- /dev/null
+++ b/src/components/pages/HomePage.js
@@ -0,0 +1,89 @@
+import {
+ Camera,
+ Search,
+ Shield,
+ Sparkles,
+ CheckCircle,
+ TrendingUp,
+} from "lucide-react";
+import { useLanguage } from "../../contexts/LanguageContext";
+
+export default function HomePage({ onProductScanned, onStartCamera }) {
+ const { t } = useLanguage();
+
+ return (
+
+ {/* Header */}
+
+
+ {/* Quick Stats */}
+
+
+
2,847
+
{t("productsScanned")}
+
+
+
94%
+
{t("safeIngredients")}
+
+
+
+ {/* Quick Actions */}
+
+ onStartCamera && onStartCamera()}
+ className="w-full bg-gradient-to-r from-blue-600 to-blue-700 text-white py-3 px-4 rounded-lg flex items-center justify-center space-x-3 shadow-md"
+ >
+
+ {t("quickScan")}
+
+
+
+
+ {t("searchProducts")}
+
+
+
+ {/* Features */}
+
+
+
+
+
+
+ {t("safetyAnalysis")}
+
+
{t("safetyAnalysisDesc")}
+
+
+
+
+
+
+
+
+
+ {t("smartRecommendations")}
+
+
+ {t("smartRecommendationsDesc")}
+
+
+
+
+
+
+ {/* Footer */}
+
+
{t("poweredBy")}
+
+ {t("learnHow")}
+
+
+
+ );
+}
diff --git a/src/components/pages/ProductPage.js b/src/components/pages/ProductPage.js
new file mode 100644
index 0000000..8d59545
--- /dev/null
+++ b/src/components/pages/ProductPage.js
@@ -0,0 +1,686 @@
+"use client";
+
+import { useState } from "react";
+import {
+ ArrowLeft,
+ Heart,
+ Share,
+ Star,
+ Shield,
+ CheckCircle,
+ AlertTriangle,
+ XCircle,
+ TrendingUp,
+ TrendingDown,
+} from "lucide-react";
+
+export default function ProductPage({ product, onBack }) {
+ const [activeTab, setActiveTab] = useState("ingredients");
+
+ // Sample product data - you'll replace this with real analysis data
+ const sampleProduct = product || {
+ name: "Sensodyne Pronamel Gentle Whitening",
+ brand: "Sensodyne",
+ image: "🦷",
+ overallScore: 8.7, // 1-10 scale instead of percentage
+ totalIngredients: 12,
+ overallAssessment: "Excellent choice with high-quality, safe ingredients",
+ keyIngredients: {
+ positive: ["Potassium Nitrate", "Sodium Fluoride", "Calcium Carbonate"],
+ negative: ["Titanium Dioxide", "Sodium Lauryl Sulfate"],
+ },
+ primaryConcerns: [
+ "May cause irritation in sensitive individuals",
+ "Contains whitening agents under review",
+ ],
+ primaryBenefits: [
+ "Reduces sensitivity",
+ "Prevents cavities",
+ "Strengthens enamel",
+ ],
+ scoreBreakdown: {
+ beneficial: 5,
+ moderate: 4,
+ concerning: 2,
+ unknown: 1,
+ },
+ };
+
+ const getScoreColor = (score) => {
+ if (score >= 8.5) return "text-green-600 bg-green-50 border-green-200";
+ if (score >= 7.0) return "text-blue-600 bg-blue-50 border-blue-200";
+ if (score >= 5.5) return "text-yellow-600 bg-yellow-50 border-yellow-200";
+ if (score >= 3.5) return "text-orange-600 bg-orange-50 border-orange-200";
+ return "text-red-600 bg-red-50 border-red-200";
+ };
+
+ const getScoreLabel = (score) => {
+ if (score >= 8.5) return "Excellent";
+ if (score >= 7.0) return "Good";
+ if (score >= 5.5) return "Average";
+ if (score >= 3.5) return "Below Average";
+ return "Poor";
+ };
+
+ const getScoreIcon = (score) => {
+ if (score >= 8.5) return
;
+ if (score >= 7.0) return
;
+ if (score >= 5.5) return
;
+ return
;
+ };
+
+ const renderTabContent = () => {
+ switch (activeTab) {
+ case "ingredients":
+ return
;
+ case "summary":
+ return
;
+ case "concerns":
+ return
;
+ case "benefits":
+ return
;
+ default:
+ return
;
+ }
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+
Ingredient Analysis
+
+
+
+
+
+
+
+
+
+
+
+ {/* Product Overview */}
+
+ {/* Product Image and Basic Info */}
+
+
+ {sampleProduct.image}
+
+
+
+ {sampleProduct.name}
+
+
{sampleProduct.brand}
+
+
+ 4.5/5
+
+ ({sampleProduct.totalIngredients} ingredients analyzed)
+
+
+
+
+
+ {/* Overall Score - 1-10 Scale */}
+
+
+
+ {sampleProduct.overallScore}
+
+
+ {getScoreLabel(sampleProduct.overallScore)}
+
+
+ Overall Safety Score (1-10)
+
+
+ {/* Score visualization */}
+
+
= 8.5
+ ? "bg-green-500"
+ : sampleProduct.overallScore >= 7.0
+ ? "bg-blue-500"
+ : sampleProduct.overallScore >= 5.5
+ ? "bg-yellow-500"
+ : sampleProduct.overallScore >= 3.5
+ ? "bg-orange-500"
+ : "bg-red-500"
+ }`}
+ style={{ width: `${(sampleProduct.overallScore / 10) * 100}%` }}
+ >
+
+
+
+ {sampleProduct.overallAssessment}
+
+
+
+
+ {/* Key Ingredients Impact */}
+
+ {/* Positive Ingredients */}
+ {sampleProduct.keyIngredients.positive.length > 0 && (
+
+
+
+
Key Benefits
+
+
+ {sampleProduct.keyIngredients.positive.map(
+ (ingredient, index) => (
+
+
+ {ingredient}
+
+ ),
+ )}
+
+
+ )}
+
+ {/* Negative Ingredients */}
+ {sampleProduct.keyIngredients.negative.length > 0 && (
+
+
+
+
Main Concerns
+
+
+ {sampleProduct.keyIngredients.negative.map(
+ (ingredient, index) => (
+
+ ),
+ )}
+
+
+ )}
+
+
+ {/* Score Breakdown */}
+
+
+ Ingredient Categories
+
+
+
+
+ {sampleProduct.scoreBreakdown.beneficial}
+
+
Beneficial
+
+
+
+ {sampleProduct.scoreBreakdown.moderate}
+
+
Moderate
+
+
+
+ {sampleProduct.scoreBreakdown.concerning}
+
+
Concerning
+
+
+
+ {sampleProduct.scoreBreakdown.unknown}
+
+
Unknown
+
+
+
+
+
+ {/* Tab Navigation */}
+
+
+ {[
+ { id: "ingredients", label: "Ingredients" },
+ { id: "summary", label: "Summary" },
+ { id: "concerns", label: "Concerns" },
+ { id: "benefits", label: "Benefits" },
+ ].map((tab) => (
+ setActiveTab(tab.id)}
+ className={`flex-shrink-0 px-6 py-3 text-sm font-medium border-b-2 transition-colors ${
+ activeTab === tab.id
+ ? "border-blue-600 text-blue-600"
+ : "border-transparent text-gray-500 hover:text-gray-700"
+ }`}
+ >
+ {tab.label}
+
+ ))}
+
+
+
+ {/* Tab Content */}
+
{renderTabContent()}
+
+ );
+}
+
+// Ingredients Tab Component
+function IngredientsTab({ product }) {
+ // Mock ingredients with individual scores
+ const ingredients = [
+ {
+ name: "Potassium Nitrate",
+ purpose: "Desensitizing agent",
+ score: 9.2,
+ category: "beneficial",
+ description:
+ "Helps reduce tooth sensitivity by blocking pain signals to nerves",
+ concerns: [],
+ benefits: ["Reduces sensitivity", "Clinically proven", "FDA approved"],
+ },
+ {
+ name: "Sodium Fluoride",
+ purpose: "Anticavity agent",
+ score: 9.0,
+ category: "beneficial",
+ description: "Prevents tooth decay and strengthens enamel",
+ concerns: [],
+ benefits: ["Prevents cavities", "Strengthens enamel", "ADA recommended"],
+ },
+ {
+ name: "Hydrated Silica",
+ purpose: "Mild abrasive",
+ score: 7.2,
+ category: "moderate",
+ description: "Helps remove plaque and surface stains gently",
+ concerns: ["May be abrasive with excessive use"],
+ benefits: ["Effective cleaning", "Whitening properties"],
+ },
+ {
+ name: "Titanium Dioxide",
+ purpose: "Whitening agent",
+ score: 6.5,
+ category: "concerning",
+ description: "Provides whitening and opacity to toothpaste",
+ concerns: [
+ "Potential respiratory irritant",
+ "Under regulatory review",
+ "May cause inflammation",
+ ],
+ benefits: ["Whitening effect", "Color enhancement"],
+ },
+ {
+ name: "Sodium Lauryl Sulfate",
+ purpose: "Foaming agent",
+ score: 4.2,
+ category: "concerning",
+ description: "Creates foam and helps distribute toothpaste",
+ concerns: [
+ "May cause mouth ulcers",
+ "Can irritate sensitive gums",
+ "Strips natural oils",
+ ],
+ benefits: ["Creates foam", "Helps cleaning action"],
+ },
+ ];
+
+ const getCategoryColor = (category, score) => {
+ switch (category) {
+ case "beneficial":
+ return "bg-green-100 text-green-800 border-green-200";
+ case "moderate":
+ return "bg-blue-100 text-blue-800 border-blue-200";
+ case "concerning":
+ return "bg-red-100 text-red-800 border-red-200";
+ default:
+ return "bg-gray-100 text-gray-800 border-gray-200";
+ }
+ };
+
+ const getScoreColor = (score) => {
+ if (score >= 8.5) return "text-green-600";
+ if (score >= 7.0) return "text-blue-600";
+ if (score >= 5.5) return "text-yellow-600";
+ if (score >= 3.5) return "text-orange-600";
+ return "text-red-600";
+ };
+
+ return (
+
+
+ Found {ingredients.length} ingredients • Analyzed with our safety
+ database
+
+
+ {ingredients.map((ingredient, index) => (
+
+
+
+
+ {ingredient.name}
+
+
{ingredient.purpose}
+
+ {ingredient.category.charAt(0).toUpperCase() +
+ ingredient.category.slice(1)}
+
+
+
+
+ {ingredient.score}/10
+
+
Safety Score
+
+
+
+
{ingredient.description}
+
+ {ingredient.benefits.length > 0 && (
+
+
+ Benefits:
+
+
+ {ingredient.benefits.map((benefit, i) => (
+
+ {benefit}
+
+ ))}
+
+
+ )}
+
+ {ingredient.concerns.length > 0 && (
+
+
+ Concerns:
+
+
+ {ingredient.concerns.map((concern, i) => (
+
+ ))}
+
+
+ )}
+
+ ))}
+
+ );
+}
+
+// Summary Tab Component
+function SummaryTab({ product }) {
+ return (
+
+
+
Overall Analysis
+
+ This toothpaste received a score of{" "}
+ {product.overallScore}/10 based on the analysis of{" "}
+ {product.totalIngredients} ingredients.
+ {product.overallAssessment}
+
+
+
+
+
+ {product.scoreBreakdown.beneficial}
+
+
Beneficial Ingredients
+
+
+
+ {product.scoreBreakdown.concerning}
+
+
Concerning Ingredients
+
+
+
+
+
+
+
+ Contains proven effective ingredients for oral health
+
+
+
+
+
+ Generally safe for daily use by most adults
+
+
+ {product.scoreBreakdown.concerning > 0 && (
+
+
+
+ Contains some ingredients that may cause sensitivity in certain
+ individuals
+
+
+ )}
+
+
+
+
+
Recommendations
+
+ {product.overallScore >= 8.5 && (
+
+ âś… Highly Recommended: This is an excellent
+ choice for most people with high safety standards.
+
+ )}
+ {product.overallScore >= 7.0 && product.overallScore < 8.5 && (
+
+ 👍 Good Choice: Solid formulation with mostly
+ beneficial ingredients.
+
+ )}
+ {product.overallScore >= 5.5 && product.overallScore < 7.0 && (
+
+ ⚠️ Use with Caution: Consider alternatives if you
+ have sensitive teeth or gums.
+
+ )}
+ {product.overallScore < 5.5 && (
+
+ ❌ Not Recommended: Consider switching to a
+ product with fewer concerning ingredients.
+
+ )}
+
+
• Use twice daily for best results
+
• Discontinue use if irritation occurs
+
• Consult your dentist for personalized advice
+
+
+
+ );
+}
+
+// Concerns Tab Component
+function ConcernsTab({ product }) {
+ return (
+
+
+ Potential issues identified in this product
+
+
+ {product.primaryConcerns.length > 0
+ ?
+ {product.primaryConcerns.map((concern, index) => (
+
+
+
+
+
+ {concern}
+
+
+
+ What this means: This ingredient may
+ cause adverse reactions in some individuals.
+
+
+ Who should be careful: People with
+ sensitive teeth, gums, or known allergies.
+
+
+ What to do: Monitor your reaction and
+ discontinue if irritation occurs.
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+ General Safety Tips
+
+
+
+ • Always read ingredient labels if you have known
+ allergies
+
+ • Start with small amounts when trying new products
+
+ • Consult your dentist if you experience persistent
+ irritation
+
+
+ • Keep products away from children under 6 years old
+
+
+
+
+
+
+ :
+
+
+ No Major Concerns
+
+
+ This product has no significant safety concerns for most users.
+
+
}
+
+ );
+}
+
+// Benefits Tab Component
+function BenefitsTab({ product }) {
+ return (
+
+
+ Key benefits and positive aspects of this product
+
+
+ {product.primaryBenefits.length > 0
+ ?
+ {product.primaryBenefits.map((benefit, index) => (
+
+
+
+
+
+ {benefit}
+
+
+
+ This ingredient or property contributes positively to
+ oral health and safety.
+
+
+
+
+
+ ))}
+
+
+
+ Why These Ingredients Matter
+
+
+
+
+ 🦷 Oral Health Benefits
+
+
+ • Cavity prevention
+ • Enamel strengthening
+ • Plaque reduction
+ • Sensitivity relief
+
+
+
+
+ âś… Safety Benefits
+
+
+ • FDA approved ingredients
+ • Clinically tested
+ • Natural components
+ • Low irritation risk
+
+
+
+
+
+ :
+
+
+ Limited Benefits
+
+
+ This product has fewer beneficial ingredients than recommended.
+
+
}
+
+ );
+}
diff --git a/src/components/pages/ScanPage.js b/src/components/pages/ScanPage.js
new file mode 100644
index 0000000..6131031
--- /dev/null
+++ b/src/components/pages/ScanPage.js
@@ -0,0 +1,66 @@
+import { Camera, Upload, Type, Zap } from "lucide-react";
+
+export default function ScanPage({ onProductScanned, onStartCamera }) {
+ return (
+
+ {/* Header */}
+
+
+ {/* Camera Preview Area */}
+
+
+
+
+
+ Camera preview will appear here
+
+
+
+
+
+ {/* Scan Options */}
+
+ onStartCamera && onStartCamera()}
+ className="w-full bg-blue-600 hover:bg-blue-700 text-white py-4 px-6 rounded-xl flex items-center justify-center space-x-3 shadow-lg transition-colors"
+ >
+
+ Start Camera Scan
+
+
+
+
+ Upload from Gallery
+
+
+
+
+ Type Ingredients
+
+
+
+ {/* Quick Tips */}
+
+
+
+
+
+ Scanning Tips
+
+
+ • Ensure good lighting
+ • Hold camera steady
+ • Focus on ingredients list
+ • Keep text clear and readable
+
+
+
+
+
+ );
+}
diff --git a/src/components/pages/SettingsPage.js b/src/components/pages/SettingsPage.js
new file mode 100644
index 0000000..13587ee
--- /dev/null
+++ b/src/components/pages/SettingsPage.js
@@ -0,0 +1,111 @@
+import {
+ User,
+ Bell,
+ Shield,
+ HelpCircle,
+ Info,
+ ChevronRight,
+ Moon,
+ Globe,
+} from "lucide-react";
+import { useLanguage } from "../../contexts/LanguageContext";
+import { DetailedLanguageSelector } from "../LanguageSelector";
+
+export default function SettingsPage() {
+ const { t } = useLanguage();
+
+ const settingSections = [
+ {
+ title: t("account"),
+ items: [
+ { icon: User, label: t("profile"), value: t("updateInfo") },
+ { icon: Bell, label: t("notifications"), value: t("manageAlerts") },
+ ],
+ },
+ {
+ title: t("preferences"),
+ items: [
+ {
+ icon: Shield,
+ label: t("safetyThreshold"),
+ value: t("highSensitivity"),
+ },
+ { icon: Moon, label: t("darkMode"), value: t("systemTheme") },
+ ],
+ },
+ {
+ title: t("support"),
+ items: [
+ { icon: HelpCircle, label: t("helpCenter"), value: t("getSupport") },
+ { icon: Info, label: t("aboutApp"), value: t("version") },
+ ],
+ },
+ ];
+
+ return (
+
+ {/* Header */}
+
+
+ {/* Language Selector */}
+
+
+
+
+ {/* Settings Sections */}
+
+ {settingSections.map((section, sectionIndex) => (
+
+
+ {section.title}
+
+
+ {section.items.map((item, itemIndex) => {
+ const Icon = item.icon;
+ return (
+
+
+
+
+
+
+ {item.label}
+
+
{item.value}
+
+
+
+ );
+ })}
+
+
+ ))}
+
+
+ {/* App Info */}
+
+
+
🦷 {t("appName")}
+
+ Making toothpaste ingredient analysis simple and accessible for
+ everyone.
+
+
+
+ © 2024 {t("appName")}. Made with care for your oral health.
+
+
+
+ );
+}
diff --git a/src/contexts/LanguageContext.js b/src/contexts/LanguageContext.js
new file mode 100644
index 0000000..8089133
--- /dev/null
+++ b/src/contexts/LanguageContext.js
@@ -0,0 +1,554 @@
+"use client";
+
+import { createContext, useContext, useState, useEffect } from "react";
+
+const LanguageContext = createContext();
+
+// Complete translation data for English and Finnish
+const translations = {
+ en: {
+ // App Name & Branding
+ appName: "PastePick",
+ appTagline: "Your smart companion for analyzing toothpaste ingredients",
+
+ // Navigation
+ home: "Home",
+ favorites: "Favorites",
+ scan: "Scan",
+ recent: "Recent",
+ settings: "Settings",
+
+ // Home Page
+ productsScanned: "Products Scanned",
+ safeIngredients: "Safe Ingredients",
+ quickScan: "Quick Scan",
+ searchProducts: "Search Products",
+ safetyAnalysis: "Safety Analysis",
+ safetyAnalysisDesc: "Detailed ingredient safety ratings",
+ smartRecommendations: "Smart Recommendations",
+ smartRecommendationsDesc: "Better alternatives based on your needs",
+ poweredBy: "Powered by AI-driven ingredient analysis",
+ learnHow: "Learn how PastPick works →",
+
+ // Scanning
+ scanProduct: "Scan Product",
+ scanToothpaste: "Scan Toothpaste",
+ scanIngredients: "Scan Ingredients",
+ analyzeIngredients: "Analyze toothpaste ingredients instantly",
+ startCameraScan: "Start Camera Scan",
+ chooseFromGallery: "Choose from Gallery",
+ takePhoto: "Take Photo",
+ openCamera: "Open Camera",
+ scanningTips: "Scanning Tips",
+ scanFront: "Scan Product Front",
+ scanIngredientsList: "Scan Ingredients List",
+ scanPackage: "Scan Package/Box",
+
+ // Analysis Results
+ ingredientAnalysis: "Ingredient Analysis",
+ overallScore: "Overall Safety Score",
+ overallSafetyScore: "Overall Safety Score (1-10)",
+ dailyUse: "Daily Use",
+ sensitivity: "Sensitivity",
+ formula: "Formula",
+ ingredients: "Ingredients",
+ summary: "Summary",
+ concerns: "Concerns",
+ benefits: "Benefits",
+ sources: "Sources",
+ claims: "Claims",
+ keyIngredients: "Key Ingredients",
+ keyBenefits: "Key Benefits",
+ mainConcerns: "Main Concerns",
+ positiveIngredients: "Beneficial Ingredients",
+ negativeIngredients: "Ingredients of Concern",
+ safetyScore: "Safety Score",
+ ingredientCategories: "Ingredient Categories",
+
+ // Score Categories
+ beneficial: "Beneficial",
+ moderate: "Moderate",
+ concerning: "Concerning",
+ unknown: "Unknown",
+
+ // Status Labels
+ excellent: "Excellent",
+ safe: "Safe",
+ good: "Good",
+ average: "Average",
+ belowAverage: "Below Average",
+ poor: "Poor",
+ caution: "Caution",
+ avoid: "Avoid",
+
+ // Analysis Messages
+ scanningInProgress: "Scanning in progress...",
+ analyzingImage: "Analyzing image...",
+ processingResults: "Processing results...",
+ scanSuccessful: "Scan successful!",
+ analysisComplete: "Analysis complete",
+ analyzing: "Analyzing...",
+ analyzeIngredientsCTA: "Analyze Ingredients",
+
+ // Error Messages
+ imageTooBlurry: "Image is too blurry. Please retake with better focus.",
+ noTextFound:
+ "No ingredients found. Make sure to scan the ingredients list.",
+ noIngredientsFound: "Could not identify ingredients. Please try again.",
+ productNotFound:
+ "Product not found. Try scanning the ingredients list directly.",
+ scanError: "Something went wrong. Please try again.",
+ cameraError: "Unable to access camera. Please check permissions.",
+ networkError: "Network error. Please check your connection.",
+
+ // Camera Tips
+ scanTip1: "Ensure good lighting",
+ scanTip2: "Hold camera steady",
+ scanTip3: "Focus on ingredients list",
+ scanTip4: "Keep text clear and readable",
+ scanTip5: "Avoid shadows and glare",
+ mobileDetected: "Mobile device detected",
+ cameraWorksBesetMobile: "Camera works best on mobile devices",
+
+ // Settings Page
+ profile: "Profile",
+ notifications: "Notifications",
+ manageAlerts: "Manage alerts",
+ updateInfo: "Update your information",
+ safetyThreshold: "Safety Threshold",
+ highSensitivity: "High sensitivity",
+ darkMode: "Dark Mode",
+ language: "Language",
+ helpCenter: "Help Center",
+ getSupport: "Get support",
+ aboutApp: "About PastPick",
+ version: "Version 1.0.0",
+ account: "Account",
+ preferences: "Preferences",
+ support: "Support",
+
+ // Theme Options
+ lightTheme: "Light",
+ darkTheme: "Dark",
+ systemTheme: "System Default",
+
+ // Actions
+ retake: "Retake",
+ analyze: "Analyze",
+ save: "Save",
+ share: "Share",
+ viewDetails: "View Details",
+ tryAgain: "Try Again",
+ cancel: "Cancel",
+ ok: "OK",
+ close: "Close",
+ back: "Back",
+ next: "Next",
+ done: "Done",
+
+ // Favorites
+ noFavorites: "No favorites yet",
+ noFavoritesDesc: "Start scanning products to add them to your favorites",
+ addToFavorites: "Add to Favorites",
+ removeFromFavorites: "Remove from Favorites",
+
+ // Recent Scans
+ recentScans: "Recent Scans",
+ noRecentScans: "No recent scans",
+ noRecentScansDesc: "Your scan history will appear here",
+ scanHistory: "Scan History",
+ scannedAt: "Scanned {time}",
+
+ // Scan Types
+ productFront: "Product Front",
+ ingredientsList: "Ingredients List",
+ packageBox: "Package/Box",
+
+ // Analysis Details
+ overallAnalysis: "Overall Analysis",
+ recommendations: "Recommendations",
+ highlyRecommended: "Highly Recommended",
+ goodChoice: "Good Choice",
+ useWithCaution: "Use with Caution",
+ notRecommended: "Not Recommended",
+ generalSafetyTips: "General Safety Tips",
+ noMajorConcerns: "No Major Concerns",
+ limitedBenefits: "Limited Benefits",
+
+ // Ingredient Analysis
+ foundIngredients: "Found {count} ingredients",
+ analyzedWithDatabase: "Analyzed with our safety database",
+ whatThisMeans: "What this means:",
+ whoShouldBeCareful: "Who should be careful:",
+ whatToDo: "What to do:",
+
+ // Language Selection
+ selectLanguage: "Select Language",
+ currentLanguage: "Current Language",
+ changeLanguage: "Change Language",
+
+ // Time Formats
+ hoursAgo: "{count} hours ago",
+ daysAgo: "{count} days ago",
+ minutesAgo: "{count} minutes ago",
+ justNow: "just now",
+
+ // Pluralization
+ ingredientsAnalyzed: "{count} ingredients analyzed",
+ concernsFound: "{count} concerns found",
+
+ // Loading States
+ loading: "Loading...",
+ pleaseWait: "Please wait...",
+ },
+
+ fi: {
+ // App Name & Branding
+ appName: "PastePick",
+ appTagline: "Älykäs kumppanisi hammastahnien ainesosien analysointiin",
+
+ // Navigation
+ home: "Koti",
+ favorites: "Suosikit",
+ scan: "Skannaa",
+ recent: "Viimeisimmät",
+ settings: "Asetukset",
+
+ // Home Page
+ productsScanned: "Skannattuja Tuotteita",
+ safeIngredients: "Turvallisia Ainesosia",
+ quickScan: "Pikaskannaus",
+ searchProducts: "Hae Tuotteita",
+ safetyAnalysis: "Turvallisuusanalyysi",
+ safetyAnalysisDesc: "Yksityiskohtaiset ainesosien turvallisuusarviot",
+ smartRecommendations: "Älykkäät Suositukset",
+ smartRecommendationsDesc: "Parempia vaihtoehtoja tarpeidesi perusteella",
+ poweredBy: "Tekoälyyn perustuva ainesosien analyysi",
+ learnHow: "Opi kuinka PastPick toimii →",
+
+ // Scanning
+ scanProduct: "Skannaa Tuote",
+ scanToothpaste: "Skannaa Hammastahna",
+ scanIngredients: "Skannaa Ainesosat",
+ analyzeIngredients: "Analysoi hammastahnien ainesosat välittömästi",
+ startCameraScan: "Aloita Kameraskannaus",
+ chooseFromGallery: "Valitse Galleriasta",
+ takePhoto: "Ota Kuva",
+ openCamera: "Avaa Kamera",
+ scanningTips: "Skannausvinkit",
+ scanFront: "Skannaa Tuotteen Etupuoli",
+ scanIngredientsList: "Skannaa Ainesosaluettelo",
+ scanPackage: "Skannaa Pakkaus/Laatikko",
+
+ // Analysis Results
+ ingredientAnalysis: "Ainesosa-analyysi",
+ overallScore: "Kokonaisturvallisuuspisteet",
+ overallSafetyScore: "Kokonaisturvallisuuspisteet (1-10)",
+ dailyUse: "Päivittäinen Käyttö",
+ sensitivity: "Herkkyys",
+ formula: "Koostumus",
+ ingredients: "Ainesosat",
+ summary: "Yhteenveto",
+ concerns: "Huolenaiheet",
+ benefits: "Hyödyt",
+ sources: "Lähteet",
+ claims: "Väitteet",
+ keyIngredients: "Tärkeimmät Ainesosat",
+ keyBenefits: "Keskeiset Hyödyt",
+ mainConcerns: "Päähuolenaiheet",
+ positiveIngredients: "Hyödylliset Ainesosat",
+ negativeIngredients: "Huolestuttavat Ainesosat",
+ safetyScore: "Turvallisuuspisteet",
+ ingredientCategories: "Ainesosa Kategoriat",
+
+ // Score Categories
+ beneficial: "Hyödyllinen",
+ moderate: "Kohtalainen",
+ concerning: "Huolestuttava",
+ unknown: "Tuntematon",
+
+ // Status Labels
+ excellent: "Erinomainen",
+ safe: "Turvallinen",
+ good: "Hyvä",
+ average: "Keskiverto",
+ belowAverage: "Keskitason Alapuolella",
+ poor: "Huono",
+ caution: "Varoitus",
+ avoid: "Vältä",
+
+ // Analysis Messages
+ scanningInProgress: "Skannaus käynnissä...",
+ analyzingImage: "Analysoidaan kuvaa...",
+ processingResults: "Käsitellään tuloksia...",
+ scanSuccessful: "Skannaus onnistui!",
+ analysisComplete: "Analyysi valmis",
+ analyzing: "Analysoidaan...",
+ analyzeIngredientsCTA: "Analysoi Ainesosat",
+
+ // Error Messages
+ imageTooBlurry:
+ "Kuva on liian epätarkka. Ota uusi kuva paremmalla tarkennuksella.",
+ noTextFound:
+ "Ainesosia ei löytynyt. Varmista että skannaat ainesosaluetteloa.",
+ noIngredientsFound: "Ainesosia ei voitu tunnistaa. Yritä uudelleen.",
+ productNotFound:
+ "Tuotetta ei löytynyt. Kokeile skannata ainesosaluettelo suoraan.",
+ scanError: "Jotain meni pieleen. Yritä uudelleen.",
+ cameraError: "Kameraan ei saada yhteyttä. Tarkista käyttöoikeudet.",
+ networkError: "Verkkovirhe. Tarkista internetyhteytesi.",
+
+ // Camera Tips
+ scanTip1: "Varmista hyvä valaistus",
+ scanTip2: "Pidä kamera vakaana",
+ scanTip3: "Kohdista ainesosaluetteloon",
+ scanTip4: "Pidä teksti selkeänä ja luettavana",
+ scanTip5: "Vältä varjoja ja heijastuksia",
+ mobileDetected: "Mobiililaite havaittu",
+ cameraWorksBesetMobile: "Kamera toimii parhaiten mobiililaitteilla",
+
+ // Settings Page
+ profile: "Profiili",
+ notifications: "Ilmoitukset",
+ manageAlerts: "Hallinnoi hälytyksiä",
+ updateInfo: "Päivitä tietosi",
+ safetyThreshold: "Turvallisuuskynnys",
+ highSensitivity: "Korkea herkkyys",
+ darkMode: "Tumma Tila",
+ language: "Kieli",
+ helpCenter: "Ohje",
+ getSupport: "Hanki tukea",
+ aboutApp: "Tietoa PastPick",
+ version: "Versio 1.0.0",
+ account: "Tili",
+ preferences: "Asetukset",
+ support: "Tuki",
+
+ // Theme Options
+ lightTheme: "Vaalea",
+ darkTheme: "Tumma",
+ systemTheme: "Järjestelmän Oletus",
+
+ // Actions
+ retake: "Ota Uudelleen",
+ analyze: "Analysoi",
+ save: "Tallenna",
+ share: "Jaa",
+ viewDetails: "Näytä Tiedot",
+ tryAgain: "Yritä Uudelleen",
+ cancel: "Peruuta",
+ ok: "OK",
+ close: "Sulje",
+ back: "Takaisin",
+ next: "Seuraava",
+ done: "Valmis",
+
+ // Favorites
+ noFavorites: "Ei suosikkeja vielä",
+ noFavoritesDesc:
+ "Aloita tuotteiden skannaaminen lisätäksesi niitä suosikkeihin",
+ addToFavorites: "Lisää Suosikkeihin",
+ removeFromFavorites: "Poista Suosikeista",
+
+ // Recent Scans
+ recentScans: "Viimeisimmät Skannaukset",
+ noRecentScans: "Ei viimeaikaisia skannauksia",
+ noRecentScansDesc: "Skannaushistoriasi näkyy täällä",
+ scanHistory: "Skannaushistoria",
+ scannedAt: "Skannattu {time}",
+
+ // Scan Types
+ productFront: "Tuotteen Etupuoli",
+ ingredientsList: "Ainesosaluettelo",
+ packageBox: "Pakkaus/Laatikko",
+
+ // Analysis Details
+ overallAnalysis: "Kokonaisanalyysi",
+ recommendations: "Suositukset",
+ highlyRecommended: "Erittäin Suositeltava",
+ goodChoice: "Hyvä Valinta",
+ useWithCaution: "Käytä Varovasti",
+ notRecommended: "Ei Suositella",
+ generalSafetyTips: "Yleiset Turvallisuusvinkit",
+ noMajorConcerns: "Ei Merkittäviä Huolenaiheita",
+ limitedBenefits: "Rajoitetut Hyödyt",
+
+ // Ingredient Analysis
+ foundIngredients: "Löydettiin {count} ainesosaa",
+ analyzedWithDatabase: "Analysoitu turvallisuustietokannallamme",
+ whatThisMeans: "Mitä tämä tarkoittaa:",
+ whoShouldBeCareful: "Kenen tulisi olla varovainen:",
+ whatToDo: "Mitä tehdä:",
+
+ // Language Selection
+ selectLanguage: "Valitse Kieli",
+ currentLanguage: "Nykyinen Kieli",
+ changeLanguage: "Vaihda Kieli",
+
+ // Time Formats
+ hoursAgo: "{count} tuntia sitten",
+ daysAgo: "{count} päivää sitten",
+ minutesAgo: "{count} minuuttia sitten",
+ justNow: "juuri nyt",
+
+ // Pluralization
+ ingredientsAnalyzed: "{count} ainesosaa analysoitu",
+ concernsFound: "{count} huolenaihetta löydetty",
+
+ // Loading States
+ loading: "Ladataan...",
+ pleaseWait: "Odota hetki...",
+ },
+};
+
+// Ingredient translation mappings (Foreign → English)
+const ingredientTranslations = {
+ // Finnish to English
+ natriumfluoridi: "sodium fluoride",
+ kalsiumkarbonaatti: "calcium carbonate",
+ natriumlauryylisulfaatti: "sodium lauryl sulfate",
+ natriumlauryylieetterinylätti: "sodium laureth sulfate",
+ titaanidioksidi: "titanium dioxide",
+ "hydratoitu piidioksidi": "hydrated silica",
+ kaliumnitraatti: "potassium nitrate",
+ ksylitoli: "xylitol",
+ sorbitoli: "sorbitol",
+ glyseriini: "glycerin",
+ mentoli: "menthol",
+ tritriumfosfaatti: "trisodium phosphate",
+ natriumsakariini: "sodium saccharin",
+ selluloosakumi: "cellulose gum",
+ natriumhydroksidi: "sodium hydroxide",
+
+ // German to English
+ natriumfluorid: "sodium fluoride",
+ kalziumkarbonat: "calcium carbonate",
+ natriumlaurylsulfat: "sodium lauryl sulfate",
+ titandioxid: "titanium dioxide",
+ "hydratisierte kieselsäure": "hydrated silica",
+ kaliumnitrat: "potassium nitrate",
+ glyzerin: "glycerin",
+ natriumsaccharin: "sodium saccharin",
+
+ // French to English
+ "fluorure de sodium": "sodium fluoride",
+ "carbonate de calcium": "calcium carbonate",
+ "laurylsulfate de sodium": "sodium lauryl sulfate",
+ "dioxyde de titane": "titanium dioxide",
+ "silice hydratée": "hydrated silica",
+ "nitrate de potassium": "potassium nitrate",
+ glycérine: "glycerin",
+
+ // Spanish to English
+ "fluoruro de sodio": "sodium fluoride",
+ "carbonato de calcio": "calcium carbonate",
+ "lauril sulfato de sodio": "sodium lauryl sulfate",
+ "diĂłxido de titanio": "titanium dioxide",
+ "sĂlice hidratada": "hydrated silica",
+ "nitrato de potasio": "potassium nitrate",
+ glicerina: "glycerin",
+
+ // Common abbreviations and synonyms
+ sls: "sodium lauryl sulfate",
+ sles: "sodium laureth sulfate",
+ peg: "polyethylene glycol",
+ edta: "ethylenediaminetetraacetic acid",
+ bht: "butylated hydroxytoluene",
+ bha: "butylated hydroxyanisole",
+ "fdc&c": "food drug and cosmetic color",
+ "fd&c": "food drug and cosmetic color",
+ ci: "color index",
+};
+
+export const LanguageProvider = ({ children }) => {
+ const [language, setLanguage] = useState("en");
+
+ useEffect(() => {
+ // Load saved language from localStorage
+ const savedLanguage = localStorage.getItem("pastepick-language");
+ if (savedLanguage && translations[savedLanguage]) {
+ setLanguage(savedLanguage);
+ } else {
+ // Auto-detect browser language
+ const browserLang = navigator.language.slice(0, 2);
+ if (translations[browserLang]) {
+ setLanguage(browserLang);
+ }
+ }
+ }, []);
+
+ const changeLanguage = (lang) => {
+ if (translations[lang]) {
+ setLanguage(lang);
+ localStorage.setItem("pastepick-language", lang);
+ }
+ };
+
+ // Translation function with interpolation support
+ const t = (key, params = {}) => {
+ let translation =
+ translations[language][key] || translations["en"][key] || key;
+
+ // Handle parameter interpolation {param}
+ Object.keys(params).forEach((param) => {
+ translation = translation.replace(
+ new RegExp(`{${param}}`, "g"),
+ params[param],
+ );
+ });
+
+ return translation;
+ };
+
+ // Translate ingredients from foreign languages to English
+ const translateIngredients = (ingredientText) => {
+ if (!ingredientText) return "";
+
+ let translatedText = ingredientText.toLowerCase();
+
+ // Apply ingredient translations
+ Object.entries(ingredientTranslations).forEach(([foreign, english]) => {
+ const regex = new RegExp(foreign, "gi");
+ translatedText = translatedText.replace(regex, english);
+ });
+
+ return translatedText;
+ };
+
+ // Get available languages with their native names
+ const getAvailableLanguages = () => [
+ { code: "en", name: "English", nativeName: "English" },
+ { code: "fi", name: "Finnish", nativeName: "Suomi" },
+ ];
+
+ // Get current language info
+ const getCurrentLanguageInfo = () => {
+ const languages = getAvailableLanguages();
+ return languages.find((lang) => lang.code === language) || languages[0];
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useLanguage = () => {
+ const context = useContext(LanguageContext);
+ if (!context) {
+ throw new Error("useLanguage must be used within a LanguageProvider");
+ }
+ return context;
+};
diff --git a/src/contexts/ThemeContext.js b/src/contexts/ThemeContext.js
new file mode 100644
index 0000000..8e6d2fc
--- /dev/null
+++ b/src/contexts/ThemeContext.js
@@ -0,0 +1,88 @@
+"use client";
+
+import { createContext, useContext, useState, useEffect } from "react";
+
+const ThemeContext = createContext();
+
+export const ThemeProvider = ({ children }) => {
+ const [theme, setTheme] = useState("system"); // 'light', 'dark', or 'system'
+ const [actualTheme, setActualTheme] = useState("light"); // The actual theme being used
+
+ useEffect(() => {
+ // Load saved theme from localStorage
+ const savedTheme = localStorage.getItem("pastepick-theme");
+ if (savedTheme) {
+ setTheme(savedTheme);
+ }
+ }, []);
+
+ useEffect(() => {
+ const updateTheme = () => {
+ let newActualTheme = theme;
+
+ if (theme === "system") {
+ // Use system preference
+ newActualTheme = window.matchMedia("(prefers-color-scheme: dark)")
+ .matches
+ ? "dark"
+ : "light";
+ }
+
+ setActualTheme(newActualTheme);
+
+ // Update DOM
+ if (newActualTheme === "dark") {
+ document.documentElement.classList.add("dark");
+ } else {
+ document.documentElement.classList.remove("dark");
+ }
+ };
+
+ updateTheme();
+
+ // Listen for system theme changes
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
+ const handleChange = () => {
+ if (theme === "system") {
+ updateTheme();
+ }
+ };
+
+ mediaQuery.addEventListener("change", handleChange);
+ return () => mediaQuery.removeEventListener("change", handleChange);
+ }, [theme]);
+
+ const changeTheme = (newTheme) => {
+ setTheme(newTheme);
+ localStorage.setItem("pastepick-theme", newTheme);
+ };
+
+ const toggleTheme = () => {
+ const newTheme = actualTheme === "dark" ? "light" : "dark";
+ changeTheme(newTheme);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useTheme = () => {
+ const context = useContext(ThemeContext);
+ if (!context) {
+ throw new Error("useTheme must be used within a ThemeProvider");
+ }
+ return context;
+};
diff --git a/src/services/mockAnalysisService.js b/src/services/mockAnalysisService.js
new file mode 100644
index 0000000..65ef43c
--- /dev/null
+++ b/src/services/mockAnalysisService.js
@@ -0,0 +1,322 @@
+// Mock ingredient analysis service for PastPick
+
+// Database of common toothpaste ingredients with their analysis
+const ingredientDatabase = {
+ // Safe ingredients (7-10 score)
+ "potassium nitrate": {
+ name: "Potassium Nitrate",
+ score: 9.2,
+ category: "beneficial",
+ purpose: "Desensitizing agent",
+ description:
+ "Helps reduce tooth sensitivity by blocking pain signals to nerves",
+ concerns: [],
+ benefits: ["Reduces sensitivity", "Clinically proven", "FDA approved"],
+ impact: "positive",
+ },
+ "sodium fluoride": {
+ name: "Sodium Fluoride",
+ score: 9.0,
+ category: "beneficial",
+ purpose: "Anticavity agent",
+ description: "Prevents tooth decay and strengthens enamel",
+ concerns: [],
+ benefits: ["Prevents cavities", "Strengthens enamel", "ADA recommended"],
+ impact: "positive",
+ },
+ "calcium carbonate": {
+ name: "Calcium Carbonate",
+ score: 8.8,
+ category: "beneficial",
+ purpose: "Natural abrasive",
+ description: "Natural mineral that gently removes plaque and stains",
+ concerns: [],
+ benefits: ["Natural ingredient", "Gentle cleaning", "Whitening effect"],
+ impact: "positive",
+ },
+ xylitol: {
+ name: "Xylitol",
+ score: 9.5,
+ category: "beneficial",
+ purpose: "Natural sweetener",
+ description: "Natural sugar alcohol that helps prevent cavities",
+ concerns: [],
+ benefits: ["Prevents cavities", "Natural sweetener", "Reduces bacteria"],
+ impact: "positive",
+ },
+
+ // Moderate ingredients (5-6.9 score)
+ "hydrated silica": {
+ name: "Hydrated Silica",
+ score: 7.2,
+ category: "moderate",
+ purpose: "Mild abrasive",
+ description: "Helps remove plaque and surface stains gently",
+ concerns: ["May be too abrasive with excessive use"],
+ benefits: ["Effective cleaning", "Whitening properties"],
+ impact: "neutral",
+ },
+ "titanium dioxide": {
+ name: "Titanium Dioxide",
+ score: 6.5,
+ category: "concerning",
+ purpose: "Whitening agent",
+ description: "Provides whitening and opacity to toothpaste",
+ concerns: [
+ "Potential respiratory irritant",
+ "Under regulatory review",
+ "May cause inflammation",
+ ],
+ benefits: ["Whitening effect", "Color enhancement"],
+ impact: "negative",
+ },
+
+ // Concerning ingredients (3-4.9 score)
+ "sodium lauryl sulfate": {
+ name: "Sodium Lauryl Sulfate (SLS)",
+ score: 4.2,
+ category: "concerning",
+ purpose: "Foaming agent",
+ description: "Creates foam and helps distribute toothpaste",
+ concerns: [
+ "May cause mouth ulcers",
+ "Can irritate sensitive gums",
+ "Strips natural protective oils",
+ "May worsen canker sores",
+ ],
+ benefits: ["Creates satisfying foam", "Helps cleaning action"],
+ impact: "negative",
+ },
+ triclosan: {
+ name: "Triclosan",
+ score: 3.8,
+ category: "concerning",
+ purpose: "Antibacterial agent",
+ description: "Antimicrobial agent used to prevent plaque",
+ concerns: [
+ "Potential hormone disruption",
+ "Antibiotic resistance concerns",
+ "Environmental impact",
+ "Banned in some countries",
+ ],
+ benefits: ["Antibacterial properties", "Reduces plaque"],
+ impact: "negative",
+ },
+ "artificial colors": {
+ name: "Artificial Colors",
+ score: 4.0,
+ category: "concerning",
+ purpose: "Coloring agent",
+ description: "Synthetic dyes to make toothpaste visually appealing",
+ concerns: [
+ "No oral health benefit",
+ "Potential allergic reactions",
+ "Unnecessary chemical exposure",
+ ],
+ benefits: ["Aesthetic appeal"],
+ impact: "negative",
+ },
+
+ // Common synonyms and abbreviations
+ sls: "sodium lauryl sulfate",
+ "sodium fluoride 0.24%": "sodium fluoride",
+ "calcium carbonate precipitated": "calcium carbonate",
+};
+
+// Parse ingredient text and normalize names
+export const parseIngredients = (ingredientText) => {
+ if (!ingredientText) return [];
+
+ // Remove common prefixes
+ let cleanText = ingredientText
+ .toLowerCase()
+ .replace(/^(ingredients|ainesosat|inhaltsstoffe):\s*/i, "")
+ .replace(/\([^)]*\)/g, ""); // Remove parentheses content
+
+ // Split by common separators
+ const ingredients = cleanText
+ .split(/[,;.]/)
+ .map((ingredient) => ingredient.trim())
+ .filter((ingredient) => ingredient.length > 2)
+ .map((ingredient) => {
+ // Handle common synonyms
+ const normalized = ingredient.toLowerCase();
+ return ingredientDatabase[normalized] ? normalized : ingredient;
+ });
+
+ return ingredients;
+};
+
+// Analyze a single ingredient
+const analyzeIngredient = (ingredientName) => {
+ const normalizedName = ingredientName.toLowerCase().trim();
+
+ // Check if ingredient exists in database
+ if (ingredientDatabase[normalizedName]) {
+ return ingredientDatabase[normalizedName];
+ }
+
+ // For unknown ingredients, provide generic analysis
+ return {
+ name: ingredientName.charAt(0).toUpperCase() + ingredientName.slice(1),
+ score: 6.5, // Neutral score for unknown ingredients
+ category: "unknown",
+ purpose: "Not identified",
+ description:
+ "This ingredient is not in our database. Consider researching it independently.",
+ concerns: ["Unknown safety profile"],
+ benefits: ["Function not determined"],
+ impact: "neutral",
+ };
+};
+
+// Calculate overall score based on ingredients
+const calculateOverallScore = (analyzedIngredients) => {
+ if (analyzedIngredients.length === 0) return 5.0;
+
+ let weightedSum = 0;
+ let totalWeight = 0;
+
+ analyzedIngredients.forEach((ingredient, index) => {
+ // Earlier ingredients in the list have more weight (toothpaste ingredients are listed by concentration)
+ const weight = Math.max(1, analyzedIngredients.length - index);
+ weightedSum += ingredient.score * weight;
+ totalWeight += weight;
+ });
+
+ const baseScore = weightedSum / totalWeight;
+
+ // Apply penalties for concerning ingredients
+ const concerningIngredients = analyzedIngredients.filter(
+ (ing) => ing.category === "concerning",
+ );
+ const penalty = concerningIngredients.length * 0.5;
+
+ // Apply bonuses for beneficial ingredients
+ const beneficialIngredients = analyzedIngredients.filter(
+ (ing) => ing.category === "beneficial",
+ );
+ const bonus = Math.min(beneficialIngredients.length * 0.3, 1.0);
+
+ const finalScore = Math.max(1.0, Math.min(10.0, baseScore - penalty + bonus));
+ return Math.round(finalScore * 10) / 10; // Round to 1 decimal place
+};
+
+// Get key ingredients that most affected the score
+const getKeyIngredients = (analyzedIngredients) => {
+ const sortedIngredients = [...analyzedIngredients].sort((a, b) => {
+ // Sort by impact magnitude (distance from neutral score of 6.5)
+ const impactA = Math.abs(a.score - 6.5);
+ const impactB = Math.abs(b.score - 6.5);
+ return impactB - impactA;
+ });
+
+ const positive = sortedIngredients
+ .filter((ing) => ing.impact === "positive")
+ .slice(0, 3)
+ .map((ing) => ing.name);
+
+ const negative = sortedIngredients
+ .filter((ing) => ing.impact === "negative")
+ .slice(0, 3)
+ .map((ing) => ing.name);
+
+ return { positive, negative };
+};
+
+// Main analysis function
+export const analyzeIngredients = async (ingredientText) => {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ try {
+ // Parse ingredients from text
+ const ingredientNames = parseIngredients(ingredientText);
+
+ if (ingredientNames.length === 0) {
+ resolve({
+ success: false,
+ error: "No ingredients found in the text",
+ });
+ return;
+ }
+
+ // Analyze each ingredient
+ const analyzedIngredients = ingredientNames.map(analyzeIngredient);
+
+ // Calculate overall score
+ const overallScore = calculateOverallScore(analyzedIngredients);
+
+ // Get key ingredients
+ const keyIngredients = getKeyIngredients(analyzedIngredients);
+
+ // Generate overall assessment
+ let overallAssessment = "";
+ if (overallScore >= 8.5) {
+ overallAssessment =
+ "Excellent choice with high-quality, safe ingredients";
+ } else if (overallScore >= 7.0) {
+ overallAssessment = "Good formulation with mostly safe ingredients";
+ } else if (overallScore >= 5.5) {
+ overallAssessment =
+ "Average product with some ingredients of concern";
+ } else if (overallScore >= 3.5) {
+ overallAssessment =
+ "Below average with several concerning ingredients";
+ } else {
+ overallAssessment =
+ "Poor formulation with many problematic ingredients";
+ }
+
+ // Get primary concerns and benefits
+ const allConcerns = analyzedIngredients
+ .flatMap((ing) => ing.concerns)
+ .filter((concern, index, arr) => arr.indexOf(concern) === index)
+ .slice(0, 5);
+
+ const allBenefits = analyzedIngredients
+ .flatMap((ing) => ing.benefits)
+ .filter((benefit, index, arr) => arr.indexOf(benefit) === index)
+ .slice(0, 5);
+
+ resolve({
+ success: true,
+ analysis: {
+ overallScore,
+ overallAssessment,
+ totalIngredients: analyzedIngredients.length,
+ keyIngredients,
+ primaryConcerns: allConcerns,
+ primaryBenefits: allBenefits,
+ ingredients: analyzedIngredients,
+ scoreBreakdown: {
+ beneficial: analyzedIngredients.filter(
+ (ing) => ing.category === "beneficial",
+ ).length,
+ moderate: analyzedIngredients.filter(
+ (ing) => ing.category === "moderate",
+ ).length,
+ concerning: analyzedIngredients.filter(
+ (ing) => ing.category === "concerning",
+ ).length,
+ unknown: analyzedIngredients.filter(
+ (ing) => ing.category === "unknown",
+ ).length,
+ },
+ },
+ });
+ } catch (error) {
+ resolve({
+ success: false,
+ error: "Error analyzing ingredients: " + error.message,
+ });
+ }
+ }, 1500); // Simulate processing time
+ });
+};
+
+// Sample ingredient texts for testing
+export const sampleIngredientTexts = [
+ "Ingredients: Potassium Nitrate, Sodium Fluoride, Hydrated Silica, Calcium Carbonate, Water, Natural Mint Flavor",
+ "Ingredients: Sodium Lauryl Sulfate, Titanium Dioxide, Triclosan, Artificial Colors, Sodium Fluoride, Water",
+ "Ingredients: Calcium Carbonate, Xylitol, Natural Flavors, Sodium Fluoride, Potassium Nitrate, Cellulose Gum",
+];