const PublishOfferButton = require('../components/PublishOfferButton'); const BuyOrSellButtonGroup = require('../components/BuyOrSellButtonGroup'); const PremiumSelector = require('../components/PremiumSelector'); const PriceDisplay = require('../components/PriceDisplay'); const AmountInput = require('../components/AmountInput'); const PlaceInput = require('../components/PlaceInput'); const TimeInput = require('../components/TimeInput'); const BitcoinMethodCheckboxes = require('../components/BitcoinMethodCheckboxes'); const TrustCheckboxes = require('../components/TrustCheckboxes'); const BigNotesCheckbox = require('../components/BigNotesCheckbox'); const PopupNotification = require('../components/PopupNotification'); class CreateOfferModal { // Actual creation logic to be provided by a service // Stop relying on IDs constructor({ parentElement, onCreationCallback }) { this.element = null; this.parentElement = parentElement; this.onCreationCallback = onCreationCallback; this.publishOfferButton = null; this.buyOrSellButtonGroup = null; this.premiumSelector = null; this.amountInput = null; this.placeInput = null; this.timeInput = null; this.btcMethodCheckboxes = null; this.trustCheckboxes = null; this.bigNotesCheckbox = null; } render() { const modalRoot = document.createElement('div'); this.element = modalRoot; modalRoot.id = 'create-offer-modal-root'; modalRoot.className = 'full-screen-modal-background'; const modal = document.createElement('div'); modal.className = 'full-screen-modal'; modal.id = 'create-offer-root'; const controls = document.createElement('div'); controls.id = 'create-offer-controls'; const title = document.createElement('h2'); title.textContent = 'Añade los detalles de tu oferta'; const sections = [ { id: 'buy-or-sell-area', class: 'create-offer-step', title: '' }, { id: 'premium-area', class: 'create-offer-step', title: 'Premium', contentId: 'premium-content-area', }, { id: 'amount-area', class: 'create-offer-step', title: '¿Cuánto?' }, { id: 'place-and-time-area', class: 'create-offer-step', title: '¿Dónde y cuándo?', contentId: 'place-and-time-boxes', }, { id: 'bitcoin-methods-area', class: 'create-offer-step', title: '¿Cómo se mueve el Bitcoin?', contentId: 'bitcoin-methods-checkboxes', }, { id: 'trust-area', class: 'create-offer-step', title: '¿Quién puede ver la oferta?', contentId: 'trusted-checkboxes-area', }, { id: 'other-area', class: 'create-offer-step', title: 'Extras' }, ]; controls.appendChild(title); sections.forEach((section) => { const div = document.createElement('div'); div.id = section.id; div.className = section.class; if (section.title) { const heading = document.createElement('h3'); heading.textContent = section.title; div.appendChild(heading); } if (section.contentId) { const contentDiv = document.createElement('div'); contentDiv.id = section.contentId; div.appendChild(contentDiv); } controls.appendChild(div); }); const submitButtonArea = document.createElement('div'); submitButtonArea.id = 'submit-button-area'; const closeButtonArea = document.createElement('div'); closeButtonArea.id = 'close-offer-controls-area'; const closeButton = document.createElement('button'); closeButton.id = 'close-offer'; closeButton.className = 'button-secondary button-medium'; closeButton.textContent = 'Volver'; closeButtonArea.appendChild(closeButton); controls.appendChild(submitButtonArea); controls.appendChild(closeButtonArea); modal.appendChild(controls); modalRoot.appendChild(modal); this.parentElement.appendChild(this.element); const createOfferEventBus = new EventTarget(); const mockPriceProvidingCallback = () => { return Math.floor(Math.random() * (95000 - 70000 + 1) + 70000); }; this.publishOfferButton = new PublishOfferButton({ parentElement: document.getElementById('submit-button-area'), id: 'button-submit-offer', onClickCallback: async () => { await this.createOffer(); await this.onCreationCallback(); await this.toggleOfferCreatedAlert(); }, }); this.publishOfferButton.render(); this.buyOrSellButtonGroup = new BuyOrSellButtonGroup({ parentElement: document.getElementById('buy-or-sell-area'), id: 'button-group-buy-or-sell', }); this.buyOrSellButtonGroup.render(); this.premiumSelector = new PremiumSelector({ parentElement: document.getElementById('premium-content-area'), id: 'premium-selector-area', eventSink: createOfferEventBus, }); this.premiumSelector.render(); const priceDisplay = new PriceDisplay({ parentElement: document.getElementById('premium-content-area'), id: 'premium-price-display-area', premiumProvidingCallback: () => { return this.premiumSelector.getPremium(); }, priceProvidingCallback: mockPriceProvidingCallback, }); priceDisplay.render(); createOfferEventBus.addEventListener('premium-changed', () => { priceDisplay.updatePrices(); }); this.amountInput = new AmountInput({ parentElement: document.getElementById('amount-area'), id: 'amount-area-content', }); this.amountInput.render(); this.placeInput = new PlaceInput({ parentElement: document.getElementById('place-and-time-boxes'), id: 'place-input', }); this.placeInput.render(); this.timeInput = new TimeInput({ parentElement: document.getElementById('place-and-time-boxes'), id: 'time-input', }); this.timeInput.render(); this.btcMethodCheckboxes = new BitcoinMethodCheckboxes({ parentElement: document.getElementById('bitcoin-methods-checkboxes'), }); this.btcMethodCheckboxes.render(); this.trustCheckboxes = new TrustCheckboxes({ parentElement: document.getElementById('trusted-checkboxes-area'), }); this.trustCheckboxes.render(); this.bigNotesCheckbox = new BigNotesCheckbox({ parentElement: document.getElementById('other-area'), }); this.bigNotesCheckbox.render(); } toggle() { this.element.classList.toggle('shown'); } async createOffer() { const wants = this.buyOrSellButtonGroup.wants(); const premium = this.premiumSelector.getPremium(); const trade_amount_eur = this.amountInput.intEurAmount; const location_details = this.placeInput.inputText; const time_availability_details = this.timeInput.inputText; const is_onchain_accepted = this.btcMethodCheckboxes.isOnchainAccepted; const is_lightning_accepted = this.btcMethodCheckboxes.isLightningAccepted; const show_offer_to_trusted = this.trustCheckboxes.showOfferToTrusted; const show_offer_to_trusted_trusted = this.trustCheckboxes.showOfferToTrustedTrusted; const show_offer_to_all_members = this.trustCheckboxes.showOfferToAllMembers; const are_big_notes_accepted = this.bigNotesCheckbox.areBigNotesAccepted; const offerDetails = { wants, premium, trade_amount_eur, location_details, time_availability_details, is_onchain_accepted, is_lightning_accepted, show_offer_to_trusted, show_offer_to_trusted_trusted, show_offer_to_all_members, are_big_notes_accepted, }; await fetch('/api/offer', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ offerDetails }), }); this.toggle(); } } function offersPage() { const offerCreatedPopup = new PopupNotification({ parentElement: document.body, text: '¡Oferta creada! Puedes verla en tus ofertas.', }); offerCreatedPopup.render(); const createOfferModal = new CreateOfferModal({ parentElement: document.body, onCreationCallback: async () => { await myOffers.getOffersFromApi(); await myOffers.render(); offerCreatedPopup.displayTemporarily(3000); }, }); createOfferModal.render(); // ----------- const navbuttonHome = document.getElementById('navbutton-home'); const navbuttonOffers = document.getElementById('navbutton-offers'); navbuttonHome.addEventListener('click', () => { window.location.href = '/home'; }); navbuttonOffers.addEventListener('click', () => { window.location.href = '/offers'; }); const buttonStartCreateOffer = document.getElementById( 'button-start-create-offer' ); const buttonViewMyOffers = document.getElementById('button-view-my-offers'); const closeOffer = document.getElementById('close-offer'); const createOfferModalRoot = document.getElementById( 'create-offer-modal-root' ); const viewMyOffersRoot = document.getElementById('view-my-offers-root'); const offerDeletedPopup = document.getElementById( 'offer-deleted-confirmation' ); const ownOffersContainer = document.getElementById('own-offers-container'); function toggleCreateOfferModal() { createOfferModalRoot.classList.toggle('shown'); } function toggleViewMyOffersPanel() { viewMyOffersRoot.style.display = viewMyOffersRoot.style.display === 'block' ? 'none' : 'block'; } function toggleOfferDeletedAlert() { offerDeletedPopup.classList.remove('max-size-zero'); offerDeletedPopup.classList.add('revealed'); setTimeout(() => { offerDeletedPopup.classList.remove('revealed'); }, 3000); setTimeout(() => { offerDeletedPopup.classList.add('max-size-zero'); }, 4000); } class Offer { constructor(offerData) { this.uuid = offerData.uuid; this.public_key = offerData.public_key; this.wants = offerData.wants; this.premium = offerData.premium; this.trade_amount_eur = offerData.trade_amount_eur; this.location_details = offerData.location_details; this.time_availability_details = offerData.time_availability_details; this.show_offer_to_trusted = offerData.show_offer_to_trusted; this.show_offer_to_trusted_trusted = offerData.show_offer_to_trusted_trusted; this.show_offer_to_all_members = offerData.show_offer_to_all_members; this.is_onchain_accepted = offerData.is_onchain_accepted; this.is_lightning_accepted = offerData.is_lightning_accepted; this.are_big_notes_accepted = offerData.are_big_notes_accepted; this.created_at = offerData.created_at; this.last_updated_at = offerData.last_updated_at; } buildHTML() { const offerCard = document.createElement('div'); offerCard.classList.add('myoffer-card'); offerCard.classList.add('shadowed-round-area'); const tradeDescDiv = document.createElement('div'); tradeDescDiv.classList.add('trade-desc'); const youBuyText = document.createElement('p'); youBuyText.classList.add('offer-card-content-title'); youBuyText.innerText = 'Compras'; tradeDescDiv.append(youBuyText); const youBuyData = document.createElement('p'); youBuyData.classList.add('offer-card-content-data'); if (this.wants === 'BTC') { youBuyData.innerText = `${this.trade_amount_eur * 1021} sats`; } if (this.wants === 'EUR') { youBuyData.innerText = `${this.trade_amount_eur} €`; } tradeDescDiv.append(youBuyData); const youSellText = document.createElement('p'); youSellText.id = 'you-sell-title'; youSellText.classList.add('offer-card-content-title'); youSellText.innerText = 'Vendes'; tradeDescDiv.append(youSellText); const youSellData = document.createElement('p'); youSellData.classList.add('offer-card-content-data'); if (this.wants === 'BTC') { youSellData.innerText = `${this.trade_amount_eur} €`; } if (this.wants === 'EUR') { youSellData.innerText = `${this.trade_amount_eur * 1021} sats`; } tradeDescDiv.append(youSellData); const premiumDescDiv = document.createElement('div'); premiumDescDiv.classList.add('premium-desc'); const premiumTitle = document.createElement('p'); premiumTitle.classList.add('offer-card-content-title'); premiumTitle.innerText = 'Premium'; premiumDescDiv.append(premiumTitle); const premiumData = document.createElement('p'); premiumData.classList.add('offer-card-content-data'); premiumData.innerText = `${this.premium * 100} %`; premiumDescDiv.append(premiumData); const offerPriceTitle = document.createElement('p'); offerPriceTitle.classList.add('offer-card-content-title'); offerPriceTitle.innerText = 'Precio oferta'; premiumDescDiv.append(offerPriceTitle); const offerPriceData = document.createElement('p'); offerPriceData.classList.add('offer-card-content-data'); offerPriceData.innerText = `90000 €/BTC`; premiumDescDiv.append(offerPriceData); const marketPriceTitle = document.createElement('p'); marketPriceTitle.classList.add('offer-card-content-title'); marketPriceTitle.innerText = 'Precio mercado'; premiumDescDiv.append(marketPriceTitle); const marketPriceData = document.createElement('p'); marketPriceData.innerText = `88000 €/BTC`; premiumDescDiv.append(marketPriceData); const whereDescDiv = document.createElement('div'); whereDescDiv.classList.add('where-desc'); const whereDescTitle = document.createElement('p'); whereDescTitle.classList.add('offer-card-content-title'); whereDescTitle.innerText = 'Dónde'; whereDescDiv.append(whereDescTitle); const whereDescData = document.createElement('p'); whereDescData.classList.add('offer-long-text'); whereDescData.innerText = `${this.location_details}`; whereDescDiv.append(whereDescData); const whenDescDiv = document.createElement('div'); whenDescDiv.classList.add('when-desc'); const whenDescTitle = document.createElement('p'); whenDescTitle.classList.add('offer-card-content-title'); whenDescTitle.innerText = 'Cúando'; whenDescDiv.append(whenDescTitle); const whenDescData = document.createElement('p'); whenDescData.classList.add('offer-long-text'); whenDescData.innerText = `${this.time_availability_details}`; whenDescDiv.append(whenDescData); const bitcoinMethodsDiv = document.createElement('div'); bitcoinMethodsDiv.classList.add('bitcoin-methods-desc'); const bitcoinMethodsTitle = document.createElement('p'); bitcoinMethodsTitle.classList.add('offer-card-content-title'); bitcoinMethodsTitle.innerText = 'Protocolos Bitcoin aceptados'; bitcoinMethodsDiv.append(bitcoinMethodsTitle); const onchainAcceptedContainer = document.createElement('div'); onchainAcceptedContainer.classList.add('left-icon-checkboxed-field'); if (this.is_onchain_accepted) { const onchainIcon = document.createElement('img'); onchainIcon.src = '/img/chains-lasecagold.svg'; const onchainText = document.createElement('p'); onchainText.innerText = 'Onchain'; const checkIcon = document.createElement('img'); checkIcon.src = '/img/circle-check-green.svg'; onchainAcceptedContainer.append(onchainIcon, onchainText, checkIcon); } else { const onchainIcon = document.createElement('img'); onchainIcon.src = '/img/chains-gray.svg'; const onchainText = document.createElement('p'); onchainText.innerText = 'Onchain'; const checkIcon = document.createElement('img'); checkIcon.src = '/img/circle-xmark-gray.svg'; onchainAcceptedContainer.append(onchainIcon, onchainText, checkIcon); } const lightningAcceptedContainer = document.createElement('div'); lightningAcceptedContainer.classList.add('left-icon-checkboxed-field'); if (this.is_lightning_accepted) { const lightningIcon = document.createElement('img'); lightningIcon.src = '/img/bolt-lightning-lasecagold.svg'; const lightningText = document.createElement('p'); lightningText.innerText = 'Lightning'; const checkIcon = document.createElement('img'); checkIcon.src = '/img/circle-check-green.svg'; lightningAcceptedContainer.append( lightningIcon, lightningText, checkIcon ); } else { const lightningIcon = document.createElement('img'); lightningIcon.src = '/img/bolt-lightning-gray.svg'; const lightningText = document.createElement('p'); lightningText.innerText = 'Lightning'; const checkIcon = document.createElement('img'); checkIcon.src = '/img/circle-xmark-gray.svg'; lightningAcceptedContainer.append( lightningIcon, lightningText, checkIcon ); } bitcoinMethodsDiv.append( onchainAcceptedContainer, lightningAcceptedContainer ); const visibilityDiv = document.createElement('div'); visibilityDiv.classList.add('visibility-desc'); const visibilityTitle = document.createElement('p'); visibilityTitle.classList.add('offer-card-content-title'); visibilityTitle.innerText = 'Visibilidad'; visibilityDiv.append(visibilityTitle); const showOfferToTrustedContainer = document.createElement('div'); showOfferToTrustedContainer.classList.add('right-icon-checkboxed-field'); if (this.show_offer_to_trusted) { const showOfferToTrustedIcon = document.createElement('img'); showOfferToTrustedIcon.src = '/img/user-lasecagold.svg'; const showOfferToTrustedText = document.createElement('p'); showOfferToTrustedText.innerText = 'Confiados'; const checkIcon = document.createElement('img'); checkIcon.src = '/img/circle-check-green.svg'; showOfferToTrustedContainer.append( showOfferToTrustedIcon, showOfferToTrustedText, checkIcon ); } else { const showOfferToTrustedIcon = document.createElement('img'); showOfferToTrustedIcon.src = '/img/user-gray.svg'; const showOfferToTrustedText = document.createElement('p'); showOfferToTrustedText.innerText = 'Confiados'; const checkIcon = document.createElement('img'); checkIcon.src = '/img/circle-xmark-gray.svg'; showOfferToTrustedContainer.append( showOfferToTrustedIcon, showOfferToTrustedText, checkIcon ); } const showOfferToTrustedTrustedContainer = document.createElement('div'); showOfferToTrustedTrustedContainer.classList.add( 'right-icon-checkboxed-field' ); if (this.show_offer_to_trusted_trusted) { const showOfferToTrustedTrustedIcon = document.createElement('img'); showOfferToTrustedTrustedIcon.src = '/img/user-group-lasecagold.svg'; const showOfferToTrustedTrustedText = document.createElement('p'); showOfferToTrustedTrustedText.innerText = 'Sus confiados'; const checkIcon = document.createElement('img'); checkIcon.src = '/img/circle-check-green.svg'; showOfferToTrustedTrustedContainer.append( showOfferToTrustedTrustedIcon, showOfferToTrustedTrustedText, checkIcon ); } else { const showOfferToTrustedTrustedIcon = document.createElement('img'); showOfferToTrustedTrustedIcon.src = '/img/user-group-gray.svg'; const showOfferToTrustedTrustedText = document.createElement('p'); showOfferToTrustedTrustedText.innerText = 'Sus confiados'; const checkIcon = document.createElement('img'); checkIcon.src = '/img/circle-xmark-gray.svg'; showOfferToTrustedTrustedContainer.append( showOfferToTrustedTrustedIcon, showOfferToTrustedTrustedText, checkIcon ); } const showOfferToAllMembersContainer = document.createElement('div'); showOfferToAllMembersContainer.classList.add( 'right-icon-checkboxed-field' ); if (this.show_offer_to_all_members) { const showOfferToAllMembersIcon = document.createElement('img'); showOfferToAllMembersIcon.src = '/img/many-users-lasecagold.svg'; const showOfferToAllMembersText = document.createElement('p'); showOfferToAllMembersText.innerText = 'Todos'; const checkIcon = document.createElement('img'); checkIcon.src = '/img/circle-check-green.svg'; showOfferToAllMembersContainer.append( showOfferToAllMembersIcon, showOfferToAllMembersText, checkIcon ); } else { const showOfferToAllMembersIcon = document.createElement('img'); showOfferToAllMembersIcon.src = '/img/many-users-gray.svg'; const showOfferToAllMembersText = document.createElement('p'); showOfferToAllMembersText.innerText = 'Todos'; const checkIcon = document.createElement('img'); checkIcon.src = '/img/circle-xmark-gray.svg'; showOfferToAllMembersContainer.append( showOfferToAllMembersIcon, showOfferToAllMembersText, checkIcon ); } visibilityDiv.append( showOfferToTrustedContainer, showOfferToTrustedTrustedContainer, showOfferToAllMembersContainer ); const otherOfferFeaturesDiv = document.createElement('div'); otherOfferFeaturesDiv.classList.add('other-desc'); const otherOfferFeaturesTitle = document.createElement('p'); otherOfferFeaturesTitle.classList.add('offer-card-content-title'); otherOfferFeaturesTitle.innerText = 'Otros'; otherOfferFeaturesDiv.append(otherOfferFeaturesTitle); const areBigNotesAcceptedContainer = document.createElement('div'); areBigNotesAcceptedContainer.classList.add('left-icon-checkboxed-field'); if (this.are_big_notes_accepted) { const areBigNotesAcceptedIcon = document.createElement('img'); areBigNotesAcceptedIcon.src = '/img/eur-bill-lasecagold.svg'; const areBigNotesAcceptedText = document.createElement('p'); areBigNotesAcceptedText.innerText = 'Billetes grandes'; const checkIcon = document.createElement('img'); checkIcon.src = '/img/circle-check-green.svg'; areBigNotesAcceptedContainer.append( areBigNotesAcceptedIcon, areBigNotesAcceptedText, checkIcon ); } else { const areBigNotesAcceptedIcon = document.createElement('img'); areBigNotesAcceptedIcon.src = '/img/eur-bill-gray.svg'; const areBigNotesAcceptedText = document.createElement('p'); areBigNotesAcceptedText.innerText = 'Billetes grandes'; const checkIcon = document.createElement('img'); checkIcon.src = '/img/circle-xmark-gray.svg'; areBigNotesAcceptedContainer.append( areBigNotesAcceptedIcon, areBigNotesAcceptedText, checkIcon ); } otherOfferFeaturesDiv.append(areBigNotesAcceptedContainer); const actionButtonsArea = document.createElement('p'); actionButtonsArea.classList.add('offer-action-buttons-area'); const editActionArea = document.createElement('div'); editActionArea.classList.add('offer-action-area'); editActionArea.classList.add('subtle-box'); const editActionIcon = document.createElement('img'); editActionIcon.src = '/img/edit.svg'; const editActionText = document.createElement('p'); editActionText.innerText = 'Editar'; editActionArea.append(editActionIcon, editActionText); const deleteActionArea = document.createElement('div'); deleteActionArea.classList.add('offer-action-area'); deleteActionArea.classList.add('subtle-box'); const deleteActionIcon = document.createElement('img'); deleteActionIcon.src = '/img/trash-can-darkred.svg'; const deleteActionText = document.createElement('p'); deleteActionText.innerText = 'Eliminar'; deleteActionArea.append(deleteActionIcon, deleteActionText); deleteActionArea.addEventListener('click', async () => { await deleteOfferByUuid(this.uuid); await myOffers.getOffersFromApi(); await myOffers.render(); toggleOfferDeletedAlert(); }); actionButtonsArea.append(editActionArea, deleteActionArea); offerCard.append( tradeDescDiv, premiumDescDiv, whereDescDiv, whenDescDiv, bitcoinMethodsDiv, visibilityDiv, otherOfferFeaturesDiv, actionButtonsArea ); return offerCard; } } class MyOffers { constructor(ownOffersContainerElement) { this.ownOffersContainerElement = ownOffersContainerElement; this.offers = []; } async getOffersFromApi() { const offersResponse = await fetch('/api/publickey-offers'); this.offers = []; const offersData = (await offersResponse.json()).data; if (offersResponse.ok) { for (const record of offersData) { this.offers.push(new Offer(record)); } } } async render() { if (this.offers.length === 0) { this.ownOffersContainerElement.innerHTML = '
Vaya, no hay nada por aquí...
'; return; } this.ownOffersContainerElement.innerHTML = ''; for (const someOffer of this.offers) { this.ownOffersContainerElement.append(someOffer.buildHTML()); } } } async function deleteOfferByUuid(offerUuid) { await fetch(`/api/offer/${offerUuid}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', }, }); } buttonStartCreateOffer.addEventListener('click', () => { toggleCreateOfferModal(); }); buttonViewMyOffers.addEventListener('click', async () => { await myOffers.getOffersFromApi(); await myOffers.render(); toggleViewMyOffersPanel(); }); closeOffer.addEventListener('click', () => { toggleCreateOfferModal(); }); const myOffers = new MyOffers(ownOffersContainer); } offersPage();