import React, { useState, useEffect, useCallback } from ‘react’;
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, PieChart, Pie, Cell } from ‘recharts’;
import { Zap, Leaf, ShoppingBag, TrendingUp, Users, Info, Rocket, Sparkles } from ‘lucide-react’; // Added Sparkles icon for AI
// Utility for exponential backoff during API calls
const exponentialBackoff = async (func, retries = 3, delay = 1000) => {
try {
return await func();
} catch (error) {
if (retries > 0 && error.message.includes(‘429’)) {
console.warn(`Retrying after ${delay}ms… (${retries} retries left)`);
await new Promise(res => setTimeout(res, delay));
return exponentialBackoff(func, retries – 1, delay * 2);
}
throw error;
}
};
// Main App component for the Sustainable Retail Insights Simulator (SRIS)
function App() {
// State for simulated retailer baseline data – now editable for retailer name
const [baseline, setBaseline] = useState({
retailerName: ‘Your Retailer’, // Default name, now editable
annualCustomers: 5000000, // 5 million annual customers
highPlasticUnits: 13000000, // E.g., 5M berries + 8M ready meals = 13M units
avgGreenIntentCustomers: 2000000, // 40% of 5M annual customers
plasticReductionTarget: 0.20, // 20% by 2030
avgBasketValue: 50, // Average basket value in GBP
sustainableProductAvgPrice: 3, // Average price of a sustainable alternative
sustainableProductMargin: 0.35, // Higher margin for sustainable products
carbonPerPlasticUnitKg: 0.04, // Kg CO2 equivalent per plastic unit (hypothetical)
});
// State for selected nudge strategies and their effectiveness multipliers
const [nudges, setNudges] = useState({
personalizedOffer: {
enabled: true,
label: “Personalized Digital Offer”,
description: “Targeted app/email notifications for green alternatives.”,
conversionRate: 0.07, // 7% conversion for target group
upliftSustainableSales: 0.15, // 15% uplift in sustainable sales for converted customers
customerRetentionLift: 0.005, // 0.5% lift in retention for engaged customers
},
posPrompt: {
enabled: false,
label: “Point-of-Sale Prompt”,
description: “In-store digital prompts at checkout for greener choices.”,
conversionRate: 0.03, // 3% conversion for target group
upliftSustainableSales: 0.08,
customerRetentionLift: 0.002,
},
gamification: {
enabled: false,
label: “Sustainability Gamification”,
description: “Loyalty points/badges for choosing eco-friendly products.”,
engagementRate: 0.10, // 10% engagement in gamified features
sustainablePurchaseLift: 0.05, // 5% additional sustainable purchase lift for engaged users
brandSentimentLift: 0.02, // 2% positive brand sentiment lift
},
});
// State for AI-generated strategic summary based on simulation results
const [geminiStrategicSummary, setGeminiStrategicSummary] = useState(”);
const [loadingGemini, setLoadingGemini] = useState(false);
const [geminiError, setGeminiError] = useState(”);
// Calculated results state
const [results, setResults] = useState(null);
// Function to perform the simulation based on current state
const performSimulation = useCallback(() => {
let projectedPlasticUnitsAvoided = 0;
let projectedSustainableSalesIncrease = 0;
let projectedCustomerRetentionIncrease = 0;
let projectedCarbonAvoidedKg = 0;
let projectedBrandSentimentLift = 0;
const targetCustomers = baseline.avgGreenIntentCustomers;
// const initialHighPlasticSalesValue = baseline.highPlasticUnits * baseline.sustainableProductAvgPrice; // Rough value – not directly used in final results display
// Calculate impact from selected nudges
if (nudges.personalizedOffer.enabled) {
const convertedCustomers = targetCustomers * nudges.personalizedOffer.conversionRate;
projectedPlasticUnitsAvoided += convertedCustomers * (baseline.highPlasticUnits / baseline.annualCustomers); // Assuming 1 plastic unit per converted customer avg
projectedSustainableSalesIncrease += convertedCustomers * baseline.sustainableProductAvgPrice * (1 + nudges.personalizedOffer.upliftSustainableSales);
projectedCustomerRetentionIncrease += convertedCustomers * nudges.personalizedOffer.customerRetentionLift;
}
if (nudges.posPrompt.enabled) {
const convertedCustomers = targetCustomers * nudges.posPrompt.conversionRate;
projectedPlasticUnitsAvoided += convertedCustomers * (baseline.highPlasticUnits / baseline.annualCustomers);
projectedSustainableSalesIncrease += convertedCustomers * baseline.sustainableProductAvgPrice * (1 + nudges.posPrompt.upliftSustainableSales);
projectedCustomerRetentionIncrease += convertedCustomers * nudges.posPrompt.customerRetentionLift;
}
if (nudges.gamification.enabled) {
const engagedCustomers = targetCustomers * nudges.gamification.engagementRate;
projectedSustainableSalesIncrease += engagedCustomers * baseline.sustainableProductAvgPrice * nudges.gamification.sustainablePurchaseLift;
projectedBrandSentimentLift += baseline.annualCustomers * nudges.gamification.brandSentimentLift;
}
projectedCarbonAvoidedKg = projectedPlasticUnitsAvoided * baseline.carbonPerPlasticUnitKg;
const totalSustainableRevenueIncrease = projectedSustainableSalesIncrease * baseline.sustainableProductMargin;
const totalCLVIncrease = projectedCustomerRetentionIncrease * baseline.avgBasketValue * 12; // CLV lift over 1 year (monthly value * 12)
setResults({
projectedPlasticUnitsAvoided: Math.round(projectedPlasticUnitsAvoided),
projectedCarbonAvoidedKg: Math.round(projectedCarbonAvoidedKg),
projectedSustainableRevenueIncrease: Math.round(totalSustainableRevenueIncrease),
projectedCustomerRetentionIncrease: Math.round(projectedCustomerRetentionIncrease),
projectedBrandSentimentLift: Math.round(projectedBrandSentimentLift),
totalEstimatedAnnualImpact: Math.round(totalSustainableRevenueIncrease + totalCLVIncrease), // Simplified sum for overall impact
});
}, [baseline, nudges]);
// Perform simulation on initial load and whenever baseline or nudges change
useEffect(() => {
performSimulation();
}, [performSimulation]);
// Handler for enabling/disabling nudges
const handleNudgeToggle = (nudgeName) => {
setNudges(prev => ({
…prev,
[nudgeName]: { …prev[nudgeName], enabled: !prev[nudgeName].enabled },
}));
};
// Handler for the Gemini API call to generate strategic summary
const generateGeminiStrategicSummary = async () => {
if (!results) {
setGeminiError(‘Please run a simulation first to generate results.’);
return;
}
setLoadingGemini(true);
setGeminiStrategicSummary(”);
setGeminiError(”);
const prompt = `Based on the following simulated retail impact results for “${baseline.retailerName}”:
– Plastic Units Avoided: ${results.projectedPlasticUnitsAvoided.toLocaleString()}
– Carbon Avoided (Kg): ${results.projectedCarbonAvoidedKg.toLocaleString()}
– Sustainable Revenue Increase: £${results.projectedSustainableRevenueIncrease.toLocaleString()}
– Projected CLV Increase: £${(results.projectedCustomerRetentionIncrease * baseline.avgBasketValue * 12).toLocaleString()}
– Total Estimated Annual Impact: £${results.totalEstimatedAnnualImpact.toLocaleString()}
As a digital transformation expert for UK retailers focused on sustainable impact, provide a very concise (2-3 sentences) strategic summary of these results. Highlight the dual commercial and environmental benefits and suggest a high-level next step.`;
let chatHistory = [];
chatHistory.push({ role: “user”, parts: [{ text: prompt }] });
const payload = { contents: chatHistory };
const apiKey = “”; // Leave as-is for Canvas runtime
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent?key=${apiKey}`;
try {
const response = await exponentialBackoff(async () => {
const res = await fetch(apiUrl, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify(payload)
});
if (!res.ok) {
const errorBody = await res.json();
throw new Error(`API error: ${res.status} ${res.statusText} – ${JSON.stringify(errorBody)}`);
}
return res;
});
const result = await response.json();
if (result.candidates && result.candidates.length > 0 &&
result.candidates[0].content && result.candidates[0].content.parts &&
result.candidates[0].content.parts.length > 0) {
const text = result.candidates[0].content.parts[0].text;
setGeminiStrategicSummary(text);
} else {
setGeminiError(‘Could not generate strategic summary. Please try again.’);
}
} catch (error) {
console.error(‘Error calling Gemini API:’, error);
setGeminiError(`Failed to connect or generate: ${error.message}`);
} finally {
setLoadingGemini(false);
}
};
// Data for the commercial impact chart
const commercialImpactData = results ? [
{ name: ‘Sustainable Revenue Increase’, value: results.projectedSustainableRevenueIncrease },
{ name: ‘Projected CLV Increase’, value: results.projectedCustomerRetentionIncrease * baseline.avgBasketValue * 12 },
] : [];
// Recharts data for Environmental Impact (Pie Chart)
const environmentalData = results ? [
{ name: ‘Plastic Avoided (Units)’, value: results.projectedPlasticUnitsAvoided, color: ‘#047857’ },
{ name: ‘Carbon Avoided (Kg)’, value: results.projectedCarbonAvoidedKg, color: ‘#34d399’ },
] : [];
const COLORS = [‘#047857’, ‘#34d399’, ‘#fcd34d’, ‘#fb923c’]; // Tailwind emerald and amber shades
// Custom label for Pie Chart slices
const renderCustomizedLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, percent, index }) => {
const RADIAN = Math.PI / 180;
const radius = innerRadius + (outerRadius – innerRadius) * 0.5;
const x = cx + radius * Math.cos(-midAngle * RADIAN);
const y = cy + radius * Math.sin(-midAngle * RADIAN);
return (
{`${(percent * 100).toFixed(0)}%`}
);
};
return (
// Tailwind CSS CDN should be included in your root index.html
//
Sustainable Retail Insights Simulator (SRIS)
Powering Data-Driven Sustainable Impact for Retailers
{/* Input Section */}
Simulation Setup
{/* Retailer Baseline Inputs */}
Retailer Baseline
setBaseline({ …baseline, retailerName: e.target.value })}
/>
setBaseline({ …baseline, annualCustomers: parseInt(e.target.value) || 0 })}
min=”0″
/>
setBaseline({ …baseline, highPlasticUnits: parseInt(e.target.value) || 0 })}
min=”0″
/>
setBaseline({ …baseline, avgGreenIntentCustomers: parseInt(e.target.value) || 0 })}
min=”0″
/>
{/* Nudge Strategy Selection moved to lg:col-span-2 to give it more space, alongside the results now */}
Choose Sustainable Nudge Strategies
{Object.keys(nudges).map((key) => (
handleNudgeToggle(key)}
className=”h-5 w-5 text-emerald-400 rounded-md focus:ring-emerald-300 border-gray-300 mt-1 flex-shrink-0″
/>
{nudges[key].description}
))}
Select one or more strategies to simulate their combined impact.
{/* Results Section */}
Simulated Impact Report (6-Month Pilot)
{results && (
{/* Environmental Impact Cards */}
Plastic Units Avoided
{results.projectedPlasticUnitsAvoided.toLocaleString()} units
Carbon Avoided (Approx.)
{results.projectedCarbonAvoidedKg.toLocaleString()} kg CO2e
{/* Commercial Impact Cards */}
Sustainable Revenue Increase
£{results.projectedSustainableRevenueIncrease.toLocaleString()}
Projected CLV Increase
£{(results.projectedCustomerRetentionIncrease * baseline.avgBasketValue * 12).toLocaleString()}
Total Estimated Annual Impact
£{results.totalEstimatedAnnualImpact.toLocaleString()}
)}
{/* Charts for detailed visualization */}
Commercial Impact Breakdown (GBP)
Environmental Impact (Units & Kg)
{environmentalData.map((entry, index) => (
))}
{/* ✨ AI-Powered Strategic Summary Button & Display */}
AI-Powered Strategic Summary
Leverage Intrinsic Communications’ AI to quickly synthesize these insights into actionable next steps.
{geminiError && (
{geminiError}
)}
{geminiStrategicSummary && (
{geminiStrategicSummary}
)}
{/* Call to Action for Intrinsic Communications – unchanged */}
Turn Insights Into Real Impact.
The SRIS is a glimpse into what’s possible. Intrinsic Communications specializes in translating these strategic insights into actionable, data-driven digital campaigns that deliver both commercial growth and genuine sustainable impact for your brand.
);
}
export default App;