feat: KoePi mehrere benannte oeffentliche Links statt nur einem, Migration des bestehenden Sina-Links, Name in Logs
This commit is contained in:
@@ -3312,6 +3312,7 @@ function PushLogs({ toast }) {
|
||||
<div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', gap:8, marginBottom:3, flexWrap:'wrap' }}>
|
||||
<span style={{ color:'#4ecdc4', fontFamily:"'Space Mono',monospace", fontSize:11, fontWeight:700 }}>
|
||||
{PUBLIC_LINK_LABELS[l.linkType] || '🔗 Öffentlicher Link besucht'}
|
||||
{l.linkName && <span style={{ color:'#ffe66d' }}> — {l.linkName}</span>}
|
||||
</span>
|
||||
<span style={{ display:'flex', alignItems:'center', gap:8 }}>
|
||||
<span style={{ color:'rgba(255,255,255,0.35)', fontFamily:'monospace', fontSize:10 }}>
|
||||
|
||||
@@ -262,35 +262,58 @@ export default function Koepi({ toast }) {
|
||||
finally { setResolvingStores(false); }
|
||||
};
|
||||
|
||||
const [shareInfo, setShareInfo] = useState(null); // { token, url } | null
|
||||
const [shareLinks, setShareLinks] = useState([]);
|
||||
const [shareOpen, setShareOpen] = useState(false);
|
||||
const [shareBusy, setShareBusy] = useState(false);
|
||||
useEffect(() => {
|
||||
if (!isAdmin) return;
|
||||
api('/tools/koepi/share-link').then(setShareInfo).catch(()=>{});
|
||||
}, [isAdmin]);
|
||||
const [newLinkName, setNewLinkName] = useState('');
|
||||
|
||||
const loadShareLinks = () => {
|
||||
api('/tools/koepi/share-links').then(r => setShareLinks(r.links || [])).catch(()=>{});
|
||||
};
|
||||
useEffect(() => { if (isAdmin) loadShareLinks(); }, [isAdmin]);
|
||||
|
||||
const createShareLink = async () => {
|
||||
const name = newLinkName.trim();
|
||||
if (!name) { toast?.('Bitte einen Namen eingeben','error'); return; }
|
||||
setShareBusy(true);
|
||||
try {
|
||||
const r = await api('/tools/koepi/share-link', { method:'POST', body:{} });
|
||||
setShareInfo(r);
|
||||
toast('🔗 Neuer Link erzeugt — alter Link ist damit ungültig');
|
||||
await api('/tools/koepi/share-links', { method:'POST', body:{ name } });
|
||||
setNewLinkName('');
|
||||
toast(`🔗 Link für "${name}" erstellt`);
|
||||
loadShareLinks();
|
||||
} catch(e) { toast?.(e.message||'Fehler','error'); }
|
||||
finally { setShareBusy(false); }
|
||||
};
|
||||
const disableShareLink = async () => {
|
||||
const resetShareLink = async (id, name) => {
|
||||
if (!window.confirm(`Link für "${name}" wirklich zurücksetzen? Der alte Link funktioniert danach nicht mehr.`)) return;
|
||||
setShareBusy(true);
|
||||
try {
|
||||
await api('/tools/koepi/share-link/disable', { method:'POST', body:{} });
|
||||
setShareInfo({ token:null, url:null });
|
||||
toast('🔗 Link deaktiviert');
|
||||
await api(`/tools/koepi/share-links/${id}/reset`, { method:'POST', body:{} });
|
||||
toast(`🔄 Link für "${name}" zurückgesetzt`);
|
||||
loadShareLinks();
|
||||
} catch(e) { toast?.(e.message||'Fehler','error'); }
|
||||
finally { setShareBusy(false); }
|
||||
};
|
||||
const copyShareLink = async () => {
|
||||
if (!shareInfo?.url) return;
|
||||
try { await navigator.clipboard.writeText(shareInfo.url); toast('Link kopiert'); }
|
||||
catch { toast?.('Kopieren nicht möglich — bitte manuell markieren','error'); }
|
||||
const deleteShareLink = async (id, name) => {
|
||||
if (!window.confirm(`Link für "${name}" wirklich löschen?`)) return;
|
||||
setShareBusy(true);
|
||||
try {
|
||||
await api(`/tools/koepi/share-links/${id}`, { method:'DELETE' });
|
||||
toast(`🗑 Link für "${name}" gelöscht`);
|
||||
loadShareLinks();
|
||||
} catch(e) { toast?.(e.message||'Fehler','error'); }
|
||||
finally { setShareBusy(false); }
|
||||
};
|
||||
const copyLink = async (url) => {
|
||||
try { await navigator.clipboard.writeText(url); toast('Link kopiert'); return; } catch {}
|
||||
try {
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = url; ta.style.position = 'fixed'; ta.style.left = '-9999px';
|
||||
document.body.appendChild(ta); ta.focus(); ta.select();
|
||||
const ok = document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
toast?.(ok ? 'Link kopiert' : 'Kopieren nicht möglich', ok ? 'success' : 'error');
|
||||
} catch { toast?.('Kopieren nicht möglich','error'); }
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -310,37 +333,52 @@ export default function Koepi({ toast }) {
|
||||
{resolvingStores ? '⏳ läuft…' : '🏪 Filial-Liste neu auflösen'}
|
||||
</button>
|
||||
<button onClick={clearCache} style={{ ...S.btn('#666666',true), fontSize:10, flexShrink:0, whiteSpace:'nowrap' }}>🗑 Cache leeren</button>
|
||||
<button onClick={()=>setShareOpen(v=>!v)} style={{ ...S.btn(shareInfo?.token?'#ffe66d':'#888888',true), fontSize:10, flexShrink:0, whiteSpace:'nowrap' }}>
|
||||
🔗 Teilen{shareInfo?.token ? ' (aktiv)' : ''}
|
||||
<button onClick={()=>setShareOpen(v=>!v)} style={{ ...S.btn(shareLinks.length?'#ffe66d':'#888888',true), fontSize:10, flexShrink:0, whiteSpace:'nowrap' }}>
|
||||
🔗 Teilen{shareLinks.length ? ` (${shareLinks.length})` : ''}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{isAdmin && shareOpen && (
|
||||
<div style={{ ...S.card, marginTop:10 }}>
|
||||
<div style={{ ...S.sub, marginBottom:10 }}>
|
||||
Öffentlicher, schreibgeschützter Link ohne Login — zeigt zuerst die Prospekte, man kann dort
|
||||
auch zu den Angeboten wechseln. Keine Admin-Funktionen sichtbar/erreichbar über diesen Link.
|
||||
Öffentliche, schreibgeschützte Links ohne Login — zeigen zuerst die Prospekte, man kann dort
|
||||
auch zu den Angeboten wechseln. Keine Admin-Funktionen sichtbar/erreichbar über diese Links.
|
||||
Du kannst mehrere Links gleichzeitig anlegen (z.B. einen pro Person) — in den Logs siehst du
|
||||
dann, wer welchen Link benutzt hat.
|
||||
</div>
|
||||
{shareInfo?.token ? (
|
||||
<>
|
||||
<div style={{ display:'flex', gap:8, marginBottom:10, flexWrap:'wrap' }}>
|
||||
<input readOnly value={shareInfo.url} onClick={e=>e.target.select()} style={{ ...S.inp, flex:1, minWidth:200, fontSize:11 }}/>
|
||||
<button onClick={copyShareLink} style={{ ...S.btn('#4ecdc4') }}>📋 Kopieren</button>
|
||||
</div>
|
||||
<div style={{ display:'flex', gap:8, flexWrap:'wrap' }}>
|
||||
<button onClick={createShareLink} disabled={shareBusy} style={{ ...S.btn('#f59e0b'), opacity:shareBusy?0.5:1 }}>
|
||||
{shareBusy?'…':'🔄 Neuen Link erzeugen (alter wird ungültig)'}
|
||||
</button>
|
||||
<button onClick={disableShareLink} disabled={shareBusy} style={{ ...S.btn('#f87171'), opacity:shareBusy?0.5:1 }}>
|
||||
{shareBusy?'…':'🚫 Link deaktivieren'}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<button onClick={createShareLink} disabled={shareBusy} style={{ ...S.btn('#4ecdc4'), opacity:shareBusy?0.5:1 }}>
|
||||
{shareBusy?'…':'🔗 Link erzeugen'}
|
||||
</button>
|
||||
|
||||
{shareLinks.length > 0 && (
|
||||
<div style={{ display:'flex', flexDirection:'column', gap:8, marginBottom:14 }}>
|
||||
{shareLinks.map(l => (
|
||||
<div key={l.id} style={{ border:'1px solid rgba(255,255,255,0.08)', borderRadius:8, padding:'8px 10px' }}>
|
||||
<div style={{ color:'#ffe66d', fontFamily:"'Space Mono',monospace", fontSize:12, fontWeight:700, marginBottom:4 }}>
|
||||
{l.name}
|
||||
</div>
|
||||
<div style={{ display:'flex', gap:8, marginBottom:6, flexWrap:'wrap' }}>
|
||||
<input readOnly value={l.url} onClick={e=>e.target.select()} style={{ ...S.inp, flex:1, minWidth:180, fontSize:10 }}/>
|
||||
<button onClick={()=>copyLink(l.url)} style={{ ...S.btn('#4ecdc4'), fontSize:11 }}>📋</button>
|
||||
</div>
|
||||
<div style={{ display:'flex', gap:8, flexWrap:'wrap' }}>
|
||||
<button onClick={()=>resetShareLink(l.id, l.name)} disabled={shareBusy} style={{ ...S.btn('#f59e0b'), fontSize:10, opacity:shareBusy?0.5:1 }}>
|
||||
🔄 Zurücksetzen
|
||||
</button>
|
||||
<button onClick={()=>deleteShareLink(l.id, l.name)} disabled={shareBusy} style={{ ...S.btn('#f87171'), fontSize:10, opacity:shareBusy?0.5:1 }}>
|
||||
🗑 Löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ display:'flex', gap:8, flexWrap:'wrap' }}>
|
||||
<input value={newLinkName} onChange={e=>setNewLinkName(e.target.value)}
|
||||
onKeyDown={e=>e.key==='Enter' && createShareLink()}
|
||||
placeholder="Name (z.B. Sina)" style={{ ...S.inp, flex:1, minWidth:140 }}/>
|
||||
<button onClick={createShareLink} disabled={shareBusy || !newLinkName.trim()} style={{ ...S.btn('#4ecdc4'), opacity:(shareBusy || !newLinkName.trim())?0.5:1 }}>
|
||||
{shareBusy?'…':'➕ Neuen Link erstellen'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user