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
-
+
setSearch(p => ({...p, product: e.target.value}))} className="flex-grow px-4 py-3 bg-gray-50 border-2 border-gray-100 rounded-xl focus:border-black outline-none text-sm font-bold transition-all" /> setSearch(p => ({...p, category: e.target.value}))} className="flex-grow px-4 py-3 bg-gray-50 border-2 border-gray-100 rounded-xl focus:border-black outline-none text-sm font-bold transition-all" @@ -149,7 +187,7 @@ const App: React.FC = () => {
setSearch(p => ({...p, country: e.target.value}))} className="w-full pl-10 pr-4 py-3 bg-gray-50 border-2 border-gray-100 rounded-xl focus:border-black outline-none text-sm font-bold transition-all" @@ -166,43 +204,85 @@ const App: React.FC = () => {
-
+
{search.error && ( -
{search.error}
+
+

{search.error}

+ +
)} {search.loading && !data && (
-
-
OHO!
+
+
-

{loadMessage}

+

{loadMessage}

)} {data && ( <> - {/* Metric Strip - Styled like Cultural Nuance Boxes */} -
- {data.marketMetrics && [data.marketMetrics.roas, data.marketMetrics.engagement, data.marketMetrics.fatigue, data.marketMetrics.sov].map((m, i) => m && ( -
-
- {m.label} -
- {m.value} - {m.trend === 'up' ? : } -
-

{m.explanation}

-
+ {/* 1. Header: Status & Metrics */} +
+
+
+ + Live Market Pulse
- ))} + {lastUpdated && ( +
+ + Audit Sync: {lastUpdated} +
+ )} +
+ + {/* Metric Strip */} +
+ {(Object.values(data.marketMetrics) as MarketMetric[]).map((m, i) => m && ( +
+
+
+ {m.label} + +
+
+ {m.value} + {m.trend === 'up' ? : } +
+

{m.explanation}

+
+
+ ))} +
- {/* Cultural Velocity & Share of Search */} -
-
+ {/* 2. SOS & Velocity Row */} +
+
+
+

Share of Search

+ +
+
+ {data.shareOfSearch?.map((item, i) => ( +
+
+ {item.competitor} + {item.percentage}% +
+
+
+
+
+ ))} +
+
+ +
-

Cultural Velocity

+

Cultural Velocity

@@ -219,263 +299,268 @@ const App: React.FC = () => { ))}
- -
-
-

Share of Search

- -
-
- {data.shareOfSearch?.map((item, i) => ( -
-
- {item.competitor} - {item.percentage}% -
-
-
-
-
- ))} -
-
- {/* Inverted Claim Density Word Cloud */} -
-
-

Claim Density

- +
+
+ +

Grounded Competitor Claim Density

-
- {data.competitorClaims?.map((claim, idx) => ( - - ))} -
-
- {hoveredClaim ? ( -
- {hoveredClaim.keyword}: - "{hoveredClaim.explanation}" -
- ) : ( - Hover over keywords for market insights - )} -
-
- - {/* Efficacy Benchmark & Creators */} -
-
-
- The Efficacy Benchmark aggregates content resonance, sentiment alignment, and conversion velocity against category leaders. -
- -
-
- Efficacy Benchmark -
-

Content Index

-
- {data.radarMetrics?.slice(0, 4).map((rm, i) => ( -
-
{rm.label}
-
{rm.score}%
-
+
+
+ {data.competitorClaims.map((claim, idx) => ( + ))}
+ {hoveredClaim && ( +
+
+
+ {hoveredClaim.keyword} + +
+
+

"{hoveredClaim.explanation}"

+
+ )} +
+
+ + {/* 4. Benchmarking Row: Efficacy Benchmark & High-Impact Creators */} +
+
+
+

Efficacy Benchmark

+
-
-
-
{dynamicQualityScore}.
-
Quality
-
+
+ {data.radarMetrics?.map((metric, i) => ( +
+
+ {metric.label} + {metric.score}/100 +
+
+
+
+
+
Benchmark: {metric.benchmark}
+
+ ))}
-
-
-

Top Creators

- +
+
+

High-Impact Creators

+
-
- {data.topCreators?.slice(0, 4).map((cr, i) => ( -
-
-
{cr.name}
-
{cr.handle}
+
+ {data.topCreators?.slice(0, 3).map((creator, i) => ( +
+
+ {creator.name.charAt(0)}
- +
+
{creator.name} @{creator.handle}
+
{creator.category}
+
"{creator.impact}"
+
+
))}
- {/* Executive Intelligence + Brief */} -
-
-
- -

Executive Intelligence + Brief

-
-
- - -
+ {/* 5. Whitespace Analysis Row */} +
+
+ +

Strategic Whitespace Analysis

- -
-
+
+ {data.whitespaceAnalysis?.map((item, i) => ( + + ))} +
+
+ + {/* 6. Intelligence Core */} +
+
+
+
-

Strategic Overview

-

{data.summary?.overview}

+
+ +

Executive Intelligence

+
+

+ {data.summary.overview} +

- -
-
-
-

Core Objective

-

{data.agencyBrief?.objective}

-
-
-

Target Demographic

-

{data.agencyBrief?.targetAudience}

-
-
- -
-

Market Proposition

-

{data.agencyBrief?.keyMessage}

-
- {data.agencyBrief?.creativeHooks?.map((hook, i) => ( - - {hook} +
+

+ Strategic Takeaways +

+
    + {data.summary.strategicTakeaways.map((point, idx) => ( +
  • + + {idx + 1} - ))} -
-
+ {point} + + ))} +
- -
-

- Tactical Options -

-
- {data.agencyBrief?.tacticalContent?.map((tactical, i) => ( -
-
- {tactical.channel} - -
-

{tactical.format}

-

{tactical.description}

-
+
+
+
+ +

Tactical Brief

+
+ +
+
+
+ +
+
Core Objective
+

{data.agencyBrief.objective}

+
+
+
+ +
+
Target Persona
+

{data.agencyBrief.targetAudience}

+
+
+
+
+
+ Actionable Strategies +
+
    + {data.agencyBrief.creativeHooks.map((hook, i) => ( +
  • + / +

    {hook}

    +
  • ))} -
+ +
- {/* Cultural Nuance Section */} -
+ {/* 7. Cultural Nuance Section (Always 4 items requested via service) */} +

Cultural Nuance

-
- {data.culturalNuances?.map((nuance, i) => ( -
+
+ {data.culturalNuances?.slice(0, 4).map((nuance, i) => ( +
-

{nuance.term}

- - - Verified - +

{nuance.term}

+
- -

- {nuance.insight} -

- -
-
Oho! Tip
-

- {nuance.strategyTip} -

+

{nuance.insight}

+
+
Strategic Pivot
+

{nuance.strategyTip}

))}
+ + {/* 8. Industry Pulse Section (Moved to End) */} +
+
+ +

Industry Pulse

+
+
+ {data.newsFeed?.map((news, i) => ( +
+
+
{news.source}
+
+ {getSentimentIcon(news.sentiment)} + {news.score} +
+
+

"{news.headline}"

+
+ +
+
+ ))} +
+
+ + {/* 9. Audit Trail & Reference Hub (Bottom) */} +
+
+
+

+ Grounded Audit Trail & Reference Hub +

+
+ {data.summary?.sources?.map((source, idx) => ( + + {source.title} + + + ))} +
+
+

System processed via Gemini-3 High-Density Reasoning Platform

+
+
+
)}
- {/* FOOTER: Audited Sources */} - {data && ( -
-
-

- Audited Records & Sources -

-
- {data.summary?.sources?.map((source, idx) => ( - - {source.title} - - - ))} -
-
-
- )} - - {/* Floating Controls */} + {/* Persistent Controls */}
-
-

Real-Time Auditing

+
+

Grounded Intelligence

- Oho! synthesizes real-time market insights grounded in live global search data via Gemini-3-Flash. + The APAC Strategy & Insights Engine utilizes Gemini-3-Pro with Google Search grounding to synthesize real-time audits. Every metric is backed by live market data.

@@ -500,8 +585,8 @@ const App: React.FC = () => { )} {/* Side Accents */} -
-
+
+
); }; diff --git a/components/InsightCard.tsx b/components/InsightCard.tsx index 22c10d5..9d649e2 100644 --- a/components/InsightCard.tsx +++ b/components/InsightCard.tsx @@ -1,62 +1,3 @@ -import React from 'react'; -import { ExternalLink, CheckCircle2 } from 'lucide-react'; -import { SocialContent } from '../types'; - -interface InsightCardProps { - insight: SocialContent; - // Adding index for unique image generation - index: number; -} - -export const InsightCard: React.FC = ({ insight, index }) => { - // Use platform or headline for image placeholder - const imageSeed = insight.platform.toLowerCase(); - - return ( -
-
- - - Verified Source - -
- - #{insight.platform} - -
-
- -
- {insight.headline} -
- Platform: - {insight.platform} content strategy -
-
- -

{insight.headline}

-

- Key social performance indicator: {insight.metrics} -

- -
-
- KPI Engagement - {insight.metrics} -
- - -
-
- ); -}; +// This component has been deprecated and removed from the active dashboard layout. +export {}; diff --git a/components/StrategicSummary.tsx b/components/StrategicSummary.tsx index a2bd7b9..47fe9f2 100644 --- a/components/StrategicSummary.tsx +++ b/components/StrategicSummary.tsx @@ -1,76 +1,3 @@ -import React from 'react'; -import { Summary } from '../types'; -import { ShieldCheck, Info, ArrowUpRight, Target } from 'lucide-react'; - -interface StrategicSummaryProps { - summary: Summary | null; - loading: boolean; -} - -export const StrategicSummary: React.FC = ({ summary, loading }) => { - if (loading) { - return ( -