diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..fa244c9
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,10 @@
+# secajs
+
+## Architecture
+
+This repository contains a webapp. It covers the full stack which consists of:
+- The client side code, in `src/front/`.
+- The backend service in `src/`, except for `src/front/`.
+- A Postgres database. Database connections and migrations are in `src/database/`.
+- Besides, there is an admin CLI, with entrypoint in `src/cli.js` and commands in `src/commands/`.
+
diff --git a/Dockerfile b/Dockerfile
index 10706ec..42d2c5c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM debian:latest
+FROM debian:12
# Install dependencies
RUN apt-get update
diff --git a/package.json b/package.json
index a1170cb..568a109 100644
--- a/package.json
+++ b/package.json
@@ -20,12 +20,13 @@
"start": "node src/app.js",
"start:container": "docker compose up -d --build",
"stop:container": "docker compose down",
+ "migrate": "npx sequelize-cli db:migrate",
"build": "webpack",
"watch": "webpack --watch",
"cli": "node src/cli.js",
- "test": "playwright test",
"lint": "eslint . --fix",
- "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,html,ejs}\""
+ "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,html,ejs}\"",
+ "test": "playwright test"
},
"keywords": [],
"author": "",
diff --git a/playwright.config.js b/playwright.config.js
new file mode 100644
index 0000000..c7f57c8
--- /dev/null
+++ b/playwright.config.js
@@ -0,0 +1,13 @@
+const { defineConfig } = require('@playwright/test');
+
+module.exports = defineConfig({
+ testDir: './tests',
+ use: {
+ baseURL: 'http://localhost:3000',
+ },
+ webServer: {
+ command: 'npm start',
+ url: 'http://localhost:3000',
+ reuseExistingServer: !process.env.CI,
+ },
+});
diff --git a/public/css/offers.css b/public/css/offers.css
index 6a4e706..17ac4b5 100644
--- a/public/css/offers.css
+++ b/public/css/offers.css
@@ -11,11 +11,11 @@
max-width: 30%;
}
- .premium-selector-area {
+ #premium-selector-area {
width: 80px;
}
- .amount-area-content {
+ #amount-area-content {
width: 50%;
}
@@ -50,11 +50,11 @@
width: 50px;
}
- .premium-selector-area {
+ #premium-selector-area {
width: 100px;
}
- .amount-area-content {
+ #amount-area-content {
width: 33%;
}
@@ -221,8 +221,10 @@
font-size: 0.9em;
}
-.create-offer-controls {
+#create-offer-controls {
text-align: center;
+ overflow-y: auto;
+ max-height: 800px;
padding: 20px;
}
@@ -241,16 +243,16 @@
margin-top: 0;
}
-.close-offer-controls-area {
+#close-offer-controls-area {
display: flex;
justify-content: end;
}
-.premium-area > * {
+#premium-area > * {
display: block;
}
-.premium-content-area {
+#premium-content-area {
width: 80%;
height: 50px;
align-items: center;
@@ -259,19 +261,19 @@
display: flex;
}
-.premium-selector-area {
+#premium-selector-area {
margin-left: auto;
margin-right: 5%;
display: flex;
}
-.premium-value {
+#premium-value {
border: 1px solid gray;
width: 50%;
align-content: center;
}
-.premium-buttons-container {
+#premium-buttons-container {
width: 50%;
}
@@ -287,22 +289,22 @@
background: #fff8ce;
}
-.button-increase-premium {
+#button-increase-premium {
border-top-right-radius: 10px;
}
-.button-decrease-premium {
+#button-decrease-premium {
border-bottom-right-radius: 10px;
}
-.premium-price-display-area {
+#premium-price-display-area {
margin-left: 0;
margin-right: auto;
font-size: 1em;
text-align: start;
}
-.premium-price-display-area > * {
+#premium-price-display-area > * {
margin-top: 0;
margin-bottom: 0;
}
@@ -312,7 +314,7 @@
font-size: 0.8em;
}
-.amount-area-content {
+#amount-area-content {
margin-left: auto;
margin-right: auto;
}
@@ -375,7 +377,7 @@
width: 2em;
}
-.submit-button-area {
+#submit-button-area {
margin-top: 1em;
margin-bottom: 1em;
}
@@ -384,7 +386,7 @@
width: 33%;
}
-.close-offer {
+#close-offer {
margin-left: auto;
margin-right: auto;
}
diff --git a/public/css/seca.css b/public/css/seca.css
index 35b6217..1cc3034 100644
--- a/public/css/seca.css
+++ b/public/css/seca.css
@@ -141,8 +141,6 @@ h1 {
padding: 10px;
width: fit-content;
max-width: 95%;
- max-height: 90vh;
- overflow: auto;
margin: 20px auto;
}
diff --git a/src/constants.js b/src/constants.js
index a454e1b..7af448b 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -2,24 +2,14 @@ const DEFAULT_SESSION_DURATION_SECONDS = 60 * 60 * 24 * 30;
const DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS = 60 * 60 * 24 * 30;
const DEFAULT_REDIRECT_DELAY = 3 * 1000; // 3seconds times milliseconds;
-const API_ROOT = '/api';
const API_PATHS = {
- offer: API_ROOT + '/offer',
- loginNostrChallenge: API_ROOT + '/login/nostr-challenge',
- loginNostrVerify: API_ROOT + '/login/nostr-verify',
- signupNostrChallenge: API_ROOT + '/signup/nostr-challenge',
- signupNostrVerify: API_ROOT + '/signup/nostr-verify',
-};
-
-const WEB_PATHS = {
- home: '/home',
createProfile: '/createProfile',
+ home: '/home',
};
module.exports = {
DEFAULT_SESSION_DURATION_SECONDS,
DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS,
API_PATHS,
- WEB_PATHS,
DEFAULT_REDIRECT_DELAY,
};
diff --git a/src/front/components/AmountInput.js b/src/front/components/AmountInput.js
deleted file mode 100644
index 9badd2a..0000000
--- a/src/front/components/AmountInput.js
+++ /dev/null
@@ -1,113 +0,0 @@
-const formatNumberWithSpaces = require('../utils/formatNumbersWithSpaces');
-
-class AmountInput {
- constructor({ parentElement, id }) {
- this.element = null;
- this.parentElement = parentElement;
- this.id = id;
-
- this.eurInput = null;
- this.btcInput = null;
- }
-
- render() {
- const amountArea = document.createElement('div');
- amountArea.id = this.id;
- amountArea.className = 'amount-area-content';
-
- const eurAmount = document.createElement('div');
- eurAmount.className = 'money-amount-input-area';
-
- this.eurInput = document.createElement('input');
- this.eurInput.type = 'text';
- this.eurInput.className = 'money-input input-money-amount';
- this.eurInput.value = '100';
- this.eurInput.required = true;
-
- const eurSymbol = document.createElement('div');
- eurSymbol.className = 'curr-symbol';
-
- const eurCharacter = document.createElement('span');
- eurCharacter.className = 'curr-character';
- eurCharacter.textContent = '€';
-
- eurSymbol.appendChild(eurCharacter);
- eurAmount.appendChild(this.eurInput);
- eurAmount.appendChild(eurSymbol);
-
- const btcAmount = document.createElement('div');
- btcAmount.className = 'money-amount-input-area';
-
- this.btcInput = document.createElement('input');
- this.btcInput.type = 'text';
- this.btcInput.className = 'money-input input-money-amount';
- this.btcInput.disabled = true;
-
- const satsSymbol = document.createElement('div');
- satsSymbol.className = 'curr-symbol';
-
- const satsCharacter = document.createElement('span');
- satsCharacter.className = 'curr-character';
- satsCharacter.textContent = 'SAT';
-
- satsSymbol.appendChild(satsCharacter);
- btcAmount.appendChild(this.btcInput);
- btcAmount.appendChild(satsSymbol);
-
- amountArea.appendChild(eurAmount);
- amountArea.appendChild(btcAmount);
-
- this.eurInput.addEventListener('blur', () => {
- this.validateAndFormatEurAmountInput();
- this.updateBtcInput();
- });
-
- this.eurInput.addEventListener('input', () => {
- this.eurInput.value = this.eurInput.value.replace(/[^0-9]/g, '');
- this.updateBtcInput();
- });
-
- this.updateBtcInput();
-
- this.element = amountArea;
- this.parentElement.appendChild(this.element);
- }
-
- get intEurAmount() {
- const eurAmountFieldValue = this.eurInput.value;
- const regularExpression = /([\d\s]+)/;
- const matchResult = eurAmountFieldValue.match(regularExpression);
-
- if (!matchResult) {
- return null;
- }
-
- const numberString = matchResult[1];
- const cleanInputNumber = parseInt(numberString.replace(/\s/gi, ''));
-
- return cleanInputNumber;
- }
-
- validateAndFormatEurAmountInput() {
- const cleanInputNumber = this.intEurAmount;
- this.eurInput.classList.remove('input-is-valid', 'input-is-invalid');
- if (cleanInputNumber) {
- this.eurInput.value = formatNumberWithSpaces(cleanInputNumber);
- this.eurInput.classList.add('input-is-valid');
- return;
- }
-
- this.eurInput.classList.add('input-is-invalid');
- }
-
- updateBtcInput() {
- const eurToSatRate = 1021;
- const cleanEurAmount = this.intEurAmount;
-
- const satsAmount = cleanEurAmount * eurToSatRate;
- const formattedSatsAmount = formatNumberWithSpaces(satsAmount);
- this.btcInput.value = formattedSatsAmount;
- }
-}
-
-module.exports = AmountInput;
diff --git a/src/front/components/BigNotesCheckbox.js b/src/front/components/BigNotesCheckbox.js
deleted file mode 100644
index 6361a72..0000000
--- a/src/front/components/BigNotesCheckbox.js
+++ /dev/null
@@ -1,35 +0,0 @@
-class BigNotesCheckbox {
- constructor({ parentElement }) {
- this.bigNotesContainer = null;
- this.bigNotesCheckboxElement = null;
- this.parentElement = parentElement;
- }
-
- render() {
- const container = document.createElement('div');
- container.className = 'checkbox-row';
-
- const checkbox = document.createElement('input');
- checkbox.type = 'checkbox';
- checkbox.name = 'large-bills';
- checkbox.id = 'large-bills-checkbox';
-
- const label = document.createElement('label');
- label.htmlFor = 'large-bills-checkbox';
- label.textContent = 'Se pueden usar billetes grandes (100€, 200€, 500€)';
-
- container.appendChild(checkbox);
- container.appendChild(label);
-
- this.bigNotesContainer = container;
- this.bigNotesCheckboxElement = checkbox;
-
- this.parentElement.append(this.bigNotesContainer);
- }
-
- get areBigNotesAccepted() {
- return this.bigNotesCheckboxElement.checked;
- }
-}
-
-module.exports = BigNotesCheckbox;
diff --git a/src/front/components/BitcoinMethodCheckboxes.js b/src/front/components/BitcoinMethodCheckboxes.js
deleted file mode 100644
index 27cb3f8..0000000
--- a/src/front/components/BitcoinMethodCheckboxes.js
+++ /dev/null
@@ -1,74 +0,0 @@
-class BitcoinMethodCheckboxes {
- constructor({ parentElement }) {
- this.onchainContainer = null;
- this.onchainCheckboxElement = null;
- this.lightningContainer = null;
- this.lightningCheckboxElement = null;
- this.parentElement = parentElement;
- }
-
- render() {
- this.onchainContainer = this.buildCheckbox({
- id: 'onchain',
- label: 'Onchain',
- });
- this.onchainCheckboxElement = this.onchainContainer.querySelector('input');
-
- this.lightningContainer = this.buildCheckbox({
- id: 'lightning',
- label: 'Lightning',
- });
- this.lightningCheckboxElement =
- this.lightningContainer.querySelector('input');
-
- for (const btcMethodCheckbox of [
- this.onchainCheckboxElement,
- this.lightningCheckboxElement,
- ]) {
- btcMethodCheckbox.addEventListener('click', () => {
- this.validateBitcoinMethodCheckboxes(btcMethodCheckbox);
- });
- }
-
- this.parentElement.appendChild(this.onchainContainer);
- this.parentElement.appendChild(this.lightningContainer);
- }
-
- buildCheckbox({ label }) {
- const checkboxContainer = document.createElement('div');
- checkboxContainer.className = 'checkbox-row';
-
- const checkbox = document.createElement('input');
- checkbox.type = 'checkbox';
- checkbox.checked = true;
-
- const labelElement = document.createElement('label');
- labelElement.htmlFor = checkbox.id;
- labelElement.textContent = label;
-
- checkboxContainer.appendChild(checkbox);
- checkboxContainer.appendChild(labelElement);
-
- return checkboxContainer;
- }
-
- validateBitcoinMethodCheckboxes(clickedCheckbox) {
- let checkedCount = [
- this.onchainCheckboxElement,
- this.lightningCheckboxElement,
- ].filter((cb) => cb.checked).length;
- if (checkedCount === 0) {
- clickedCheckbox.checked = true;
- }
- }
-
- get isOnchainAccepted() {
- return this.onchainCheckboxElement.checked;
- }
-
- get isLightningAccepted() {
- return this.lightningCheckboxElement.checked;
- }
-}
-
-module.exports = BitcoinMethodCheckboxes;
diff --git a/src/front/components/CloseModalButton.js b/src/front/components/CloseModalButton.js
deleted file mode 100644
index b857ea5..0000000
--- a/src/front/components/CloseModalButton.js
+++ /dev/null
@@ -1,21 +0,0 @@
-class CloseModalButton {
- constructor({ parentElement, id, onClickCallback }) {
- this.element = null;
- this.parentElement = parentElement;
- this.id = id;
- this.onClickCallback = onClickCallback;
- }
-
- render() {
- const closeButton = document.createElement('button');
- closeButton.className = 'close-offer button-secondary button-medium';
- closeButton.textContent = 'Volver';
-
- closeButton.addEventListener('click', this.onClickCallback);
-
- this.element = closeButton;
- this.parentElement.appendChild(this.element);
- }
-}
-
-module.exports = CloseModalButton;
diff --git a/src/front/components/CreateOfferModal.js b/src/front/components/CreateOfferModal.js
deleted file mode 100644
index b5301d1..0000000
--- a/src/front/components/CreateOfferModal.js
+++ /dev/null
@@ -1,239 +0,0 @@
-const PublishOfferButton = require('./PublishOfferButton');
-const BuyOrSellButtonGroup = require('./BuyOrSellButtonGroup');
-const PremiumSelector = require('./PremiumSelector');
-const PriceDisplay = require('./PriceDisplay');
-const AmountInput = require('./AmountInput');
-const PlaceInput = require('./PlaceInput');
-const TimeInput = require('./TimeInput');
-const BitcoinMethodCheckboxes = require('./BitcoinMethodCheckboxes');
-const TrustCheckboxes = require('./TrustCheckboxes');
-const BigNotesCheckbox = require('./BigNotesCheckbox');
-const CloseModalButton = require('./CloseModalButton');
-
-class CreateOfferModal {
- constructor({ parentElement, onCreationCallback, offerService }) {
- this.element = null;
- this.parentElement = parentElement;
- this.onCreationCallback = onCreationCallback;
- this.offerService = offerService;
-
- 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.className = 'full-screen-modal-background';
-
- const modal = document.createElement('div');
- modal.className = 'full-screen-modal';
-
- const controls = document.createElement('div');
- controls.className = 'create-offer-controls';
-
- const title = document.createElement('h2');
- title.textContent = 'Añade los detalles de tu oferta';
- controls.appendChild(title);
-
- const buyOrSellDiv = document.createElement('div');
- buyOrSellDiv.className = 'create-offer-step';
- this.buyOrSellButtonGroup = new BuyOrSellButtonGroup({
- parentElement: buyOrSellDiv,
- });
- this.buyOrSellButtonGroup.render();
- controls.appendChild(buyOrSellDiv);
-
- const premiumDiv = document.createElement('div');
- premiumDiv.classList = 'premium-area';
- premiumDiv.className = 'create-offer-step';
- const premiumHeading = document.createElement('h3');
- premiumHeading.textContent = 'Premium';
- premiumDiv.appendChild(premiumHeading);
- const premiumContentDiv = document.createElement('div');
- premiumContentDiv.classList = 'premium-content-area';
- premiumDiv.appendChild(premiumContentDiv);
- controls.appendChild(premiumDiv);
-
- const createOfferEventBus = new EventTarget();
-
- const mockPriceProvidingCallback = () => {
- return Math.floor(Math.random() * (95000 - 70000 + 1) + 70000);
- };
-
- this.premiumSelector = new PremiumSelector({
- parentElement: premiumContentDiv,
- eventSink: createOfferEventBus,
- });
- this.premiumSelector.render();
-
- const priceDisplay = new PriceDisplay({
- parentElement: premiumContentDiv,
- id: 'premium-price-display-area',
- premiumProvidingCallback: () => {
- return this.premiumSelector.getPremium();
- },
- priceProvidingCallback: mockPriceProvidingCallback,
- });
- priceDisplay.render();
- createOfferEventBus.addEventListener('premium-changed', () => {
- priceDisplay.updatePrices();
- });
-
- const amountDiv = document.createElement('div');
- amountDiv.className = 'create-offer-step';
- const amountHeading = document.createElement('h3');
- amountHeading.textContent = '¿Cuánto?';
- amountDiv.appendChild(amountHeading);
- controls.appendChild(amountDiv);
-
- this.amountInput = new AmountInput({
- parentElement: amountDiv,
- });
-
- this.amountInput.render();
-
- const placeTimeDiv = document.createElement('div');
- placeTimeDiv.className = 'create-offer-step';
- const placeTimeHeading = document.createElement('h3');
- placeTimeHeading.textContent = '¿Dónde y cuándo?';
- placeTimeDiv.appendChild(placeTimeHeading);
- const placeTimeContentDiv = document.createElement('div');
- placeTimeDiv.appendChild(placeTimeContentDiv);
- controls.appendChild(placeTimeDiv);
-
- this.placeInput = new PlaceInput({
- parentElement: placeTimeContentDiv,
- });
-
- this.placeInput.render();
-
- this.timeInput = new TimeInput({
- parentElement: placeTimeContentDiv,
- });
-
- this.timeInput.render();
-
- const bitcoinMethodsDiv = document.createElement('div');
- bitcoinMethodsDiv.className = 'create-offer-step';
- const bitcoinMethodsHeading = document.createElement('h3');
- bitcoinMethodsHeading.textContent = '¿Cómo se mueve el Bitcoin?';
- bitcoinMethodsDiv.appendChild(bitcoinMethodsHeading);
- const bitcoinMethodsContentDiv = document.createElement('div');
- bitcoinMethodsDiv.appendChild(bitcoinMethodsContentDiv);
- controls.appendChild(bitcoinMethodsDiv);
-
- this.btcMethodCheckboxes = new BitcoinMethodCheckboxes({
- parentElement: bitcoinMethodsContentDiv,
- });
-
- this.btcMethodCheckboxes.render();
-
- const trustDiv = document.createElement('div');
- trustDiv.className = 'create-offer-step';
- const trustHeading = document.createElement('h3');
- trustHeading.textContent = '¿Quién puede ver la oferta?';
- trustDiv.appendChild(trustHeading);
- const trustContentDiv = document.createElement('div');
- trustDiv.appendChild(trustContentDiv);
- controls.appendChild(trustDiv);
-
- this.trustCheckboxes = new TrustCheckboxes({
- parentElement: trustContentDiv,
- });
-
- this.trustCheckboxes.render();
-
- const otherDiv = document.createElement('div');
- otherDiv.className = 'create-offer-step';
- const otherHeading = document.createElement('h3');
- otherHeading.textContent = 'Extras';
- otherDiv.appendChild(otherHeading);
- controls.appendChild(otherDiv);
-
- this.bigNotesCheckbox = new BigNotesCheckbox({
- parentElement: otherDiv,
- });
-
- this.bigNotesCheckbox.render();
- //Continue moving components up here
-
- const submitButtonArea = document.createElement('div');
- submitButtonArea.classList.add('submit-button-area');
- this.publishOfferButton = new PublishOfferButton({
- parentElement: submitButtonArea,
- id: 'button-submit-offer',
- onClickCallback: async () => {
- await this.createOffer();
- await this.onCreationCallback();
- this.toggle();
- },
- });
- this.publishOfferButton.render();
-
- const closeButtonArea = document.createElement('div');
- closeButtonArea.className = 'close-offer-controls-area';
- const closeButton = new CloseModalButton({
- parentElement: closeButtonArea,
- onClickCallback: () => {
- this.toggle();
- },
- });
-
- closeButton.render();
-
- controls.appendChild(submitButtonArea);
- controls.appendChild(closeButtonArea);
-
- modal.appendChild(controls);
- modalRoot.appendChild(modal);
-
- this.parentElement.appendChild(this.element);
- }
-
- 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 this.offerService.createOffer(offerDetails);
- }
-}
-
-module.exports = CreateOfferModal;
diff --git a/src/front/components/OfferCard.js b/src/front/components/OfferCard.js
deleted file mode 100644
index 5c11452..0000000
--- a/src/front/components/OfferCard.js
+++ /dev/null
@@ -1,374 +0,0 @@
-class OfferCard {
- constructor({ offerData, deleteButtonCallback }) {
- 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;
-
- this.deleteButtonCallback = deleteButtonCallback;
- }
-
- 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.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 this.deleteButtonCallback();
- });
-
- actionButtonsArea.append(editActionArea, deleteActionArea);
-
- offerCard.append(
- tradeDescDiv,
- premiumDescDiv,
- whereDescDiv,
- whenDescDiv,
- bitcoinMethodsDiv,
- visibilityDiv,
- otherOfferFeaturesDiv,
- actionButtonsArea
- );
-
- return offerCard;
- }
-}
-
-module.exports = OfferCard;
diff --git a/src/front/components/PlaceInput.js b/src/front/components/PlaceInput.js
deleted file mode 100644
index 3d573b2..0000000
--- a/src/front/components/PlaceInput.js
+++ /dev/null
@@ -1,26 +0,0 @@
-class PlaceInput {
- constructor({ parentElement, id }) {
- this.element = null;
- this.parentElement = parentElement;
- this.id = id;
- }
-
- render() {
- const placeInput = document.createElement('textarea');
- placeInput.id = this.id;
- placeInput.className = 'place-and-time-box';
- placeInput.autocomplete = 'on';
- placeInput.maxLength = 140;
- placeInput.placeholder =
- "¿Dónde? Ej.'Eixample', 'La Maquinista', 'Cualquier lugar en BCN', 'Meetup BBO'";
-
- this.element = placeInput;
- this.parentElement.appendChild(this.element);
- }
-
- get inputText() {
- return this.element.value;
- }
-}
-
-module.exports = PlaceInput;
diff --git a/src/front/components/PopupNotification.js b/src/front/components/PopupNotification.js
index 8604184..e2a0588 100644
--- a/src/front/components/PopupNotification.js
+++ b/src/front/components/PopupNotification.js
@@ -9,7 +9,7 @@ class PopupNotification {
render() {
const div = document.createElement('div');
div.id = this.id;
- div.className = 'top-notification-good max-size-zero';
+ div.className = 'top-notification-good';
div.innerHTML = `
${this.text}
`; @@ -21,19 +21,5 @@ class PopupNotification { display() { this.element.classList.add('revealed'); } - - displayTemporarily(milliseconds) { - if (!milliseconds) { - milliseconds = 1000; - } - this.element.classList.remove('max-size-zero'); - this.element.classList.add('revealed'); - setTimeout(() => { - this.element.classList.remove('revealed'); - }, milliseconds); - setTimeout(() => { - this.element.classList.add('max-size-zero'); - }, milliseconds + 1000); - } } module.exports = PopupNotification; diff --git a/src/front/components/PremiumSelector.js b/src/front/components/PremiumSelector.js index 68f994d..d2defdd 100644 --- a/src/front/components/PremiumSelector.js +++ b/src/front/components/PremiumSelector.js @@ -11,10 +11,9 @@ class PremiumSelector { render() { const premiumSelectorArea = document.createElement('div'); premiumSelectorArea.id = this.id; - premiumSelectorArea.classList = 'premium-selector-area'; const premiumValue = document.createElement('div'); - premiumValue.className = 'premium-value'; + premiumValue.id = 'premium-value'; premiumValue.textContent = '0%'; this.premiumValue = premiumValue; @@ -23,12 +22,12 @@ class PremiumSelector { const increaseButton = document.createElement('button'); increaseButton.classList.add('premium-button'); - increaseButton.classList.add('button-increase-premium'); + increaseButton.id = 'button-increase-premium'; increaseButton.textContent = '+'; const decreaseButton = document.createElement('button'); decreaseButton.classList.add('premium-button'); - decreaseButton.classList.add('button-decrease-premium'); + decreaseButton.id = 'button-decrease-premium'; decreaseButton.textContent = '-'; premiumButtonsContainer.appendChild(increaseButton); diff --git a/src/front/components/PriceDisplay.js b/src/front/components/PriceDisplay.js index be3055d..39eb16d 100644 --- a/src/front/components/PriceDisplay.js +++ b/src/front/components/PriceDisplay.js @@ -16,10 +16,10 @@ class PriceDisplay { render() { const container = document.createElement('div'); - container.id = this.id; - container.classList = 'premium-price-display-area'; + container.id = 'premium-price-display-area'; const offerParagraph = document.createElement('p'); + offerParagraph.id = 'offer-price-paragraph'; offerParagraph.textContent = 'Tu precio: '; const offerSpan = document.createElement('span'); @@ -30,9 +30,11 @@ class PriceDisplay { offerParagraph.append('€/BTC'); const marketParagraph = document.createElement('p'); + marketParagraph.id = 'market-price-paragraph'; marketParagraph.textContent = '(Precio mercado: '; const marketSpan = document.createElement('span'); + marketSpan.id = 'market-price'; this.marketPriceSpan = marketSpan; marketParagraph.appendChild(marketSpan); diff --git a/src/front/components/TimeInput.js b/src/front/components/TimeInput.js deleted file mode 100644 index 68f0451..0000000 --- a/src/front/components/TimeInput.js +++ /dev/null @@ -1,26 +0,0 @@ -class TimeInput { - constructor({ parentElement, id }) { - this.element = null; - this.parentElement = parentElement; - this.id = id; - } - - render() { - const timeInput = document.createElement('textarea'); - timeInput.id = this.id; - timeInput.className = 'place-and-time-box'; - timeInput.autocomplete = 'on'; - timeInput.maxLength = 140; - timeInput.placeholder = - '¿Cuándo? Ej."Cualquier hora", "19:00-21:00", "Finde"'; - - this.element = timeInput; - this.parentElement.appendChild(this.element); - } - - get inputText() { - return this.element.value; - } -} - -module.exports = TimeInput; diff --git a/src/front/components/TrustCheckboxes.js b/src/front/components/TrustCheckboxes.js deleted file mode 100644 index f1b8768..0000000 --- a/src/front/components/TrustCheckboxes.js +++ /dev/null @@ -1,111 +0,0 @@ -class TrustCheckboxes { - constructor({ parentElement }) { - this.myTrustedContainer = null; - this.myTrustedCheckboxElement = null; - this.myTrustedTrustedContainer = null; - this.myTrustedTrustedCheckboxElement = null; - this.allMembersContainer = null; - this.allMembersCheckboxElement = null; - this.parentElement = parentElement; - } - - render() { - const checkboxesDetails = [ - { - label: 'Mis confiados', - containerProperty: 'myTrustedContainer', - checkboxProperty: 'myTrustedCheckboxElement', - defaultChecked: true, - isDisabled: true, - }, - { - label: 'Los confiados de mis confiados', - containerProperty: 'myTrustedTrustedContainer', - checkboxProperty: 'myTrustedTrustedCheckboxElement', - defaultChecked: true, - isDisabled: false, - }, - { - label: 'Todos los miembros', - containerProperty: 'allMembersContainer', - checkboxProperty: 'allMembersCheckboxElement', - defaultChecked: false, - isDisabled: false, - }, - ]; - - for (const checkboxDetails of checkboxesDetails) { - this[checkboxDetails.containerProperty] = this.buildCheckbox({ - label: checkboxDetails.label, - }); - - this[checkboxDetails.checkboxProperty] = - this[checkboxDetails.containerProperty].querySelector('input'); - - this[checkboxDetails.checkboxProperty].addEventListener('click', () => { - this.applyTrustCheckboxConstraints( - this[checkboxDetails.checkboxProperty] - ); - }); - - this[checkboxDetails.checkboxProperty].checked = - checkboxDetails.defaultChecked; - this[checkboxDetails.checkboxProperty].disabled = - checkboxDetails.isDisabled; - - this.parentElement.appendChild(this[checkboxDetails.containerProperty]); - } - } - - buildCheckbox({ label }) { - const checkboxContainer = document.createElement('div'); - checkboxContainer.className = 'checkbox-row'; - - const checkbox = document.createElement('input'); - checkbox.type = 'checkbox'; - checkbox.checked = true; - - const labelElement = document.createElement('label'); - labelElement.htmlFor = checkbox.id; - labelElement.textContent = label; - - checkboxContainer.appendChild(checkbox); - checkboxContainer.appendChild(labelElement); - - return checkboxContainer; - } - - applyTrustCheckboxConstraints(pressedCheckbox) { - if (pressedCheckbox === this.myTrustedTrustedCheckboxElement) { - if ( - !this.myTrustedTrustedCheckboxElement.checked && - this.allMembersCheckboxElement.checked - ) { - this.allMembersCheckboxElement.checked = false; - } - } - - if (pressedCheckbox === this.allMembersCheckboxElement) { - if ( - !this.myTrustedTrustedCheckboxElement.checked && - this.allMembersCheckboxElement.checked - ) { - this.myTrustedTrustedCheckboxElement.checked = true; - } - } - } - - get showOfferToTrusted() { - return this.myTrustedCheckboxElement.checked; - } - - get showOfferToTrustedTrusted() { - return this.myTrustedTrustedCheckboxElement.checked; - } - - get showOfferToAllMembers() { - return this.allMembersCheckboxElement.checked; - } -} - -module.exports = TrustCheckboxes; diff --git a/src/front/pages/invite.js b/src/front/pages/invite.js index d756ca3..24bc3b5 100644 --- a/src/front/pages/invite.js +++ b/src/front/pages/invite.js @@ -24,7 +24,7 @@ const invitesFunction = () => { if (verifyResponse.ok) { signUpSuccessPopup.display(); setTimeout(() => { - window.location.href = constants.WEB_PATHS.createProfile; + window.location.href = constants.API_PATHS.createProfile; }, constants.DEFAULT_REDIRECT_DELAY); } }, diff --git a/src/front/pages/login.js b/src/front/pages/login.js index 4b304e2..5620a99 100644 --- a/src/front/pages/login.js +++ b/src/front/pages/login.js @@ -38,7 +38,7 @@ const loginFunction = () => { nostrLoginButton.disable(); successPopup.display(); setTimeout(() => { - window.location.href = constants.WEB_PATHS.home; + window.location.href = constants.API_PATHS.home; }, constants.DEFAULT_REDIRECT_DELAY); } }, diff --git a/src/front/pages/offers.js b/src/front/pages/offers.js index 01c6ab3..1913e7a 100644 --- a/src/front/pages/offers.js +++ b/src/front/pages/offers.js @@ -1,32 +1,51 @@ -const PopupNotification = require('../components/PopupNotification'); -const CreateOfferModal = require('../components/CreateOfferModal'); -const OfferCard = require('../components/OfferCard'); - -const offerService = require('../services/offerService'); +const formatNumberWithSpaces = require('../utils/formatNumbersWithSpaces'); +const PublishOfferButton = require('../components/PublishOfferButton'); +const BuyOrSellButtonGroup = require('../components/BuyOrSellButtonGroup'); +const PremiumSelector = require('../components/PremiumSelector'); +const PriceDisplay = require('../components/PriceDisplay'); function offersPage() { - const offerCreatedPopup = new PopupNotification({ - parentElement: document.body, - text: '¡Oferta creada! Puedes verla en tus ofertas.', - }); - offerCreatedPopup.render(); + const createOfferEventBus = new EventTarget(); - const offerDeletedPopup = new PopupNotification({ - parentElement: document.body, - text: '¡Oferta eliminada!', - }); - offerDeletedPopup.render(); - - const createOfferModal = new CreateOfferModal({ - parentElement: document.body, - onCreationCallback: async () => { + const publishOfferButton = new PublishOfferButton({ + parentElement: document.getElementById('submit-button-area'), + id: 'button-submit-offer', + onClickCallback: async () => { + await publishOffer(); await myOffers.getOffersFromApi(); await myOffers.render(); - offerCreatedPopup.displayTemporarily(3000); }, - offerService: offerService, }); - createOfferModal.render(); + publishOfferButton.render(); + + const buyOrSellButtonGroup = new BuyOrSellButtonGroup({ + parentElement: document.getElementById('buy-or-sell-area'), + id: 'button-group-buy-or-sell', + }); + buyOrSellButtonGroup.render(); + + const premiumSelector = new PremiumSelector({ + parentElement: document.getElementById('premium-content-area'), + id: 'premium-selector-area', + eventSink: createOfferEventBus, + }); + premiumSelector.render(); + + const priceDisplay = new PriceDisplay({ + parentElement: document.getElementById('premium-content-area'), + id: 'premium-price-display-area', + premiumProvidingCallback: () => { + return premiumSelector.getPremium(); + }, + priceProvidingCallback: () => { + return Math.floor(Math.random() * (95000 - 70000 + 1) + 70000); + }, + }); + priceDisplay.render(); + createOfferEventBus.addEventListener('premium-changed', () => { + priceDisplay.updatePrices(); + }); + // ----------- const navbuttonHome = document.getElementById('navbutton-home'); const navbuttonOffers = document.getElementById('navbutton-offers'); @@ -43,15 +62,550 @@ function offersPage() { '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 eurAmountInput = document.getElementById('input-eur-amount'); + const btcAmountInput = document.getElementById('input-btc-amount'); + + const placeInput = document.getElementById('place-input'); + const timeInput = document.getElementById('time-input'); + + const onchainCheckbox = document.getElementById('onchain-checkbox'); + const lightningCheckbox = document.getElementById('lightning-checkbox'); + + const btcMethodCheckboxes = [onchainCheckbox, lightningCheckbox]; + + const myTrustedCheckbox = document.getElementById('my-trusted-checkbox'); + const myTrustedTrustedCheckbox = document.getElementById( + 'my-trusted-trusted-checkbox' + ); + const allMembersCheckbox = document.getElementById('all-members-checkbox'); + + const bigNotesAcceptedCheckbox = document.getElementById( + 'large-bills-checkbox' + ); + + const offerCreatedPopup = document.getElementById( + 'offer-created-confirmation' + ); + 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 readIntFromEurAmountInput() { + const eurAmountFieldValue = eurAmountInput.value; + const regularExpression = /([\d\s]+)/; + const matchResult = eurAmountFieldValue.match(regularExpression); + + if (!matchResult) { + return null; + } + + const numberString = matchResult[1]; + const cleanInputNumber = parseInt(numberString.replace(/\s/gi, '')); + + return cleanInputNumber; + } + + function validateAndFormatEurAmountInput() { + const cleanInputNumber = readIntFromEurAmountInput(); + eurAmountInput.classList.remove('input-is-valid', 'input-is-invalid'); + if (cleanInputNumber) { + eurAmountInput.value = formatNumberWithSpaces(cleanInputNumber); + eurAmountInput.classList.add('input-is-valid'); + return; + } + + eurAmountInput.classList.add('input-is-invalid'); + } + + function updateBtcInput() { + const eurToSatRate = 1021; + const cleanEurAmount = readIntFromEurAmountInput(); + + const satsAmount = cleanEurAmount * eurToSatRate; + const formattedSatsAmount = formatNumberWithSpaces(satsAmount); + btcAmountInput.value = formattedSatsAmount; + } + + function validateBitcoinMethodCheckboxes(clickedCheckbox) { + let checkedCount = btcMethodCheckboxes.filter((cb) => cb.checked).length; + if (checkedCount === 0) { + clickedCheckbox.checked = true; + } + } + + function applyTrustCheckboxConstraints(pressedCheckbox) { + if (pressedCheckbox === myTrustedTrustedCheckbox) { + console.log('first case!'); + if (!myTrustedTrustedCheckbox.checked && allMembersCheckbox.checked) { + allMembersCheckbox.checked = false; + } + } + + if (pressedCheckbox === allMembersCheckbox) { + console.log('second case!'); + if (!myTrustedTrustedCheckbox.checked && allMembersCheckbox.checked) { + myTrustedTrustedCheckbox.checked = true; + } + } + } + + async function publishOffer() { + const wants = buyOrSellButtonGroup.wants(); + + const premium = premiumSelector.getPremium(); + const trade_amount_eur = eurAmountInput.value; + const location_details = placeInput.value; + const time_availability_details = timeInput.value; + const is_onchain_accepted = onchainCheckbox.checked; + const is_lightning_accepted = lightningCheckbox.checked; + const show_offer_to_trusted = myTrustedCheckbox.checked; + const show_offer_to_trusted_trusted = myTrustedTrustedCheckbox.checked; + const show_offer_to_all_members = allMembersCheckbox.checked; + const are_big_notes_accepted = bigNotesAcceptedCheckbox.checked; + + 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 }), + }); + + toggleOfferCreatedAlert(); + + toggleCreateOfferModal(); + } + + function toggleOfferCreatedAlert() { + offerCreatedPopup.classList.remove('max-size-zero'); + offerCreatedPopup.classList.add('revealed'); + setTimeout(() => { + offerCreatedPopup.classList.remove('revealed'); + }, 3000); + setTimeout(() => { + offerCreatedPopup.classList.add('max-size-zero'); + }, 4000); + } + + 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; @@ -66,17 +620,7 @@ function offersPage() { const offersData = (await offersResponse.json()).data; if (offersResponse.ok) { for (const record of offersData) { - this.offers.push( - new OfferCard({ - offerData: record, - deleteButtonCallback: async () => { - await offerService.deleteOffer(record.uuid); - await this.getOffersFromApi(); - await this.render(); - offerDeletedPopup.displayTemporarily(3000); - }, - }) - ); + this.offers.push(new Offer(record)); } } } @@ -95,8 +639,17 @@ function offersPage() { } } + async function deleteOfferByUuid(offerUuid) { + await fetch(`/api/offer/${offerUuid}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }); + } + buttonStartCreateOffer.addEventListener('click', () => { - createOfferModal.toggle(); + toggleCreateOfferModal(); }); buttonViewMyOffers.addEventListener('click', async () => { @@ -105,6 +658,36 @@ function offersPage() { toggleViewMyOffersPanel(); }); + closeOffer.addEventListener('click', () => { + toggleCreateOfferModal(); + }); + + eurAmountInput.addEventListener('blur', () => { + validateAndFormatEurAmountInput(); + updateBtcInput(); + }); + + eurAmountInput.addEventListener('input', () => { + eurAmountInput.value = eurAmountInput.value.replace(/[^0-9]/g, ''); + updateBtcInput(); + }); + + for (const btcMethodCheckbox of btcMethodCheckboxes) { + btcMethodCheckbox.addEventListener('click', () => { + validateBitcoinMethodCheckboxes(btcMethodCheckbox); + }); + } + + myTrustedTrustedCheckbox.addEventListener('click', () => { + applyTrustCheckboxConstraints(myTrustedTrustedCheckbox); + }); + + allMembersCheckbox.addEventListener('click', () => { + applyTrustCheckboxConstraints(allMembersCheckbox); + }); + + updateBtcInput(); + const myOffers = new MyOffers(ownOffersContainer); } diff --git a/src/front/services/inviteService.js b/src/front/services/inviteService.js index 590913e..d494c6c 100644 --- a/src/front/services/inviteService.js +++ b/src/front/services/inviteService.js @@ -1,9 +1,7 @@ -const constants = require('../../constants'); - const requestAndRespondSignUpChallenge = async ({ onNostrErrorCallback }) => { let challengeResponse; try { - challengeResponse = await fetch(constants.API_PATHS.signupNostrChallenge, { + challengeResponse = await fetch('/api/signup/nostr-challenge', { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -41,7 +39,7 @@ const requestAndRespondSignUpChallenge = async ({ onNostrErrorCallback }) => { let verifyResponse; try { - verifyResponse = await fetch(constants.API_PATHS.signupNostrVerify, { + verifyResponse = await fetch('/api/signup/nostr-verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(signedEvent), diff --git a/src/front/services/loginService.js b/src/front/services/loginService.js index 852b438..d1f5815 100644 --- a/src/front/services/loginService.js +++ b/src/front/services/loginService.js @@ -1,12 +1,15 @@ -const constants = require('../../constants'); - const requestAndRespondLoginChallenge = async ({ onRejectedPubKeyCallback, onRejectedSignatureCallback, }) => { + onRejectedPubKeyCallback = () => { + document.querySelector('#rejected-nostr-nudges').style.display = 'block'; + }; + onRejectedSignatureCallback = onRejectedPubKeyCallback; + let challengeResponse; try { - challengeResponse = await fetch(constants.API_PATHS.loginNostrChallenge, { + challengeResponse = await fetch('/api/login/nostr-challenge', { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -44,7 +47,7 @@ const requestAndRespondLoginChallenge = async ({ let verifyResponse; try { - verifyResponse = await fetch(constants.API_PATHS.loginNostrVerify, { + verifyResponse = await fetch('/api/login/nostr-verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(signedEvent), diff --git a/src/front/services/offerService.js b/src/front/services/offerService.js deleted file mode 100644 index 591a0f0..0000000 --- a/src/front/services/offerService.js +++ /dev/null @@ -1,25 +0,0 @@ -const constants = require('../../constants'); - -const createOffer = async (offerDetails) => { - await fetch(constants.API_PATHS.offer, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ offerDetails }), - }); -}; - -const deleteOffer = async (offerUuid) => { - await fetch(`${constants.API_PATHS.offer}/${offerUuid}`, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - }, - }); -}; - -module.exports = { - createOffer, - deleteOffer, -}; diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 6fa2e9f..aba756c 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -31,6 +31,145 @@Vaya, no hay nada por aquí...
+¡Oferta creada! Puedes verla en tus ofertas.
+¡Oferta eliminada!
+