Files
2026-08-05 01:29:39 +02:00

121 lines
6.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const resource = typeof GetParentResourceName === 'function' ? GetParentResourceName() : 'devx_phone';
const phone = document.querySelector('#phone');
const screens = [...document.querySelectorAll('.screen')];
const format = new Intl.NumberFormat('de-DE');
let state = { player: {}, phone: {}, banking: {} };
async function post(event, data = {}) {
try {
const response = await fetch(`https://${resource}/${event}`, {
method: 'POST', headers: { 'Content-Type': 'application/json; charset=UTF-8' }, body: JSON.stringify(data)
});
const text = await response.text();
return text ? JSON.parse(text) : { ok: true };
} catch (error) {
return { ok: false, error: 'Die App hat keine Antwort erhalten.' };
}
}
function escapeHtml(value) {
return String(value ?? '').replace(/[&<>"']/g, char => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;'
}[char]));
}
function show(id) { screens.forEach(screen => screen.classList.toggle('hidden', screen.id !== id)); }
function setError(id, text = '') {
const el = document.querySelector(id); el.textContent = text; el.classList.toggle('hidden', !text);
}
function close() { post('close'); }
function refresh() { post('refresh'); }
function renderTransactions(entries = []) {
const list = document.querySelector('#transactions');
list.innerHTML = entries.length ? '' : '<div class="empty-state compact-empty"><p>Noch keine Buchungen.</p></div>';
entries.slice(0, 12).forEach(entry => {
const row = document.createElement('div');
row.className = 'transaction';
const credit = entry.direction === 'credit';
row.innerHTML = `<div><strong>${escapeHtml(entry.reference)}</strong><small>${escapeHtml(entry.counterparty || '')}</small></div><b class="${credit ? 'credit' : 'debit'}">${credit ? '+' : ''}${format.format(Number(entry.amount) || 0)} $</b>`;
list.appendChild(row);
});
}
function renderContacts(entries = []) {
const list = document.querySelector('#contactList');
list.innerHTML = entries.length ? '' : '<div class="empty-state compact-empty"><p>Noch keine Kontakte.</p></div>';
entries.forEach(entry => {
const row = document.createElement('div'); row.className = 'contact';
const system = entry.contactType && entry.contactType !== 'player';
row.innerHTML = `<div><strong>${escapeHtml(entry.contactName)}</strong><small>${escapeHtml(entry.phoneNumber)}</small></div><div class="contact-actions"></div>`;
const actions = row.querySelector('.contact-actions');
if (entry.contactType === 'npc_companion') {
const invite = document.createElement('button'); invite.className = 'invite-contact'; invite.textContent = 'Einladen';
invite.onclick = async () => {
invite.disabled = true;
const result = await post('companionInvite', { contactId: Number(entry.id), phoneNumber: entry.phoneNumber });
setError('#contactError', result.ok ? (result.message || 'Einladung gesendet.') : (result.error || 'Einladung fehlgeschlagen.'));
setTimeout(() => setError('#contactError'), 3500); invite.disabled = false;
};
actions.appendChild(invite);
}
if (!system) {
const remove = document.createElement('button'); remove.className = 'delete-contact'; remove.textContent = '×'; remove.ariaLabel = 'Löschen';
remove.onclick = async () => {
const result = await post('contactDelete', { contactId: Number(entry.id) });
if (!result.ok) setError('#contactError', result.error || 'Löschen fehlgeschlagen.');
};
actions.appendChild(remove);
}
list.appendChild(row);
});
}
function render() {
const player = state.player || {}; const phoneState = state.phone || {}; const account = state.banking?.account;
document.querySelector('#firstName').textContent = player.firstName || 'Spieler';
document.querySelector('#ownNumber').textContent = phoneState.phoneNumber || player.phoneNumber || '';
document.querySelector('#profileName').textContent = [player.firstName, player.lastName].filter(Boolean).join(' ') || 'Spieler';
document.querySelector('#profileNumber').textContent = phoneState.phoneNumber || player.phoneNumber || '';
document.querySelector('#rpXp').textContent = `${format.format(Number(phoneState.rpXp || player.rpXp) || 0)} XP`;
document.querySelector('#characterId').textContent = player.characterId || '';
document.querySelector('#balance').textContent = `${format.format(Number(account?.balance) || 0)} $`;
document.querySelector('#accountNumber').textContent = account?.accountNumber || 'Kein Konto';
renderTransactions(state.banking?.transactions || []);
renderContacts(phoneState.contacts || []);
}
for (const button of document.querySelectorAll('[data-app]')) button.onclick = () => show(button.dataset.app);
for (const button of document.querySelectorAll('.back')) button.onclick = () => show('home');
for (const button of document.querySelectorAll('.refresh')) button.onclick = refresh;
document.querySelector('#homeButton').onclick = close;
document.addEventListener('keyup', event => { if (event.key === 'Escape') close(); });
setInterval(() => { document.querySelector('#time').textContent = new Date().toLocaleTimeString('de-DE', {hour:'2-digit', minute:'2-digit'}); }, 1000);
const transferForm = document.querySelector('#transferForm');
transferForm.addEventListener('submit', async event => {
event.preventDefault(); setError('#bankError');
const button = event.currentTarget.querySelector('button'); button.disabled = true;
const result = await post('bankTransfer', Object.fromEntries(new FormData(event.currentTarget)));
if (!result.ok) setError('#bankError', result.error || 'Überweisung fehlgeschlagen.');
else { event.currentTarget.reset(); await post('refresh'); }
button.disabled = false;
});
document.querySelector('#contactForm').addEventListener('submit', async event => {
event.preventDefault(); setError('#contactError');
const result = await post('contactCreate', Object.fromEntries(new FormData(event.currentTarget)));
if (!result.ok) setError('#contactError', result.error || 'Kontakt konnte nicht gespeichert werden.');
else { event.currentTarget.reset(); await post('refresh'); }
});
window.addEventListener('message', event => {
const data = event.data || {};
if (data.action === 'open') { phone.classList.remove('hidden'); show('home'); }
if (data.action === 'close') phone.classList.add('hidden');
if (data.action === 'state') {
state = { player: data.player || {}, phone: data.phone || {}, banking: data.banking || {} };
render();
if (data.error) setError('#bankError', data.error);
}
});