FIX - complete CityMarkt sharing and browser flows

This commit is contained in:
smx.pusha
2026-08-12 18:49:32 +02:00
parent 1df5202360
commit 48c7f1a551
5 changed files with 371 additions and 80 deletions
+11
View File
@@ -2739,6 +2739,17 @@ const defaultLocales: LocaleTree = {
cityMarktShare: 'Share to Local Pages',
cityMarktShared: 'Shared to Local Pages.',
cityMarktShareHint: 'One CityMarkt share per day',
cityMarktAlreadyShared: 'Already published',
cityMarktOpenSharedHint: 'Open the Local Pages post',
cityMarktAppMissing: 'Local Pages is not installed',
cityMarktInstallHint: 'Install Local Pages to share this listing',
cityMarktAccountMissing: 'Local Pages profile required',
cityMarktAccountHint: 'Create a Local Pages profile before sharing',
cityMarktComposeTitle: 'Share CityMarkt listing',
cityMarktComposeNavTitle: 'Share listing',
cityMarktComposeHint:
'Review the listing and publish it when you are ready.',
cityMarktPhotosHint: 'The CityMarkt listing photos will be included.',
authErrors: {
invalid_email: 'Enter a valid 332 character iFruit address.',
invalid_password: 'Use a password between 6 and 64 characters.',
+128 -50
View File
@@ -6,6 +6,7 @@ import {
Camera,
CarFront,
ChevronRight,
CircleCheck,
CirclePlus,
Gift,
Hammer,
@@ -34,7 +35,6 @@ import {
import {
kBadge,
kButton,
kFab,
kGlass,
kIcon,
kNavbar,
@@ -51,6 +51,7 @@ import CityMarktSelect from '@/components/citymarkt/CityMarktSelect.vue'
import CityMarktGallery from '@/components/citymarkt/CityMarktGallery.vue'
import CityMarktOfferCard from '@/components/citymarkt/CityMarktOfferCard.vue'
import { useAccountStore } from '@/stores/account'
import { useAppStoreStore } from '@/stores/app-store'
import { useEasyShareStore } from '@/stores/easyshare'
import { useMarketplaceStore } from '@/stores/marketplace'
import { useMessageMediaStore } from '@/stores/messageMedia'
@@ -71,12 +72,6 @@ import type {
} from '@/types/marketplace'
import type { PhoneMedia } from '@/types/media'
const glassActionColors = {
bgIos: 'bg-ios-light-glass/75 dark:bg-ios-dark-glass/75',
activeBgIos: 'active:bg-white/90 dark:active:bg-white/20',
textIos: 'text-black/80 dark:text-white/80',
}
type Tab = 'discover' | 'search' | 'sell' | 'inbox' | 'profile'
type Screen = 'main' | 'detail' | 'sell' | 'chat' | 'report'
type ChatTimelineItem =
@@ -113,9 +108,14 @@ type MediaContext = {
type ProfileMediaContext = { draft: MarketplaceProfileDraft }
const phone = usePhoneStore()
const detailActionColors = computed(() => ({
bgIos: phone.isDarkMode ? 'bg-ios-dark-glass' : 'bg-ios-light-glass',
shadowIos: phone.isDarkMode ? 'shadow-ios-dark-glass' : 'shadow-ios-light-glass',
}))
const route = useRoute()
const router = useRouter()
const account = useAccountStore()
const appStore = useAppStoreStore()
const easyShare = useEasyShareStore()
const marketplace = useMarketplaceStore()
const messageMedia = useMessageMediaStore()
@@ -141,7 +141,6 @@ const offerAmount = ref('')
const offerPanelOpen = ref(false)
const offerSubmitting = ref(false)
const feedback = ref('')
const pagesSharePendingId = ref<string | null>(null)
const sellStep = ref(1)
const submitting = ref(false)
const selectedPhotoIds = ref<string[]>([])
@@ -241,6 +240,11 @@ const tabs = [
] as const
const isAuthenticated = computed(() => account.email !== '')
const localPagesInstalled = computed(
() =>
appStore.isInstalled('local-pages') &&
!appStore.homeLayout.hidden.includes('local-pages'),
)
const canSaveProfile = computed(() => {
const nameLength = profileDraft.value.displayName.trim().length
return nameLength >= 2 && nameLength <= 40 && profileDraft.value.bio.trim().length <= 160
@@ -371,21 +375,65 @@ function setFeedback(key: string): void {
async function shareToLocalPages(listingId?: string): Promise<void> {
const id = listingId ?? selectedListing.value?.id
if (!id || pagesSharePendingId.value) return
pagesSharePendingId.value = id
const response = await pages.shareCityMarkt(id)
pagesSharePendingId.value = null
if (response.success && response.data?.id) {
if (!id) return
if (!localPagesInstalled.value) {
setFeedback('Apps.localPages.cityMarktAppMissing')
return
}
if (!pages.profile?.exists) {
setFeedback('Apps.localPages.cityMarktAccountMissing')
return
}
const existingPost = pages.ownItems.find(
(item) => item.citymarkt_listing_id === id,
)
if (existingPost) {
await router.push({
path: '/apps/local-pages',
query: {
easyShareId: response.data.id,
easyShareId: existingPost.id,
easyShareKind: 'post',
},
})
return
}
setFeedback(`Apps.localPages.errors.${response.error ?? 'default'}`)
await router.push({
path: '/apps/local-pages',
query: {
cityMarktListingId: id,
compose: '1',
},
})
}
function localPagesShareLabel(listingId: string): string {
if (!localPagesInstalled.value)
return phone.t('Apps.localPages.cityMarktAppMissing')
if (!pages.profile?.exists)
return phone.t('Apps.localPages.cityMarktAccountMissing')
return phone.t(
hasLocalPagesPost(listingId)
? 'Apps.localPages.cityMarktAlreadyShared'
: 'Apps.localPages.cityMarktShare',
)
}
function localPagesShareHint(listingId: string): string {
if (!localPagesInstalled.value)
return phone.t('Apps.localPages.cityMarktInstallHint')
if (!pages.profile?.exists)
return phone.t('Apps.localPages.cityMarktAccountHint')
return phone.t(
hasLocalPagesPost(listingId)
? 'Apps.localPages.cityMarktOpenSharedHint'
: 'Apps.localPages.cityMarktShareHint',
)
}
function hasLocalPagesPost(listingId: string): boolean {
return pages.ownItems.some(
(item) => item.citymarkt_listing_id === listingId,
)
}
function shareListing(): void {
@@ -870,7 +918,10 @@ onMounted(async () => {
screen.value = 'sell'
}
if (isAuthenticated.value) {
await marketplace.loadProfile()
await Promise.all([
marketplace.loadProfile(),
localPagesInstalled.value ? pages.loadProfile() : Promise.resolve(false),
])
if (!marketplace.profile?.exists) {
if (!profileSelection) syncProfileDraft()
profileEditing.value = true
@@ -1196,9 +1247,11 @@ onMounted(async () => {
v-if="profileMode === 'own' && (item.status === 'active' || item.status === 'reserved')"
class="citymarkt-profile-listing__share"
type="button"
:disabled="pagesSharePendingId !== null"
@click="shareToLocalPages(item.id)"
><Share2 :size="15" />{{ phone.t('Apps.localPages.cityMarktShare') }}</button
><CircleCheck
v-if="hasLocalPagesPost(item.id)"
:size="15"
/><Share2 v-else :size="15" />{{ localPagesShareLabel(item.id) }}</button
></k-glass>
</div>
</template>
@@ -1211,39 +1264,40 @@ onMounted(async () => {
class="citymarkt__detail"
>
<div class="citymarkt__glass-actions">
<k-fab
<k-glass
component="button"
type="button"
:colors="glassActionColors"
class="citymarkt-detail-action"
:colors="detailActionColors"
:aria-label="phone.t('Common.back')"
@click="screen = 'main'"
><template #icon><ArrowLeft :size="19" /></template
></k-fab>
><ArrowLeft :size="19" /></k-glass
>
<div>
<k-fab
<k-glass
v-if="!selectedListing.is_owner"
component="button"
type="button"
:colors="glassActionColors"
class="citymarkt-detail-action"
:colors="detailActionColors"
:class="{ active: selectedListing.is_favorite }"
:aria-label="phone.t(selectedListing.is_favorite ? 'Apps.citymarkt.removeFavorite' : 'Apps.citymarkt.addFavorite')"
@click="toggleListingFavorite(selectedListing)"
><template #icon
><Heart
:size="19"
:fill="
selectedListing.is_favorite ? 'currentColor' : 'none'
" /></template
></k-fab>
<k-fab
><Heart
:size="19"
:fill="selectedListing.is_favorite ? 'currentColor' : 'none'"
/></k-glass
>
<k-glass
v-if="!selectedListing.is_owner"
component="button"
type="button"
:colors="glassActionColors"
class="citymarkt-detail-action"
:colors="detailActionColors"
:aria-label="phone.t('Apps.citymarkt.reportListing')"
@click="screen = 'report'"
><template #icon><MoreHorizontal :size="20" /></template
></k-fab>
><MoreHorizontal :size="20" /></k-glass
>
</div>
</div>
<CityMarktGallery
@@ -1306,14 +1360,14 @@ onMounted(async () => {
"
class="citymarkt__pages-share"
type="button"
:disabled="pagesSharePendingId !== null"
@click="shareToLocalPages()"
>
<Share2 :size="17" /><span
><strong>{{ phone.t('Apps.localPages.cityMarktShare') }}</strong
><small>{{
phone.t('Apps.localPages.cityMarktShareHint')
}}</small></span
<CircleCheck
v-if="hasLocalPagesPost(selectedListing.id)"
:size="17"
/><Share2 v-else :size="17" /><span
><strong>{{ localPagesShareLabel(selectedListing.id) }}</strong
><small>{{ localPagesShareHint(selectedListing.id) }}</small></span
>
</button>
<div class="citymarkt__owner-actions">
@@ -1922,10 +1976,17 @@ onMounted(async () => {
gap: 6px;
}
.citymarkt-detail-action {
box-shadow:
inset 0 0 0 0.5px #ffffff26,
inset 0 1px 0 #ffffff1a,
0 6px 16px #0007 !important;
width: 44px;
height: 44px;
padding: 0;
border: 0;
border-radius: 50%;
overflow: hidden;
display: grid;
place-items: center;
}
.citymarkt-detail-action.active {
color: var(--yellow);
}
.citymarkt__glass-list > .k-glass {
width: 100%;
@@ -1975,8 +2036,10 @@ onMounted(async () => {
color: inherit;
}
.citymarkt-listing-card {
isolation: isolate;
min-width: 0;
border-radius: 16px;
display: grid !important;
overflow: hidden;
transition:
filter 0.18s ease,
@@ -1987,6 +2050,7 @@ onMounted(async () => {
transform: translateY(-1px);
}
.citymarkt-listing-card > .citymarkt-listing-card__open {
grid-area: 1 / 1;
width: 100%;
min-width: 0;
padding: 0;
@@ -1997,25 +2061,35 @@ onMounted(async () => {
text-align: left;
}
.citymarkt-listing-card__favorite {
position: absolute;
position: relative;
z-index: 2;
top: 7px;
right: 7px;
grid-area: 1 / 1;
align-self: start;
justify-self: end;
width: 30px;
height: 30px;
margin: 7px;
padding: 0;
border: 1px solid #ffffff12;
border-radius: 50%;
display: grid;
display: grid !important;
place-items: center;
background: #161714d9;
color: #d0d1cb;
color: #f8f8f4;
box-shadow: 0 3px 10px #0005;
opacity: 1 !important;
visibility: visible !important;
pointer-events: auto !important;
transition:
color 0.16s ease,
background 0.16s ease,
transform 0.16s ease;
}
.citymarkt-listing-card__favorite > svg {
display: block !important;
opacity: 1 !important;
visibility: visible !important;
}
.citymarkt-listing-card__favorite:hover {
background: #242520e6;
color: #f5f5ef;
@@ -2081,6 +2155,10 @@ onMounted(async () => {
width: 116px;
height: 116px;
}
.citymarkt__grid--wide .citymarkt-listing-card__favorite {
justify-self: start;
margin-left: 79px;
}
.citymarkt__grid--wide .citymarkt-listing-card__body {
min-height: 116px;
padding: 12px;
+126 -23
View File
@@ -40,8 +40,10 @@ import CityMarktGallery from '@/components/citymarkt/CityMarktGallery.vue'
import { useAccountStore } from '@/stores/account'
import { useMessageMediaStore } from '@/stores/messageMedia'
import { useEasyShareStore } from '@/stores/easyshare'
import { useMarketplaceStore } from '@/stores/marketplace'
import { usePagesStore } from '@/stores/pages'
import { usePhoneStore } from '@/stores/phone'
import type { MarketplaceListing } from '@/types/marketplace'
import type { PagesCategory, PagesPost, PagesProfileDraft } from '@/types/pages'
import type { PhoneMedia } from '@/types/media'
import {
@@ -68,6 +70,7 @@ const phone = usePhoneStore()
const account = useAccountStore()
const messageMedia = useMessageMediaStore()
const easyShare = useEasyShareStore()
const marketplace = useMarketplaceStore()
const pages = usePagesStore()
const route = useRoute()
const router = useRouter()
@@ -91,6 +94,8 @@ const profilePending = ref(false)
const profileDraft = ref<PagesProfileDraft>({ avatarMediaId: 0, bio: '', handle: '' })
const selectedProfilePhoto = ref<PhoneMedia | null>(null)
const pickedPhotos = ref<SelectedPhoto[]>([])
const cityMarktListing = ref<MarketplaceListing | null>(null)
const cityMarktListingId = ref<string | null>(null)
const draft = ref<ComposeDraft>({
body: '',
category: 'recommendation' as Exclude<PagesCategory, 'citymarkt'>,
@@ -274,7 +279,14 @@ function syncProfileDraft(): void {
function ensureBrowserProfile(): void {
const onboardingScenario = new URLSearchParams(window.location.search).get('testScenario')
if (!import.meta.env.DEV || onboardingScenario === 'local-pages-onboarding' || pages.profile) return
if (
!import.meta.env.DEV ||
['local-pages-onboarding', 'citymarkt-local-pages-account-missing'].includes(
onboardingScenario ?? '',
) ||
pages.profile
)
return
const handle = account.email.split('@')[0].toLowerCase()
pages.profile = {
avatar_media_id: null,
@@ -387,24 +399,45 @@ async function publish(): Promise<void> {
showFeedback('Apps.localPages.errors.invalid_post')
return
}
const response = await pages.create({
body: draft.value.body.trim(),
category: draft.value.category,
district: draft.value.district,
images: draft.value.images.map((id) => ({ id })),
title: draft.value.title.trim(),
})
const response = cityMarktListingId.value
? await pages.shareCityMarkt(cityMarktListingId.value)
: await pages.create({
body: draft.value.body.trim(),
category: draft.value.category,
district: draft.value.district,
images: draft.value.images.map((id) => ({ id })),
title: draft.value.title.trim(),
})
if (!response.success) {
showFeedback(`Apps.localPages.errors.${response.error ?? 'default'}`)
return
}
draft.value = { body: '', category: 'recommendation', district: 'los_santos', images: [], title: '' }
pickedPhotos.value = []
cityMarktListing.value = null
cityMarktListingId.value = null
tab.value = 'feed'
screen.value = 'main'
await router.replace('/apps/local-pages')
await Promise.all([pages.load(), pages.loadProfile()])
showFeedback('Apps.localPages.published')
}
function closeCompose(): void {
draft.value = {
body: '',
category: 'recommendation',
district: 'los_santos',
images: [],
title: '',
}
pickedPhotos.value = []
cityMarktListing.value = null
cityMarktListingId.value = null
screen.value = 'main'
void router.replace('/apps/local-pages')
}
async function react(kind: 'like' | 'save'): Promise<void> {
if (!selected.value || !isAuthenticated.value) {
showFeedback('Apps.localPages.errors.not_authenticated')
@@ -511,7 +544,34 @@ onMounted(async () => {
screen.value = 'main'
}
onboardingReady.value = true
if (route.query.compose === '1' && pages.profile?.exists) screen.value = 'compose'
if (route.query.compose === '1' && pages.profile?.exists) {
const listingId = String(route.query.cityMarktListingId ?? '')
if (listingId) {
const response = await marketplace.get(listingId)
if (
response.success &&
response.data?.is_owner &&
['active', 'reserved'].includes(response.data.status)
) {
cityMarktListing.value = response.data
cityMarktListingId.value = response.data.id
draft.value = {
body: response.data.description,
category: 'recommendation',
district: response.data.district ?? 'los_santos',
images: response.data.images.map((image) => image.media_id),
title: response.data.title,
}
pickedPhotos.value = response.data.images.map((image) => ({
background: image.gradient,
id: image.media_id,
}))
} else {
showFeedback('Apps.localPages.errors.citymarkt_not_found')
}
}
if (!listingId || cityMarktListingId.value) screen.value = 'compose'
}
const easyShareId = String(route.query.easyShareId ?? '')
if (pages.profile?.exists && easyShareId && route.query.easyShareKind === 'post') {
const response = await pages.get(easyShareId)
@@ -752,18 +812,22 @@ onMounted(async () => {
</k-glass>
</section>
<section v-else class="pages__compose">
<section
v-else
class="pages__compose"
:class="{ 'pages__compose--citymarkt': cityMarktListing }"
>
<k-navbar
class="pages-create-navbar"
center-title
left-class="pages-create-action pages-create-action--close !w-11 !min-w-11 !max-w-11 !h-11 !p-0 !rounded-full"
left-class="pages-create-action pages-create-action--close !min-w-[58px] !h-11 !p-0 !rounded-full"
right-class="pages-create-action pages-create-action--publish !min-w-[58px] !h-11 !p-0 !rounded-full"
:title="phone.t('Apps.localPages.shareWithCity')"
:subtitle="phone.t('Apps.localPages.newPost')"
:title="phone.t(cityMarktListing ? 'Apps.localPages.cityMarktComposeNavTitle' : 'Apps.localPages.shareWithCity')"
:subtitle="phone.t(cityMarktListing ? 'Apps.localPages.categories.citymarkt' : 'Apps.localPages.newPost')"
>
<template #left>
<button class="pages-create-close" type="button" :aria-label="phone.t('Common.close')" @click="screen = 'main'">
<X :size="20" />
<button class="pages-create-close" type="button" :aria-label="phone.t('Common.close')" @click="closeCompose">
{{ phone.t('Common.close') }}
</button>
</template>
<template #right>
@@ -773,13 +837,20 @@ onMounted(async () => {
</template>
</k-navbar>
<div class="pages__compose-scroll">
<k-glass v-if="cityMarktListing" class="pages__citymarkt-source">
<Store :size="19" />
<span
><strong>{{ phone.t('Apps.localPages.cityMarktComposeTitle') }}</strong
><small>{{ phone.t('Apps.localPages.cityMarktComposeHint') }}</small></span
>
</k-glass>
<label>{{ phone.t('Apps.localPages.title') }} <span :class="{ valid: draft.title.trim().length >= 5 }">{{ draft.title.trim().length }}/80 · {{ phone.t('Apps.citymarkt.minimumCharacters', { minimum: '5' }) }}</span><k-glass class="pages__field-glass"><input v-model="draft.title" maxlength="80" :placeholder="phone.t('Apps.localPages.titlePlaceholder')" /></k-glass></label>
<label>{{ phone.t('Apps.localPages.body') }} <span :class="{ valid: draft.body.trim().length >= 10 }">{{ draft.body.trim().length }}/1500 · {{ phone.t('Apps.citymarkt.minimumCharacters', { minimum: '10' }) }}</span><k-glass class="pages__field-glass pages__field-glass--textarea"><textarea v-model="draft.body" maxlength="1500" :placeholder="phone.t('Apps.localPages.bodyPlaceholder')" /></k-glass></label>
<div class="pages__form-row"><label>{{ phone.t('Apps.localPages.category') }}<CityMarktSelect :model-value="draft.category" :options="composeCategoryOptions" @change="(value) => draft.category = value as typeof draft.category" /></label><label>{{ phone.t('Apps.localPages.location') }}<CityMarktSelect :model-value="draft.district" :options="districtOptions" @change="(value) => draft.district = value" /></label></div>
<section class="pages__photos">
<ImagePlus :size="30" />
<h2>{{ phone.t('Apps.citymarkt.addPhotos') }}</h2>
<p>{{ phone.t('Apps.citymarkt.addPhotosBody') }}</p>
<p>{{ phone.t(cityMarktListing ? 'Apps.localPages.cityMarktPhotosHint' : 'Apps.citymarkt.addPhotosBody') }}</p>
<div class="pages__photo-actions">
<k-glass><button type="button" @click="openMediaApp('photos')">
<span><Images :size="20" /></span>
@@ -981,9 +1052,7 @@ onMounted(async () => {
height: 44px;
border-radius: 9999px;
}
.pages-create-action--close {
width: 44px;
}
.pages-create-action--close,
.pages-create-action--publish {
min-width: 58px;
}
@@ -997,10 +1066,7 @@ onMounted(async () => {
background: transparent;
color: inherit;
}
.pages-create-close {
display: grid;
place-items: center;
}
.pages-create-close,
.pages-create-publish {
min-width: 58px;
padding: 0 13px;
@@ -1021,6 +1087,43 @@ onMounted(async () => {
height: auto;
padding-bottom: 35px;
}
.pages__citymarkt-source {
margin-bottom: 14px;
padding: 11px 12px;
display: flex;
align-items: center;
gap: 9px;
border-radius: 13px;
color: var(--yellow);
}
.pages__citymarkt-source span {
min-width: 0;
}
.pages__citymarkt-source strong,
.pages__citymarkt-source small {
display: block;
}
.pages__citymarkt-source strong {
font-size: 12px;
}
.pages__citymarkt-source small {
margin-top: 2px;
color: var(--muted);
font-size: 10px;
line-height: 1.35;
}
.pages__compose--citymarkt .pages__compose-scroll > label input,
.pages__compose--citymarkt .pages__compose-scroll > label textarea {
pointer-events: none;
}
.pages__compose--citymarkt .pages__form-row {
grid-template-columns: minmax(0, 1fr);
}
.pages__compose--citymarkt .pages__form-row > label:first-child,
.pages__compose--citymarkt .pages__photo-actions,
.pages__compose--citymarkt .pages__selected-strip {
display: none;
}
.pages__field-glass {
min-height: 44px;
margin-top: 5px;
+102 -7
View File
@@ -1878,6 +1878,17 @@ const marketplaceListings = [
],
},
]
let marketplaceProfile = {
avatar_media_id: 1,
avatar_url: 'https://picsum.photos/seed/citymarkt-demo-avatar/240/240',
bio: 'Fair prices, quick replies, and meetups anywhere in Los Santos.',
display_name: 'Skyline Deals',
email: 'demo@ifruit.com',
exists: true,
listing_count: marketplaceListings.filter(
(listing) => listing.seller_account_id === 1,
).length,
}
let linkedAccount = {
devices: accountDevices,
email: 'demo@ifruit.com',
@@ -2454,6 +2465,32 @@ const pagesPosts = [
images: [],
},
]
const cityMarktSharedScenarioPost = {
id: 'pages-citymarkt-owner-demo',
account_id: 1,
author_name: 'demo',
source_type: 'citymarkt',
citymarkt_listing_id: '81bc9d37-20e1-4d8a-82f8-f4b85f77cf04',
title: 'Complete mechanic tool set',
body: 'Complete mechanic tool set with trolley, sockets and diagnostic equipment. Everything is clean and ready for work.',
category: 'citymarkt',
district: 'south_los_santos',
created_at: Date.parse('2026-08-06T13:10:00Z'),
like_count: 4,
images: [
{
media_id: 'capture-tools',
gradient: 'linear-gradient(135deg, #ffc75f, #f96d80 48%, #4b4453)',
sort_order: 1,
},
],
}
function pagesPostsForScenario(testScenario) {
return testScenario === 'citymarkt-shared'
? [cityMarktSharedScenarioPost, ...pagesPosts]
: pagesPosts
}
const pagesReactions = [
{ post_id: 'pages-1', account_id: 1, kind: 'like' },
{ post_id: 'pages-3', account_id: 1, kind: 'save' },
@@ -6965,7 +7002,30 @@ app.post('/api/:endpoint', (request, response) => {
data: {
account: testScenario === 'feather-login' ? null : linkedAccount,
device: {
data: deviceData,
data:
testScenario.startsWith('citymarkt-')
? {
...deviceData,
apps: {
...deviceData.apps,
payload: {
...deviceData.apps.payload,
homeLayout: {
dock: [],
grid:
testScenario === 'citymarkt-local-pages-missing'
? []
: ['local-pages'],
hidden:
testScenario === 'citymarkt-local-pages-missing'
? ['local-pages']
: [],
version: 3,
},
},
},
}
: deviceData,
imei: '356938035643809',
name: 'Personal iFruit Phone',
sim: {
@@ -7475,7 +7535,7 @@ app.post('/api/:endpoint', (request, response) => {
}
if (endpoint === 'pages:list') {
const query = String(request.body.search ?? '').toLowerCase()
let items = pagesPosts
let items = pagesPostsForScenario(testScenario)
if (request.body.category && request.body.category !== 'all')
items = items.filter((item) => item.category === request.body.category)
if (query)
@@ -7498,7 +7558,9 @@ app.post('/api/:endpoint', (request, response) => {
return
}
if (endpoint === 'pages:get') {
const post = pagesPosts.find((item) => item.id === request.body.id)
const post = pagesPostsForScenario(testScenario).find(
(item) => item.id === request.body.id,
)
response.json(
post
? { success: true, data: pageView(post) }
@@ -7513,7 +7575,9 @@ app.post('/api/:endpoint', (request, response) => {
if (endpoint === 'pages:profile') {
const email = linkedAccount?.email ?? pagesProfile.email
const onboarding =
testScenario === 'local-pages-onboarding' && !pagesOnboardingCompleted
['local-pages-onboarding', 'citymarkt-local-pages-account-missing'].includes(
testScenario,
) && !pagesOnboardingCompleted
response.json({
success: true,
data: {
@@ -7523,7 +7587,9 @@ app.post('/api/:endpoint', (request, response) => {
email,
exists: onboarding ? false : pagesProfile.exists,
handle: onboarding ? email.split('@')[0] : pagesProfile.handle,
post_count: pagesPosts.filter((item) => item.account_id === 1).length,
post_count: pagesPostsForScenario(testScenario).filter(
(item) => item.account_id === 1,
).length,
},
})
return
@@ -7563,7 +7629,9 @@ app.post('/api/:endpoint', (request, response) => {
success: true,
data: {
hasMore: false,
items: pagesPosts.filter((item) => item.account_id === 1).map(pageView),
items: pagesPostsForScenario(testScenario)
.filter((item) => item.account_id === 1)
.map(pageView),
offset: 0,
},
})
@@ -7609,7 +7677,11 @@ app.post('/api/:endpoint', (request, response) => {
response.json({ success: false, error: 'citymarkt_not_found' })
return
}
if (pagesPosts.some((item) => item.citymarkt_listing_id === listing.id)) {
if (
pagesPostsForScenario(testScenario).some(
(item) => item.citymarkt_listing_id === listing.id,
)
) {
response.json({ success: false, error: 'citymarkt_already_shared' })
return
}
@@ -7709,6 +7781,29 @@ app.post('/api/:endpoint', (request, response) => {
response.json({ success: false, error: 'not_authenticated' })
return
}
if (endpoint === 'marketplace:profile') {
marketplaceProfile.listing_count = marketplaceListings.filter(
(listing) => listing.seller_account_id === 1,
).length
response.json({ success: true, data: marketplaceProfile })
return
}
if (endpoint === 'marketplace:profile-save') {
const avatarMediaId = Number(request.body.avatarMediaId)
const avatar = mockMedia.find(
(item) => item.id === avatarMediaId && item.mediaType === 'photo',
)
marketplaceProfile = {
...marketplaceProfile,
avatar_media_id: avatarMediaId || null,
avatar_url: avatar?.url ?? null,
bio: String(request.body.bio ?? '').trim(),
display_name: String(request.body.displayName ?? '').trim(),
exists: true,
}
response.json({ success: true, data: marketplaceProfile })
return
}
if (endpoint === 'marketplace:counts') {
response.json({
success: true,
+4
View File
@@ -1270,6 +1270,10 @@ Locales["en"] = {
title = "Title", body = "Your story", category = "Category", titlePlaceholder = "What should people know?", bodyPlaceholder = "Add details, a recommendation or directions...",
photos = "Photos", optional = "optional", camera = "Camera", gallery = "Gallery", photoLimit = "You can add up to six photos.",
cityMarktShare = "Share to Local Pages", cityMarktShared = "Shared to Local Pages.", cityMarktShareHint = "One CityMarkt share per day",
cityMarktAlreadyShared = "Already published", cityMarktOpenSharedHint = "Open the Local Pages post",
cityMarktAppMissing = "Local Pages is not installed", cityMarktInstallHint = "Install Local Pages to share this listing",
cityMarktAccountMissing = "Local Pages profile required", cityMarktAccountHint = "Create a Local Pages profile before sharing",
cityMarktComposeTitle = "Share CityMarkt listing", cityMarktComposeNavTitle = "Share listing", cityMarktComposeHint = "Review the listing and publish it when you are ready.", cityMarktPhotosHint = "The CityMarkt listing photos will be included.",
authErrors = { invalid_email = "Enter a valid 3-32 character iFruit address.", invalid_password = "Use a password between 6 and 64 characters.", invalid_credentials = "The iFruit address or password is incorrect.", email_taken = "This iFruit address is already registered.", password_mismatch = "The passwords do not match.", rate_limited = "Too many attempts. Try again shortly.", default = "The iFruit account request failed." },
errors = { profile_required = "Create your Local Pages profile first.", invalid_profile = "Check your username and bio.", invalid_profile_image = "Choose a valid photo from this phone.", profile_handle_taken = "This username is already taken.", invalid_post = "Add a title and a little more detail.", invalid_images = "Choose valid photos from this phone.", invalid_request = "This action is not valid.", post_not_found = "This post is no longer available.", citymarkt_not_found = "This CityMarkt listing is unavailable.", citymarkt_daily_limit = "You already shared a CityMarkt listing today.", citymarkt_already_shared = "This listing was already shared.", not_authenticated = "Sign in to iFruit first.", rate_limited = "Too many requests. Try again shortly.", request_failed = "The post could not be saved.", default = "Local Pages is temporarily unavailable." },
},