|
| 1 | +import React, { useState } from "react"; |
| 2 | + |
| 3 | +// SmartMarketingAssistant.jsx |
| 4 | +// Single-file React component (JavaScript + Tailwind classes) for DECA project |
| 5 | +// - Default export is the App component |
| 6 | +// - Uses Tailwind CSS for styling (assumes Tailwind is installed in host project) |
| 7 | +// - Includes mock AI logic for channel recommendation, posting-time prediction, and ad-copy generation |
| 8 | +// - Includes option to connect to a real AI API by replacing mockGenerate* functions with real fetch calls |
| 9 | + |
| 10 | +// ---------- Utility helpers (mock AI) ---------- |
| 11 | +function analyzeBusinessProfile({ industry, audienceAge, audienceLocation, averagePrice }) { |
| 12 | + // Basic heuristic scoring for channels |
| 13 | + const scores = { |
| 14 | + instagram: 0, |
| 15 | + tiktok: 0, |
| 16 | + facebook: 0, |
| 17 | + linkedin: 0, |
| 18 | + email: 0, |
| 19 | + google: 0, |
| 20 | + }; |
| 21 | + |
| 22 | + // Industry biases |
| 23 | + const youthIndustries = ["fashion", "beauty", "food", "entertainment", "gaming"]; |
| 24 | + const b2bIndustries = ["consulting", "software", "hardware", "manufacturing"]; |
| 25 | + |
| 26 | + if (youthIndustries.includes(industry)) { |
| 27 | + scores.instagram += 3; scores.tiktok += 4; scores.facebook += 1; scores.email += 0; |
| 28 | + } |
| 29 | + if (b2bIndustries.includes(industry)) { |
| 30 | + scores.linkedin += 4; scores.email += 3; scores.google += 2; |
| 31 | + } |
| 32 | + |
| 33 | + // Age influence |
| 34 | + if (audienceAge === "under25") { scores.tiktok += 3; scores.instagram += 2; } |
| 35 | + if (audienceAge === "25to44") { scores.instagram += 2; scores.facebook += 2; } |
| 36 | + if (audienceAge === "45plus") { scores.facebook += 3; scores.email += 2; } |
| 37 | + |
| 38 | + // Price influence |
| 39 | + if (averagePrice > 200) { scores.linkedin += 1; scores.google += 2; } |
| 40 | + if (averagePrice < 50) { scores.tiktok += 1; scores.instagram += 1; } |
| 41 | + |
| 42 | + // Location (global vs local) |
| 43 | + if (audienceLocation === "local") { |
| 44 | + scores.facebook += 2; scores.google += 2; scores.email += 1; |
| 45 | + } else { |
| 46 | + // worldwide |
| 47 | + scores.instagram += 1; scores.tiktok += 1; scores.google += 1; |
| 48 | + } |
| 49 | + |
| 50 | + // Convert to sorted recommendations |
| 51 | + const recommendations = Object.entries(scores) |
| 52 | + .sort((a, b) => b[1] - a[1]) |
| 53 | + .map(([k, v]) => ({ channel: k, score: v })); |
| 54 | + |
| 55 | + return recommendations; |
| 56 | +} |
| 57 | + |
| 58 | +function predictPostingTimes(recommendations) { |
| 59 | + // Mock posting time suggestions based on channel |
| 60 | + const times = { |
| 61 | + instagram: ["Weekdays 11:00–13:00", "Evenings 19:00–21:00"], |
| 62 | + tiktok: ["Evenings 18:00–23:00", "Weekends 10:00–14:00"], |
| 63 | + facebook: ["Weekdays 10:00–12:00", "Weekdays 18:00–20:00"], |
| 64 | + linkedin: ["Weekdays 07:30–09:00", "Weekdays 12:00–13:30"], |
| 65 | + email: ["Tuesday or Thursday 09:00–11:00"], |
| 66 | + google: ["Continuous (PPC peak bids at 09:00–17:00)"] |
| 67 | + }; |
| 68 | + |
| 69 | + return recommendations.slice(0, 3).map(r => ({ channel: r.channel, times: times[r.channel] || ["Anytime"] })); |
| 70 | +} |
| 71 | + |
| 72 | +function generateAdCopy({ businessName, product, tone, channel, callToAction }) { |
| 73 | + // Small templates for each channel |
| 74 | + const templates = { |
| 75 | + instagram: [ |
| 76 | + `${product} ✨ by ${businessName} — ${tone} & affordable. ${callToAction}`, |
| 77 | + `New drop: ${product}. Tap to shop and enjoy exclusive savings! ${callToAction}` |
| 78 | + ], |
| 79 | + tiktok: [ |
| 80 | + `You won't believe how ${product} transforms your day. ${callToAction}`, |
| 81 | + `Trend alert! ${product} from ${businessName} — watch till the end. ${callToAction}` |
| 82 | + ], |
| 83 | + facebook: [ |
| 84 | + `${businessName} presents ${product}. Trusted by customers worldwide. ${callToAction}`, |
| 85 | + `Discover ${product} — quality and value. Shop now at ${businessName}. ${callToAction}` |
| 86 | + ], |
| 87 | + linkedin: [ |
| 88 | + `${product} from ${businessName} increases efficiency and ROI. Contact us to learn more. ${callToAction}` |
| 89 | + ], |
| 90 | + email: [ |
| 91 | + `Subject: ${product} — Special offer from ${businessName}\n\nHi there,\nCheck out ${product} and enjoy an exclusive deal. ${callToAction}` |
| 92 | + ], |
| 93 | + google: [ |
| 94 | + `${product} — ${businessName}. Buy now. ${callToAction}` |
| 95 | + ] |
| 96 | + }; |
| 97 | + |
| 98 | + const list = templates[channel] || [`Check out ${product} at ${businessName}. ${callToAction}`]; |
| 99 | + // return first + a variant |
| 100 | + return { primary: list[0], variant: list[1] || list[0] }; |
| 101 | +} |
| 102 | + |
| 103 | +// ---------- Small UI components ---------- |
| 104 | +function Badge({ children }) { |
| 105 | + return <span className="inline-block bg-indigo-100 text-indigo-700 px-2 py-1 rounded-full text-xs font-semibold">{children}</span>; |
| 106 | +} |
| 107 | + |
| 108 | +function ChannelCard({ channel, score, times, adCopy }) { |
| 109 | + return ( |
| 110 | + <div className="p-4 border rounded-lg shadow-sm hover:shadow-md transition"> |
| 111 | + <div className="flex items-center justify-between"> |
| 112 | + <div className="flex items-center space-x-3"> |
| 113 | + <div className="w-12 h-12 rounded-full bg-gradient-to-br from-indigo-500 to-pink-500 flex items-center justify-center text-white font-bold uppercase">{channel[0]}</div> |
| 114 | + <div> |
| 115 | + <div className="font-semibold capitalize">{channel}</div> |
| 116 | + <div className="text-sm text-gray-500">Score: {score}</div> |
| 117 | + </div> |
| 118 | + </div> |
| 119 | + <div> |
| 120 | + <Badge>{Math.round(score)} pts</Badge> |
| 121 | + </div> |
| 122 | + </div> |
| 123 | + |
| 124 | + <div className="mt-3 text-sm"> |
| 125 | + <div className="font-medium">Suggested Times</div> |
| 126 | + <ul className="list-disc list-inside text-gray-600"> |
| 127 | + {times.map((t, i) => <li key={i}>{t}</li>)} |
| 128 | + </ul> |
| 129 | + </div> |
| 130 | + |
| 131 | + <div className="mt-3"> |
| 132 | + <div className="font-medium">Ad Copy</div> |
| 133 | + <div className="mt-2 text-sm bg-gray-50 p-3 rounded"> |
| 134 | + <div><strong>Primary:</strong> {adCopy.primary}</div> |
| 135 | + {adCopy.variant && <div className="mt-2 text-gray-700"><strong>Variant:</strong> {adCopy.variant}</div>} |
| 136 | + </div> |
| 137 | + </div> |
| 138 | + </div> |
| 139 | + ); |
| 140 | +} |
| 141 | + |
| 142 | +// ---------- Main App ---------- |
| 143 | +export default function App() { |
| 144 | + const [form, setForm] = useState({ |
| 145 | + businessName: "", |
| 146 | + industry: "fashion", |
| 147 | + product: "", |
| 148 | + audienceAge: "25to44", |
| 149 | + audienceLocation: "global", |
| 150 | + averagePrice: 50, |
| 151 | + tone: "friendly", |
| 152 | + callToAction: "Shop now", |
| 153 | + }); |
| 154 | + const [recommendations, setRecommendations] = useState(null); |
| 155 | + const [postingTimes, setPostingTimes] = useState(null); |
| 156 | + const [adCopies, setAdCopies] = useState(null); |
| 157 | + const [lang, setLang] = useState("en"); |
| 158 | + const [loading, setLoading] = useState(false); |
| 159 | + |
| 160 | + async function handleAnalyze(e) { |
| 161 | + e.preventDefault(); |
| 162 | + setLoading(true); |
| 163 | + |
| 164 | + // 1) Channel recommendations (mock AI) |
| 165 | + const recs = analyzeBusinessProfile(form); |
| 166 | + |
| 167 | + // 2) Predict posting times |
| 168 | + const times = predictPostingTimes(recs); |
| 169 | + |
| 170 | + // 3) Generate ad copy for top 3 channels |
| 171 | + const copies = {}; |
| 172 | + times.forEach(t => { |
| 173 | + copies[t.channel] = generateAdCopy({ |
| 174 | + businessName: form.businessName || "Your Business", |
| 175 | + product: form.product || "your product", |
| 176 | + tone: form.tone, |
| 177 | + channel: t.channel, |
| 178 | + callToAction: form.callToAction |
| 179 | + }); |
| 180 | + }); |
| 181 | + |
| 182 | + // simulate network delay |
| 183 | + await new Promise(r => setTimeout(r, 500)); |
| 184 | + |
| 185 | + setRecommendations(recs.slice(0, 5)); |
| 186 | + setPostingTimes(times); |
| 187 | + setAdCopies(copies); |
| 188 | + setLoading(false); |
| 189 | + } |
| 190 | + |
| 191 | + function handleChange(e) { |
| 192 | + const { name, value } = e.target; |
| 193 | + setForm(prev => ({ ...prev, [name]: name === "averagePrice" ? Number(value) : value })); |
| 194 | + } |
| 195 | + |
| 196 | + function downloadReport() { |
| 197 | + // Create a minimal report and trigger download |
| 198 | + const report = { |
| 199 | + generatedAt: new Date().toISOString(), |
| 200 | + profile: form, |
| 201 | + recommendations, |
| 202 | + postingTimes, |
| 203 | + adCopies |
| 204 | + }; |
| 205 | + const blob = new Blob([JSON.stringify(report, null, 2)], { type: "application/json" }); |
| 206 | + const url = URL.createObjectURL(blob); |
| 207 | + const a = document.createElement("a"); |
| 208 | + a.href = url; a.download = `${form.businessName || 'sma-report'}.json`; |
| 209 | + a.click(); |
| 210 | + URL.revokeObjectURL(url); |
| 211 | + } |
| 212 | + |
| 213 | + return ( |
| 214 | + <div className="min-h-screen bg-gradient-to-br from-white via-indigo-50 to-pink-50 p-6"> |
| 215 | + <div className="max-w-6xl mx-auto"> |
| 216 | + <header className="flex items-center justify-between mb-6"> |
| 217 | + <div className="flex items-center space-x-4"> |
| 218 | + <div className="w-14 h-14 rounded-xl bg-gradient-to-br from-indigo-600 to-pink-500 flex items-center justify-center text-white font-extrabold text-lg">SMA</div> |
| 219 | + <div> |
| 220 | + <h1 className="text-2xl font-bold">Smart Marketing Assistant</h1> |
| 221 | + <p className="text-sm text-gray-600">AI-driven channel, timing, and ad copy suggestions for small businesses.</p> |
| 222 | + </div> |
| 223 | + </div> |
| 224 | + |
| 225 | + <div className="flex items-center space-x-3"> |
| 226 | + <select value={lang} onChange={e => setLang(e.target.value)} className="px-3 py-2 rounded border bg-white"> |
| 227 | + <option value="en">English</option> |
| 228 | + <option value="es">Español</option> |
| 229 | + <option value="fr">Français</option> |
| 230 | + <option value="pt">Português</option> |
| 231 | + </select> |
| 232 | + |
| 233 | + <button onClick={() => window.open('#', '_blank')} className="px-4 py-2 bg-white border rounded shadow-sm">Help</button> |
| 234 | + </div> |
| 235 | + </header> |
| 236 | + |
| 237 | + <main className="grid grid-cols-1 lg:grid-cols-3 gap-6"> |
| 238 | + <form onSubmit={handleAnalyze} className="col-span-1 lg:col-span-1 p-6 bg-white rounded-lg shadow"> |
| 239 | + <h2 className="font-semibold text-lg mb-4">Business Profile</h2> |
| 240 | + |
| 241 | + <label className="block text-sm font-medium text-gray-700">Business name</label> |
| 242 | + <input name="businessName" value={form.businessName} onChange={handleChange} className="mt-1 w-full px-3 py-2 border rounded" placeholder="e.g., Sunny Cafe" /> |
| 243 | + |
| 244 | + <label className="mt-3 block text-sm font-medium text-gray-700">Industry</label> |
| 245 | + <select name="industry" value={form.industry} onChange={handleChange} className="mt-1 w-full px-3 py-2 border rounded"> |
| 246 | + <option value="fashion">Fashion</option> |
| 247 | + <option value="beauty">Beauty</option> |
| 248 | + <option value="food">Food & Beverage</option> |
| 249 | + <option value="software">Software</option> |
| 250 | + <option value="consulting">Consulting</option> |
| 251 | + <option value="fitness">Fitness</option> |
| 252 | + <option value="entertainment">Entertainment</option> |
| 253 | + <option value="other">Other</option> |
| 254 | + </select> |
| 255 | + |
| 256 | + <label className="mt-3 block text-sm font-medium text-gray-700">Main product / service</label> |
| 257 | + <input name="product" value={form.product} onChange={handleChange} className="mt-1 w-full px-3 py-2 border rounded" placeholder="e.g., Handcrafted lattes" /> |
| 258 | + |
| 259 | + <div className="mt-3 grid grid-cols-2 gap-2"> |
| 260 | + <div> |
| 261 | + <label className="block text-sm font-medium text-gray-700">Audience age</label> |
| 262 | + <select name="audienceAge" value={form.audienceAge} onChange={handleChange} className="mt-1 w-full px-3 py-2 border rounded"> |
| 263 | + <option value="under25">Under 25</option> |
| 264 | + <option value="25to44">25–44</option> |
| 265 | + <option value="45plus">45+</option> |
| 266 | + </select> |
| 267 | + </div> |
| 268 | + |
| 269 | + <div> |
| 270 | + <label className="block text-sm font-medium text-gray-700">Audience reach</label> |
| 271 | + <select name="audienceLocation" value={form.audienceLocation} onChange={handleChange} className="mt-1 w-full px-3 py-2 border rounded"> |
| 272 | + <option value="local">Local</option> |
| 273 | + <option value="regional">Regional</option> |
| 274 | + <option value="global">Global</option> |
| 275 | + </select> |
| 276 | + </div> |
| 277 | + </div> |
| 278 | + |
| 279 | + <label className="mt-3 block text-sm font-medium text-gray-700">Average price ($)</label> |
| 280 | + <input name="averagePrice" type="number" value={form.averagePrice} onChange={handleChange} className="mt-1 w-full px-3 py-2 border rounded" /> |
| 281 | + |
| 282 | + <label className="mt-3 block text-sm font-medium text-gray-700">Tone</label> |
| 283 | + <select name="tone" value={form.tone} onChange={handleChange} className="mt-1 w-full px-3 py-2 border rounded"> |
| 284 | + <option value="friendly">Friendly</option> |
| 285 | + <option value="professional">Professional</option> |
| 286 | + <option value="fun">Fun</option> |
| 287 | + <option value="luxury">Luxury</option> |
| 288 | + </select> |
| 289 | + |
| 290 | + <label className="mt-3 block text-sm font-medium text-gray-700">Call to action</label> |
| 291 | + <input name="callToAction" value={form.callToAction} onChange={handleChange} className="mt-1 w-full px-3 py-2 border rounded" /> |
| 292 | + |
| 293 | + <div className="mt-5 flex space-x-2"> |
| 294 | + <button type="submit" className="px-4 py-2 bg-indigo-600 text-white rounded shadow">{loading ? 'Analyzing...' : 'Analyze & Generate'}</button> |
| 295 | + <button type="button" onClick={() => { setForm({ businessName: '', industry: 'fashion', product: '', audienceAge: '25to44', audienceLocation: 'global', averagePrice: 50, tone: 'friendly', callToAction: 'Shop now' }); setRecommendations(null); setPostingTimes(null); setAdCopies(null); }} className="px-4 py-2 border rounded">Reset</button> |
| 296 | + </div> |
| 297 | + |
| 298 | + <div className="mt-6 text-xs text-gray-500">Local mock AI runs in-browser. To connect a real AI service, replace mock functions with API calls in the source code.</div> |
| 299 | + </form> |
| 300 | + |
| 301 | + <section className="col-span-1 lg:col-span-2"> |
| 302 | + <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> |
| 303 | + <div className="p-4 bg-white rounded shadow"> |
| 304 | + <h3 className="font-semibold">Summary</h3> |
| 305 | + <p className="text-sm text-gray-600 mt-2">Overview of recommendations and quick links to export results or copy ad copy to clipboard.</p> |
| 306 | + |
| 307 | + <div className="mt-3 flex space-x-2"> |
| 308 | + <button onClick={downloadReport} disabled={!recommendations} className="px-3 py-2 bg-green-600 text-white rounded disabled:opacity-50">Download JSON Report</button> |
| 309 | + <button onClick={() => navigator.clipboard.writeText(JSON.stringify({ recommendations, postingTimes, adCopies }, null, 2))} disabled={!recommendations} className="px-3 py-2 bg-indigo-600 text-white rounded disabled:opacity-50">Copy Results</button> |
| 310 | + </div> |
| 311 | + </div> |
| 312 | + |
| 313 | + <div className="p-4 bg-white rounded shadow"> |
| 314 | + <h3 className="font-semibold">Impact Forecast (mock)</h3> |
| 315 | + <div className="mt-2 text-sm text-gray-600">Estimated improvements after 3 months of using SMA:</div> |
| 316 | + <ul className="mt-3 list-disc list-inside text-gray-700"> |
| 317 | + <li>Engagement: +30%</li> |
| 318 | + <li>Ad spend waste reduction: -25%</li> |
| 319 | + <li>Average ROI improvement: +20%</li> |
| 320 | + </ul> |
| 321 | + </div> |
| 322 | + </div> |
| 323 | + |
| 324 | + <div className="grid grid-cols-1 md:grid-cols-3 gap-4"> |
| 325 | + {recommendations ? recommendations.map((r, i) => ( |
| 326 | + <ChannelCard key={r.channel} channel={r.channel} score={r.score} times={(postingTimes.find(t => t.channel === r.channel) || { times: ['Anytime'] }).times} adCopy={(adCopies && adCopies[r.channel]) || generateAdCopy({ businessName: form.businessName, product: form.product, tone: form.tone, channel: r.channel, callToAction: form.callToAction })} /> |
| 327 | + )) : ( |
| 328 | + <div className="col-span-1 md:col-span-3 p-6 bg-white rounded shadow text-center text-gray-500">Run an analysis to see channel recommendations, optimal posting times, and tailored ad copy.</div> |
| 329 | + )} |
| 330 | + </div> |
| 331 | + |
| 332 | + <div className="mt-6 p-6 bg-white rounded shadow"> |
| 333 | + <h3 className="font-semibold">Implementation Roadmap</h3> |
| 334 | + <ol className="list-decimal list-inside mt-3 text-gray-700"> |
| 335 | + <li>Connect social accounts + website analytics (Month 1)</li> |
| 336 | + <li>Train SMA with business data & set goals (Month 2)</li> |
| 337 | + <li>Launch first 4-week campaign with AI recommendations (Month 3)</li> |
| 338 | + <li>Optimize & scale based on KPIs (Months 4–6)</li> |
| 339 | + </ol> |
| 340 | + |
| 341 | + <div className="mt-4 text-sm text-gray-600">Tip: Always A/B test AI-generated copy against human-written copy for best results.</div> |
| 342 | + </div> |
| 343 | + |
| 344 | + <div className="mt-6 p-6 bg-white rounded shadow"> |
| 345 | + <h3 className="font-semibold">About the Demo & Next Steps</h3> |
| 346 | + <p className="text-sm text-gray-600 mt-2">This demo runs mock AI logic in-browser for offline demonstration and judging. For production/global use, replace the mock generators with calls to a server-side AI (OpenAI, Anthropic, etc.), secure your API keys, and add internationalization of copy templates.</p> |
| 347 | + |
| 348 | + <div className="mt-3 text-sm"> |
| 349 | + <strong>Checklist to go worldwide:</strong> |
| 350 | + <ul className="list-disc list-inside text-gray-700"> |
| 351 | + <li>Server side AI & rate-limiting</li> |
| 352 | + <li>Secure storage (GDPR / data residency)</li> |
| 353 | + <li>Translation & localization</li> |
| 354 | + <li>Payment & subscription for a SaaS model</li> |
| 355 | + </ul> |
| 356 | + </div> |
| 357 | + </div> |
| 358 | + |
| 359 | + </section> |
| 360 | + </main> |
| 361 | + |
| 362 | + <footer className="mt-8 text-center text-gray-600 text-sm">Built for DECA — Smart Marketing Assistant • Demo UI (in-browser mock AI) • Customize for your competition</footer> |
| 363 | + </div> |
| 364 | + </div> |
| 365 | + ); |
| 366 | +} |
0 commit comments