feat: free edit and publish/unpublish toggle for paid listings within 30-day period
This commit is contained in:
@@ -124,6 +124,8 @@ class ListingCard extends HTMLElement {
|
|||||||
paymentBadge = /* html */`<span class="payment-badge payment-processing"><span class="pulse-dot"></span>${t('myListings.status.processing')}</span>`
|
paymentBadge = /* html */`<span class="payment-badge payment-processing"><span class="pulse-dot"></span>${t('myListings.status.processing')}</span>`
|
||||||
} else if (paymentStatus === 'expired') {
|
} else if (paymentStatus === 'expired') {
|
||||||
paymentBadge = /* html */`<span class="payment-badge payment-expired">${t('myListings.status.expired')}</span>`
|
paymentBadge = /* html */`<span class="payment-badge payment-expired">${t('myListings.status.expired')}</span>`
|
||||||
|
} else if (paymentStatus === 'paid' && status === 'draft') {
|
||||||
|
paymentBadge = /* html */`<span class="payment-badge payment-unpublished">${t('myListings.status.unpublished')}</span>`
|
||||||
} else if (paymentStatus === 'paid') {
|
} else if (paymentStatus === 'paid') {
|
||||||
paymentBadge = /* html */`<span class="payment-badge payment-published">${t('myListings.status.published')}</span>`
|
paymentBadge = /* html */`<span class="payment-badge payment-published">${t('myListings.status.published')}</span>`
|
||||||
}
|
}
|
||||||
@@ -255,6 +257,11 @@ style.textContent = /* css */`
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listing-card .payment-unpublished {
|
||||||
|
background: rgba(120, 120, 120, 0.85);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
listing-card .pulse-dot {
|
listing-card .pulse-dot {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { t, i18n } from '../../i18n.js'
|
|||||||
import { router } from '../../router.js'
|
import { router } from '../../router.js'
|
||||||
import { auth } from '../../services/auth.js'
|
import { auth } from '../../services/auth.js'
|
||||||
import { directus } from '../../services/directus.js'
|
import { directus } from '../../services/directus.js'
|
||||||
|
import { listingsService } from '../../services/listings.js'
|
||||||
import { categoriesService } from '../../services/categories.js'
|
import { categoriesService } from '../../services/categories.js'
|
||||||
import { SUPPORTED_CURRENCIES, getDisplayCurrency } from '../../services/currency.js'
|
import { SUPPORTED_CURRENCIES, getDisplayCurrency } from '../../services/currency.js'
|
||||||
import { createInvoice, openCheckout, getPendingInvoice, savePendingInvoice, clearPendingInvoice, getInvoiceStatus } from '../../services/btcpay.js'
|
import { createInvoice, openCheckout, getPendingInvoice, savePendingInvoice, clearPendingInvoice, getInvoiceStatus } from '../../services/btcpay.js'
|
||||||
@@ -17,6 +18,7 @@ class PageCreate extends HTMLElement {
|
|||||||
super()
|
super()
|
||||||
this.editMode = false
|
this.editMode = false
|
||||||
this.editId = null
|
this.editId = null
|
||||||
|
this.editListing = null
|
||||||
this.existingImages = []
|
this.existingImages = []
|
||||||
this.formData = this.loadDraft() || this.getEmptyFormData()
|
this.formData = this.loadDraft() || this.getEmptyFormData()
|
||||||
this.imageFiles = []
|
this.imageFiles = []
|
||||||
@@ -102,6 +104,8 @@ class PageCreate extends HTMLElement {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.editListing = listing
|
||||||
|
|
||||||
this.formData = {
|
this.formData = {
|
||||||
title: listing.title || '',
|
title: listing.title || '',
|
||||||
description: listing.description || '',
|
description: listing.description || '',
|
||||||
@@ -606,6 +610,12 @@ class PageCreate extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.editListing && !listingsService.isPaidAndActive(this.editListing)) {
|
||||||
|
await directus.updateListing(this.editId, listingData)
|
||||||
|
await this.startPayment(this.editId, formElements.currency)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await directus.updateListing(this.editId, listingData)
|
await directus.updateListing(this.editId, listingData)
|
||||||
router.navigate(`/listing/${this.editId}`)
|
router.navigate(`/listing/${this.editId}`)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { t, i18n } from '../../i18n.js'
|
import { t, i18n } from '../../i18n.js'
|
||||||
import { directus } from '../../services/directus.js'
|
import { directus } from '../../services/directus.js'
|
||||||
import { auth } from '../../services/auth.js'
|
import { auth } from '../../services/auth.js'
|
||||||
|
import { listingsService } from '../../services/listings.js'
|
||||||
import { escapeHTML } from '../../utils/helpers.js'
|
import { escapeHTML } from '../../utils/helpers.js'
|
||||||
import '../listing-card.js'
|
import '../listing-card.js'
|
||||||
import '../skeleton-card.js'
|
import '../skeleton-card.js'
|
||||||
@@ -112,7 +113,7 @@ class PageMyListings extends HTMLElement {
|
|||||||
const response = await directus.getListings({
|
const response = await directus.getListings({
|
||||||
fields: [
|
fields: [
|
||||||
'id', 'status', 'title', 'slug', 'price', 'currency',
|
'id', 'status', 'title', 'slug', 'price', 'currency',
|
||||||
'condition', 'payment_status', 'expires_at', 'date_created', 'user_created',
|
'condition', 'payment_status', 'paid_at', 'expires_at', 'date_created', 'user_created',
|
||||||
'images.directus_files_id.id',
|
'images.directus_files_id.id',
|
||||||
'location.id', 'location.name'
|
'location.id', 'location.name'
|
||||||
],
|
],
|
||||||
@@ -142,7 +143,7 @@ class PageMyListings extends HTMLElement {
|
|||||||
if (listing.status === 'archived') {
|
if (listing.status === 'archived') {
|
||||||
return `<span class="status-badge status-archived">${t('myListings.status.archived')}</span>`
|
return `<span class="status-badge status-archived">${t('myListings.status.archived')}</span>`
|
||||||
}
|
}
|
||||||
if (listing.status === 'draft' && listing.payment_status !== 'processing' && listing.payment_status !== 'pending') {
|
if (listing.status === 'draft' && listing.payment_status !== 'processing' && listing.payment_status !== 'pending' && !listingsService.isPaidAndActive(listing)) {
|
||||||
return `<span class="status-badge status-draft">${t('myListings.status.draft')}</span>`
|
return `<span class="status-badge status-draft">${t('myListings.status.draft')}</span>`
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
@@ -209,12 +210,25 @@ class PageMyListings extends HTMLElement {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.listings.map(listing => {
|
const html = this.listings.map(listing => {
|
||||||
const imageId = listing.images?.[0]?.directus_files_id?.id || listing.images?.[0]?.directus_files_id
|
const imageId = listing.images?.[0]?.directus_files_id?.id || listing.images?.[0]?.directus_files_id
|
||||||
const imageUrl = imageId ? directus.getThumbnailUrl(imageId, 300) : ''
|
const imageUrl = imageId ? directus.getThumbnailUrl(imageId, 300) : ''
|
||||||
const locationName = listing.location?.name || ''
|
const locationName = listing.location?.name || ''
|
||||||
const statusBadge = this.getStatusBadge(listing)
|
const statusBadge = this.getStatusBadge(listing)
|
||||||
|
|
||||||
|
const paidActive = listingsService.isPaidAndActive(listing)
|
||||||
|
const isPublished = listing.status === 'published'
|
||||||
|
const isDraftPaid = listing.status === 'draft' && paidActive
|
||||||
|
let toggleBtn = ''
|
||||||
|
if (isPublished || isDraftPaid) {
|
||||||
|
const label = isPublished ? t('myListings.unpublish') : t('myListings.republish')
|
||||||
|
toggleBtn = /* html */`
|
||||||
|
<button class="btn-toggle-status" data-id="${listing.id}" data-status="${isPublished ? 'draft' : 'published'}">
|
||||||
|
${label}
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
return /* html */`
|
return /* html */`
|
||||||
<div class="listing-wrapper">
|
<div class="listing-wrapper">
|
||||||
${statusBadge}
|
${statusBadge}
|
||||||
@@ -229,9 +243,38 @@ class PageMyListings extends HTMLElement {
|
|||||||
payment-status="${listing.payment_status || ''}"
|
payment-status="${listing.payment_status || ''}"
|
||||||
status="${listing.status || ''}"
|
status="${listing.status || ''}"
|
||||||
></listing-card>
|
></listing-card>
|
||||||
|
${toggleBtn}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
}).join('')
|
}).join('')
|
||||||
|
|
||||||
|
setTimeout(() => this.setupToggleListeners(), 0)
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
|
setupToggleListeners() {
|
||||||
|
this.querySelectorAll('.btn-toggle-status').forEach(btn => {
|
||||||
|
btn.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
const id = btn.dataset.id
|
||||||
|
const newStatus = btn.dataset.status
|
||||||
|
this.toggleListingStatus(id, newStatus)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleListingStatus(id, newStatus) {
|
||||||
|
try {
|
||||||
|
await directus.updateListing(id, { status: newStatus })
|
||||||
|
const listing = this.listings.find(l => l.id === id)
|
||||||
|
if (listing) {
|
||||||
|
listing.status = newStatus
|
||||||
|
this.updateContent()
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to toggle listing status:', err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,6 +326,24 @@ style.textContent = /* css */`
|
|||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page-my-listings .btn-toggle-status {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--space-xs) var(--space-sm);
|
||||||
|
margin-top: var(--space-xs);
|
||||||
|
background: var(--color-bg-secondary);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
page-my-listings .btn-toggle-status:hover {
|
||||||
|
background: var(--color-bg-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
page-my-listings .empty-state {
|
page-my-listings .empty-state {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -422,6 +422,8 @@ class DirectusService {
|
|||||||
'shipping',
|
'shipping',
|
||||||
'shipping_cost',
|
'shipping_cost',
|
||||||
'views',
|
'views',
|
||||||
|
'paid_at',
|
||||||
|
'payment_status',
|
||||||
'expires_at',
|
'expires_at',
|
||||||
'date_created',
|
'date_created',
|
||||||
'user_created',
|
'user_created',
|
||||||
|
|||||||
@@ -69,6 +69,12 @@ class ListingsService {
|
|||||||
search: filters.search
|
search: filters.search
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isPaidAndActive(listing) {
|
||||||
|
return listing.paid_at
|
||||||
|
&& listing.expires_at
|
||||||
|
&& new Date(listing.expires_at) > new Date()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const listingsService = new ListingsService()
|
export const listingsService = new ListingsService()
|
||||||
|
|||||||
@@ -149,7 +149,8 @@
|
|||||||
"publishFailed": "Veröffentlichung fehlgeschlagen. Bitte versuche es erneut.",
|
"publishFailed": "Veröffentlichung fehlgeschlagen. Bitte versuche es erneut.",
|
||||||
"invalidMoneroAddress": "Ungültige Monero-Adresse. Bitte prüfe das Format.",
|
"invalidMoneroAddress": "Ungültige Monero-Adresse. Bitte prüfe das Format.",
|
||||||
"draftRestored": "Entwurf wiederhergestellt",
|
"draftRestored": "Entwurf wiederhergestellt",
|
||||||
"clearDraft": "Verwerfen"
|
"clearDraft": "Verwerfen",
|
||||||
|
"paymentExpired": "Die bezahlte Laufzeit ist abgelaufen. Bitte erneut bezahlen."
|
||||||
},
|
},
|
||||||
"notFound": {
|
"notFound": {
|
||||||
"title": "Seite nicht gefunden",
|
"title": "Seite nicht gefunden",
|
||||||
@@ -220,8 +221,13 @@
|
|||||||
"archived": "Archiviert",
|
"archived": "Archiviert",
|
||||||
"processing": "Ausstehend",
|
"processing": "Ausstehend",
|
||||||
"published": "Veröffentlicht",
|
"published": "Veröffentlicht",
|
||||||
"expired": "Abgelaufen"
|
"expired": "Abgelaufen",
|
||||||
}
|
"unpublished": "Deaktiviert"
|
||||||
|
},
|
||||||
|
"unpublish": "Deaktivieren",
|
||||||
|
"republish": "Aktivieren",
|
||||||
|
"unpublished": "Anzeige deaktiviert",
|
||||||
|
"republished": "Anzeige wieder aktiviert"
|
||||||
},
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"title": "Nachrichten",
|
"title": "Nachrichten",
|
||||||
|
|||||||
@@ -149,7 +149,8 @@
|
|||||||
"publishFailed": "Publishing failed. Please try again.",
|
"publishFailed": "Publishing failed. Please try again.",
|
||||||
"invalidMoneroAddress": "Invalid Monero address. Please check the format.",
|
"invalidMoneroAddress": "Invalid Monero address. Please check the format.",
|
||||||
"draftRestored": "Draft restored",
|
"draftRestored": "Draft restored",
|
||||||
"clearDraft": "Discard"
|
"clearDraft": "Discard",
|
||||||
|
"paymentExpired": "The paid period has expired. Please pay again."
|
||||||
},
|
},
|
||||||
"notFound": {
|
"notFound": {
|
||||||
"title": "Page Not Found",
|
"title": "Page Not Found",
|
||||||
@@ -220,8 +221,13 @@
|
|||||||
"archived": "Archived",
|
"archived": "Archived",
|
||||||
"processing": "Pending",
|
"processing": "Pending",
|
||||||
"published": "Published",
|
"published": "Published",
|
||||||
"expired": "Expired"
|
"expired": "Expired",
|
||||||
}
|
"unpublished": "Unpublished"
|
||||||
|
},
|
||||||
|
"unpublish": "Unpublish",
|
||||||
|
"republish": "Republish",
|
||||||
|
"unpublished": "Listing unpublished",
|
||||||
|
"republished": "Listing republished"
|
||||||
},
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"title": "Messages",
|
"title": "Messages",
|
||||||
|
|||||||
@@ -149,7 +149,8 @@
|
|||||||
"publishFailed": "Error al publicar. Inténtalo de nuevo.",
|
"publishFailed": "Error al publicar. Inténtalo de nuevo.",
|
||||||
"invalidMoneroAddress": "Dirección Monero no válida. Comprueba el formato.",
|
"invalidMoneroAddress": "Dirección Monero no válida. Comprueba el formato.",
|
||||||
"draftRestored": "Borrador restaurado",
|
"draftRestored": "Borrador restaurado",
|
||||||
"clearDraft": "Descartar"
|
"clearDraft": "Descartar",
|
||||||
|
"paymentExpired": "El período pagado ha expirado. Por favor, pague de nuevo."
|
||||||
},
|
},
|
||||||
"notFound": {
|
"notFound": {
|
||||||
"title": "Página no encontrada",
|
"title": "Página no encontrada",
|
||||||
@@ -220,8 +221,13 @@
|
|||||||
"archived": "Archivado",
|
"archived": "Archivado",
|
||||||
"processing": "Pendiente",
|
"processing": "Pendiente",
|
||||||
"published": "Publicado",
|
"published": "Publicado",
|
||||||
"expired": "Caducado"
|
"expired": "Caducado",
|
||||||
}
|
"unpublished": "Desactivado"
|
||||||
|
},
|
||||||
|
"unpublish": "Desactivar",
|
||||||
|
"republish": "Reactivar",
|
||||||
|
"unpublished": "Anuncio desactivado",
|
||||||
|
"republished": "Anuncio reactivado"
|
||||||
},
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"title": "Mensajes",
|
"title": "Mensajes",
|
||||||
|
|||||||
@@ -149,7 +149,8 @@
|
|||||||
"publishFailed": "La publication a échoué. Veuillez réessayer.",
|
"publishFailed": "La publication a échoué. Veuillez réessayer.",
|
||||||
"invalidMoneroAddress": "Adresse Monero invalide. Veuillez vérifier le format.",
|
"invalidMoneroAddress": "Adresse Monero invalide. Veuillez vérifier le format.",
|
||||||
"draftRestored": "Brouillon restauré",
|
"draftRestored": "Brouillon restauré",
|
||||||
"clearDraft": "Supprimer"
|
"clearDraft": "Supprimer",
|
||||||
|
"paymentExpired": "La période payée a expiré. Veuillez payer à nouveau."
|
||||||
},
|
},
|
||||||
"notFound": {
|
"notFound": {
|
||||||
"title": "Page non trouvée",
|
"title": "Page non trouvée",
|
||||||
@@ -220,8 +221,13 @@
|
|||||||
"archived": "Archivé",
|
"archived": "Archivé",
|
||||||
"processing": "En attente",
|
"processing": "En attente",
|
||||||
"published": "Publié",
|
"published": "Publié",
|
||||||
"expired": "Expiré"
|
"expired": "Expiré",
|
||||||
}
|
"unpublished": "Désactivé"
|
||||||
|
},
|
||||||
|
"unpublish": "Désactiver",
|
||||||
|
"republish": "Réactiver",
|
||||||
|
"unpublished": "Annonce désactivée",
|
||||||
|
"republished": "Annonce réactivée"
|
||||||
},
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"title": "Messages",
|
"title": "Messages",
|
||||||
|
|||||||
@@ -149,7 +149,8 @@
|
|||||||
"publishFailed": "Pubblicazione fallita. Riprova.",
|
"publishFailed": "Pubblicazione fallita. Riprova.",
|
||||||
"invalidMoneroAddress": "Indirizzo Monero non valido. Controlla il formato.",
|
"invalidMoneroAddress": "Indirizzo Monero non valido. Controlla il formato.",
|
||||||
"draftRestored": "Bozza ripristinata",
|
"draftRestored": "Bozza ripristinata",
|
||||||
"clearDraft": "Elimina"
|
"clearDraft": "Elimina",
|
||||||
|
"paymentExpired": "Il periodo pagato è scaduto. Si prega di pagare di nuovo."
|
||||||
},
|
},
|
||||||
"notFound": {
|
"notFound": {
|
||||||
"title": "Pagina non trovata",
|
"title": "Pagina non trovata",
|
||||||
@@ -220,8 +221,13 @@
|
|||||||
"archived": "Archiviato",
|
"archived": "Archiviato",
|
||||||
"processing": "In attesa",
|
"processing": "In attesa",
|
||||||
"published": "Pubblicato",
|
"published": "Pubblicato",
|
||||||
"expired": "Scaduto"
|
"expired": "Scaduto",
|
||||||
}
|
"unpublished": "Disattivato"
|
||||||
|
},
|
||||||
|
"unpublish": "Disattiva",
|
||||||
|
"republish": "Riattiva",
|
||||||
|
"unpublished": "Annuncio disattivato",
|
||||||
|
"republished": "Annuncio riattivato"
|
||||||
},
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"title": "Messaggi",
|
"title": "Messaggi",
|
||||||
|
|||||||
@@ -149,7 +149,8 @@
|
|||||||
"publishFailed": "Falha ao publicar. Por favor, tente novamente.",
|
"publishFailed": "Falha ao publicar. Por favor, tente novamente.",
|
||||||
"invalidMoneroAddress": "Endereço Monero inválido. Verifique o formato.",
|
"invalidMoneroAddress": "Endereço Monero inválido. Verifique o formato.",
|
||||||
"draftRestored": "Rascunho restaurado",
|
"draftRestored": "Rascunho restaurado",
|
||||||
"clearDraft": "Descartar"
|
"clearDraft": "Descartar",
|
||||||
|
"paymentExpired": "O período pago expirou. Por favor, pague novamente."
|
||||||
},
|
},
|
||||||
"notFound": {
|
"notFound": {
|
||||||
"title": "Página Não Encontrada",
|
"title": "Página Não Encontrada",
|
||||||
@@ -220,8 +221,13 @@
|
|||||||
"archived": "Arquivado",
|
"archived": "Arquivado",
|
||||||
"processing": "Pendente",
|
"processing": "Pendente",
|
||||||
"published": "Publicado",
|
"published": "Publicado",
|
||||||
"expired": "Expirado"
|
"expired": "Expirado",
|
||||||
}
|
"unpublished": "Desativado"
|
||||||
|
},
|
||||||
|
"unpublish": "Desativar",
|
||||||
|
"republish": "Reativar",
|
||||||
|
"unpublished": "Anúncio desativado",
|
||||||
|
"republished": "Anúncio reativado"
|
||||||
},
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"title": "Mensagens",
|
"title": "Mensagens",
|
||||||
|
|||||||
@@ -149,7 +149,8 @@
|
|||||||
"publishFailed": "Публикация не удалась. Попробуйте снова.",
|
"publishFailed": "Публикация не удалась. Попробуйте снова.",
|
||||||
"invalidMoneroAddress": "Неверный Monero-адрес. Проверьте формат.",
|
"invalidMoneroAddress": "Неверный Monero-адрес. Проверьте формат.",
|
||||||
"draftRestored": "Черновик восстановлен",
|
"draftRestored": "Черновик восстановлен",
|
||||||
"clearDraft": "Удалить черновик"
|
"clearDraft": "Удалить черновик",
|
||||||
|
"paymentExpired": "Оплаченный период истёк. Пожалуйста, оплатите снова."
|
||||||
},
|
},
|
||||||
"notFound": {
|
"notFound": {
|
||||||
"title": "Страница не найдена",
|
"title": "Страница не найдена",
|
||||||
@@ -220,8 +221,13 @@
|
|||||||
"archived": "В архиве",
|
"archived": "В архиве",
|
||||||
"processing": "На рассмотрении",
|
"processing": "На рассмотрении",
|
||||||
"published": "Опубликовано",
|
"published": "Опубликовано",
|
||||||
"expired": "Истекло"
|
"expired": "Истекло",
|
||||||
}
|
"unpublished": "Снято"
|
||||||
|
},
|
||||||
|
"unpublish": "Снять с публикации",
|
||||||
|
"republish": "Опубликовать снова",
|
||||||
|
"unpublished": "Объявление снято с публикации",
|
||||||
|
"republished": "Объявление опубликовано снова"
|
||||||
},
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"title": "Сообщения",
|
"title": "Сообщения",
|
||||||
|
|||||||
Reference in New Issue
Block a user