// screen-extra.jsx — Search results, multi-step Add Idea page, Notifications panel
const { useState: useExState, useMemo: useExMemo, useRef: useExRef, useEffect: useExEffect } = React;
/* ----------------------------------------------------------------
Search results
----------------------------------------------------------------- */
function SearchResults({ query, setSearch, openIdea, openUser, votes, onVote }) {
const Icon = window.Icon;
const [tab, setTab] = useExState('ideas');
const q = query.trim().toLowerCase();
const ideaHits = useExMemo(() => !q ? [] : window.IDEAS.filter(i =>
i.title.toLowerCase().includes(q) || i.category.toLowerCase().includes(q) || i.desc.toLowerCase().includes(q)), [q]);
const userHits = useExMemo(() => {
if (!q) return [];
const seen = new Set();
return window.IDEAS.filter(i => { const m = i.author.toLowerCase().includes(q); if (m && !seen.has(i.author)) { seen.add(i.author); return true; } return false; })
.map(i => ({ name: i.author, role: i.role, av: i.avatar }));
}, [q]);
const list = tab === 'ideas' ? ideaHits : userHits;
return (
Search
{q ? <>Results for “{query}” · {ideaHits.length + userHits.length} matches> : 'Type to search ideas and people across IdeaMint.'}
{list.length === 0 ? (
{q ? 'No matches found' : 'Start typing to search'}
{q ? 'Try a different keyword or check the People tab.' : 'Search by idea title, category, or owner name.'}
{q &&
{['Technology', 'Carbon', 'AI', 'Mohamed'].map(s => )}
}
) : tab === 'ideas' ? (
{ideaHits.map(i => (
openIdea(i)}>
{i.title}
{i.desc}
{i.author}
onVote(i.id, d)} />
))}
) : (
{userHits.map((u, idx) => (
openUser(u)}>
{u.name}
{u.role}
))}
)}
);
}
/* ----------------------------------------------------------------
Multi-step Add Idea (full page)
----------------------------------------------------------------- */
// Suggested tags per category
const SUGGESTED_TAGS = {
Technology: ['AI', 'automation', 'productivity', 'integration', 'data', 'cloud', 'mobile', 'analytics', 'security', 'API'],
Business: ['revenue', 'cost-saving', 'strategy', 'growth', 'efficiency', 'process', 'KPI', 'ROI', 'partnership', 'market'],
Marketing: ['branding', 'campaign', 'social-media', 'engagement', 'SEO', 'content', 'conversion', 'awareness', 'leads', 'CRM'],
Design: ['UX', 'UI', 'accessibility', 'design-system', 'prototype', 'usability', 'visual', 'workflow', 'user-research', 'mobile'],
Operations: ['logistics', 'supply-chain', 'compliance', 'workflow', 'quality', 'reporting', 'onboarding', 'SLA', 'cost', 'training'],
};
const DEFAULT_TAGS = ['innovation', 'team', 'pilot', 'research', 'improvement', 'customer', 'internal', 'cross-team', 'sustainability', 'agile'];
function AddIdeaPage({ goBack, onSubmit }) {
const Icon = window.Icon;
const [step, setStep] = useExState(0);
const [form, setForm] = useExState({ title: '', cat: '', desc: '', tags: [], tagInput: '' });
const [catOpen, setCatOpen] = useExState(false);
const [tagOpen, setTagOpen] = useExState(false);
const [aiState, setAiState] = useExState('idle'); // idle | thinking | done | error
const [aiDraft, setAiDraft] = useExState(''); // streamed preview text
const tagBoxRef = useExRef(null);
const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
const addTag = (e) => { if (e.key === 'Enter' && form.tagInput.trim() && form.tags.length < 5) { e.preventDefault(); set('tags', [...form.tags, form.tagInput.trim()]); set('tagInput', ''); } };
// Close tag dropdown on outside click
useExEffect(() => {
const handler = (e) => { if (tagBoxRef.current && !tagBoxRef.current.contains(e.target)) setTagOpen(false); };
document.addEventListener('mousedown', handler);
return () => document.removeEventListener('mousedown', handler);
}, []);
const suggestedTags = (SUGGESTED_TAGS[form.cat] || DEFAULT_TAGS).filter(t => !form.tags.includes(t));
const generateWithAI = async () => {
if (!form.title.trim()) return;
setAiState('thinking');
setAiDraft('');
try {
const result = await window.API.generateIdea(form.title, form.cat, form.desc);
// Animate the text typing in
const text = result.description || '';
setAiDraft('');
setAiState('done');
// Typewriter effect
let i = 0;
const tick = () => {
i += 3;
setAiDraft(text.slice(0, i));
if (i < text.length) requestAnimationFrame(tick);
else set('desc', text);
};
requestAnimationFrame(tick);
// Pre-fill suggested tags from AI if user hasn't added any
if (result.tags?.length && form.tags.length === 0) {
set('tags', result.tags.slice(0, 5));
}
} catch {
setAiState('error');
setTimeout(() => setAiState('idle'), 3000);
}
};
const steps = ['Details', 'Media', 'Review'];
const step0Valid = form.title.trim() && form.cat && form.desc.trim() && form.tags.length > 0;
const next = () => setStep(s => Math.min(2, s + 1));
const back = () => setStep(s => Math.max(0, s - 1));
return (
Add new idea
Share something worth building — it takes about a minute.
{/* stepper */}
{steps.map((s, i) => (
{i < steps.length - 1 && }
))}
{step === 0 && (
set('title', e.target.value)} placeholder="Enter your idea's general title" />
{catOpen && (
{window.CATEGORIES.map(c => (
))}
)}
form.tags.length < 5 && setTagOpen(true)}
style={{ display: 'flex', flexWrap: 'wrap', gap: 7, height: 'auto', minHeight: 48, alignItems: 'center', padding: '8px 10px', cursor: 'text' }}>
{form.tags.map((t, i) => (
{t}
))}
{form.tags.length < 5 && (
set('tagInput', e.target.value)}
onKeyDown={addTag}
onFocus={() => setTagOpen(true)}
placeholder={form.tags.length === 0 ? 'Click or type to add tags…' : ''}
style={{ border: 'none', outline: 'none', flex: 1, minWidth: 120, fontSize: 14, background: 'none' }} />
)}
{/* Tag suggestions dropdown */}
{tagOpen && suggestedTags.length > 0 && (
{form.cat ? `Suggested for ${form.cat}` : 'Suggested tags'} — click to add
{suggestedTags.slice(0, 8).map(tag => (
))}
)}
)}
{step === 1 && (
set('coverImage', url)} />
set('extraImage', url)} />
set('video', url)} />
Ideas with a cover image get 2× more views on average. Media is optional — you can publish without it.
)}
{step === 2 && (
Preview
{form.title || 'Your idea title'}
{form.desc || 'Your idea description will appear here.'}
{(form.tags.length ? form.tags : ['idea']).map(t => {t})}
You'll earn +150 USD when this idea is published.
)}
{step < 2
?
: }
);
}
function PField({ label, required, children }) {
return ({children}
);
}
function PDrop({ hint, icon, small, accept, onUploaded }) {
const Icon = window.Icon;
const inputRef = useExRef(null);
const [state, setState] = useExState('idle'); // idle | uploading | done | error
const [preview, setPreview] = useExState(null);
const [dragging, setDragging] = useExState(false);
const handleFile = async (file) => {
if (!file) return;
setState('uploading');
// Show local preview immediately
const isVideo = file.type.startsWith('video/');
if (!isVideo) setPreview(URL.createObjectURL(file));
try {
const result = await window.API.uploadFile(file);
if (result.url) {
if (isVideo) setPreview(result.url);
setState('done');
onUploaded && onUploaded(result.url);
} else {
setState('error');
}
} catch {
setState('error');
}
};
const onDrop = (e) => {
e.preventDefault();
setDragging(false);
const file = e.dataTransfer.files[0];
if (file) handleFile(file);
};
const isVideo = accept === 'video';
const acceptStr = isVideo ? 'video/mp4,video/mov,video/avi' : 'image/jpeg,image/png,image/gif,image/webp';
return (
state !== 'uploading' && inputRef.current?.click()}
onDragOver={e => { e.preventDefault(); setDragging(true); }}
onDragLeave={() => setDragging(false)}
onDrop={onDrop}
style={{
border: `1.6px dashed ${dragging ? 'var(--accent-600)' : state === 'done' ? 'var(--success)' : state === 'error' ? 'var(--danger)' : 'var(--line-strong)'}`,
borderRadius: 'var(--r)', padding: small ? '22px 14px' : '34px 14px',
textAlign: 'center', cursor: state === 'uploading' ? 'wait' : 'pointer',
transition: 'all .15s', position: 'relative', overflow: 'hidden',
background: dragging ? 'var(--accent-50)' : state === 'done' ? 'var(--success-bg)' : 'transparent',
}}>
handleFile(e.target.files[0])} />
{/* Image preview */}
{preview && !isVideo && (
)}
{/* Overlay content */}
{state === 'uploading' ? (
<>
Uploading…
>
) : state === 'done' ? (
<>
Uploaded — click to replace
>
) : state === 'error' ? (
<>
Upload failed — click to retry
>
) : (
<>
{hint}
{!small &&
PNG, JPG, GIF up to 10 MB
}
>
)}
);
}
/* ----------------------------------------------------------------
Notifications panel (dropdown)
----------------------------------------------------------------- */
function NotificationsPanel({ open, onClose, items, onRead, onReadAll }) {
const Icon = window.Icon;
if (!open) return null;
return (
<>
Notifications
{items.map(n => (
))}
>
);
}
Object.assign(window, { SearchResults, AddIdeaPage, NotificationsPanel });