diff --git a/App.tsx b/App.tsx
index 00f2e89..76af19d 100644
--- a/App.tsx
+++ b/App.tsx
@@ -1,14 +1,64 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import {
Search, FileDown, Info, LayoutGrid, Zap, Sparkles, Loader2,
TrendingUp, TrendingDown, Minus, Target, Eye, Layers,
- Newspaper, BarChart3, Cloud, MoreHorizontal, Activity,
- Play, Users, Copy, RefreshCw, Quote, ArrowUpRight, ShieldCheck, Globe, Instagram, Video, Clapperboard, MonitorPlay, CheckCircle2
+ BarChart3, Cloud, Activity, Globe, Rocket, ShieldCheck,
+ Target as TargetIcon, UserCheck, MessageSquare, Copy,
+ ArrowUpRight, CheckCircle2, Lightbulb, AlertTriangle, Clock,
+ Users, Newspaper, Smile, Frown, Meh, Star, Compass
} from 'lucide-react';
-import { DashboardData, SearchState, CompetitorClaim, MarketMetric } from './types';
+import { DashboardData, SearchState, CompetitorClaim, MarketMetric, WhitespaceItem, Creator, NewsSentiment, RadarMetric } from './types';
import { fetchMarketInsights } from './geminiService';
+const VerifiedSourceBadge: React.FC<{ source?: string; methodology?: string }> = ({ source, methodology }) => {
+ if (!source) return null;
+ return (
+
+
+
+ Verified Source
+
+
+
Primary Source:
+
{source}
+
Methodology:
+
{methodology || "Grounded Strategic Synthesis"}
+
+
+
+ );
+};
+
+const WhitespaceCard: React.FC<{ item: WhitespaceItem }> = ({ item }) => {
+ const isOpportunity = item.type === 'OPPORTUNITY';
+ return (
+
+
+ {isOpportunity ?
:
}
+
+ {item.type}
+
+
+
{item.title}
+
{item.description}
+
+ );
+};
+
+const Logo = () => (
+
+);
+
const App: React.FC = () => {
const [search, setSearch] = useState({
product: '',
@@ -20,13 +70,14 @@ const App: React.FC = () => {
const [data, setData] = useState(null);
const [hoveredClaim, setHoveredClaim] = useState(null);
const [loadMessage, setLoadMessage] = useState('Initiating Audit...');
- const [isRefreshingBrief, setIsRefreshingBrief] = useState(false);
+ const [lastUpdated, setLastUpdated] = useState(null);
const handleSearch = async (e?: React.FormEvent) => {
e?.preventDefault();
if (!search.product || !search.category || search.loading) return;
setSearch(prev => ({ ...prev, loading: true, error: null }));
+ setData(null);
setLoadMessage('Grounded Search Active...');
const messages = [
@@ -45,11 +96,12 @@ const App: React.FC = () => {
const locationContext = search.country ? `in ${search.country}` : "across the APAC region";
const result = await fetchMarketInsights(`${search.product} ${locationContext}`, search.category);
setData(result);
+ setLastUpdated(new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }));
} catch (err: any) {
console.error(err);
setSearch(prev => ({
...prev,
- error: 'Network Busy: The AI Search service is temporarily overloaded. Please try again.'
+ error: err.message || 'System busy. Please try again.'
}));
} finally {
clearInterval(interval);
@@ -57,32 +109,13 @@ const App: React.FC = () => {
}
};
- const handleRefreshBrief = async () => {
- if (!data || isRefreshingBrief) return;
- setIsRefreshingBrief(true);
- try {
- const result = await fetchMarketInsights(search.product, search.category);
- setData(prev => prev ? { ...prev, agencyBrief: result.agencyBrief, summary: result.summary, socialHighlights: result.socialHighlights } : result);
- } catch (err) {
- console.error("Failed to refresh brief", err);
- } finally {
- setIsRefreshingBrief(false);
- }
- };
-
- const copyBrief = () => {
- if (!data) return;
- const text = `Objective: ${data.agencyBrief?.objective}\nAudience: ${data.agencyBrief?.targetAudience}\nMessage: ${data.agencyBrief?.keyMessage}`;
- navigator.clipboard.writeText(text);
- };
-
const handleExportHtml = () => {
if (!data) return;
const htmlContent = `
- Oho! Report - ${search.product}
+ APAC Strategy & Insights Engine Report - ${search.product}
@@ -96,16 +129,20 @@ const App: React.FC = () => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
- link.download = `Oho_Report_${search.product.replace(/\s+/g, '_')}.html`;
+ link.download = `APAC_Strategy_Insights_Engine_${search.product.replace(/\s+/g, '_')}.html`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
- const dynamicQualityScore = data?.radarMetrics?.length
- ? Math.round(data.radarMetrics.reduce((acc, m) => acc + m.score, 0) / data.radarMetrics.length)
- : 78;
+ const getSentimentIcon = (sentiment: string) => {
+ switch (sentiment) {
+ case 'positive': return ;
+ case 'negative': return ;
+ default: return ;
+ }
+ };
return (
@@ -125,23 +162,24 @@ const App: React.FC = () => {
-
-
-
OHO
-
!
+
window.location.reload()}>
+
+
+ APAC Strategy
+ & Insights Engine
-
+