Skip to content

Commit 22a583a

Browse files
committed
listings page
1 parent 6733f60 commit 22a583a

File tree

14 files changed

+237
-44
lines changed

14 files changed

+237
-44
lines changed

components/Cards/VehicleCard.js

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import get from 'lodash/get'
55
import classNames from 'classnames'
66
import { LazyLoadImage } from 'react-lazy-load-image-component'
77
import { useTranslation } from 'react-i18next'
8+
import { useDispatch, useSelector } from 'react-redux'
89
import { bodyType as bodyTypes } from '~consts/vehicle'
910
import {
1011
FiMapPin,
@@ -13,9 +14,11 @@ import {
1314
FiDisc,
1415
} from 'react-icons/fi'
1516
import { GiCarWheel, GiGasPump, GiElectric } from 'react-icons/gi'
17+
import ListingsCreator from '../../store/listing/creators'
18+
import { loadingUpdatingMyListing } from '../../store/listing/selectors'
1619
import { FiCalendar } from 'react-icons/fi'
1720
import Lottie from 'lottie-react'
18-
import { BaseButton, VehicleDetail } from '~components'
21+
import { BaseButton, VehicleDetail, Button } from '~components'
1922
import {
2023
formatMillage,
2124
getVehicleTitle,
@@ -58,20 +61,24 @@ const VehicleCard = (props) => {
5861
featured,
5962
recommended,
6063
images, // handle re-render
64+
isSold,
65+
isAdmin,
6166
} = props
6267

6368
const listingId = get(props, 'listingId')
6469
const power = get(props, 'power')
6570

66-
const ref = useRef(null)
71+
const isUpdatingListing = useSelector(loadingUpdatingMyListing)
6772

73+
const ref = useRef(null)
74+
const dispatch = useDispatch()
6875
const [overlayActive, setOverlayActive] = useState(false)
6976
const { isMobile } = useViewport()
7077
useOutsideClick({ ref, isOpen: overlayActive, setOpen: setOverlayActive })
7178
const image = find(images, (image) => image.order === 0) || head(images)
7279
const { t } = useTranslation()
7380
const vehicleBodyYear = formatVehicleMainLabel(bodyType, regDate, t, isMobile)
74-
const vehicleBody = t(`vehicle.${bodyTypes[bodyType]}`);
81+
const vehicleBody = t(`vehicle.${bodyTypes[bodyType]}`)
7582
const vehicleMileage = isMobile ? mileage : formatMillage(mileage, t)
7683
const heartRef = useRef(null)
7784
const vehicleTitle = getVehicleTitle({ make, model, power, capacity }, t)
@@ -118,12 +125,14 @@ const VehicleCard = (props) => {
118125
recommended={recommended}
119126
visible={!overlayActive}
120127
/>
121-
<button
122-
className={styles.likeButton}
123-
onClick={() => setVehicleFavorite()}
124-
>
125-
<Lottie {...lottieAnimation} />
126-
</button>
128+
{!isAdmin && (
129+
<button
130+
className={styles.likeButton}
131+
onClick={() => setVehicleFavorite()}
132+
>
133+
<Lottie {...lottieAnimation} />
134+
</button>
135+
)}
127136
<VehicleCardOverlay
128137
visible={overlayActive}
129138
onClose={() => setOverlayActive(false)}
@@ -132,6 +141,7 @@ const VehicleCard = (props) => {
132141
listingId={listingId}
133142
price={finalPrice}
134143
title={vehicleTitle}
144+
isAdmin={isAdmin}
135145
/>
136146
</VehicleCardOverlay>
137147
<div className={styles.image}>
@@ -155,6 +165,7 @@ const VehicleCard = (props) => {
155165
href={`/listing/${listingId}`}
156166
>
157167
{vehicleTitle}
168+
{isSold && <span className={styles.soldChip}>{t('label.sold')}</span>}
158169
</BaseButton>
159170
<div className={styles.features}>
160171
<VehicleDetail icon={<FiCalendar />}>{vehicleBodyYear}</VehicleDetail>
@@ -170,7 +181,9 @@ const VehicleCard = (props) => {
170181
<VehicleDetail
171182
icon={<FiMapPin />}
172183
>{`${city}, ${countryCode}`}</VehicleDetail>
173-
<VehicleDetail mobileOnly icon={<GiCarWheel />}>{vehicleBody}</VehicleDetail>
184+
<VehicleDetail mobileOnly icon={<GiCarWheel />}>
185+
{vehicleBody}
186+
</VehicleDetail>
174187
</div>
175188
</div>
176189
<div className={styles.footer}>
@@ -186,6 +199,25 @@ const VehicleCard = (props) => {
186199
>
187200
{`${price}€`}
188201
</span>
202+
203+
{isAdmin && (
204+
<Button
205+
type={Button.types.GHOST}
206+
color="red"
207+
className={styles.soldButton}
208+
disabled={isUpdatingListing}
209+
onClick={() => {
210+
dispatch(
211+
ListingsCreator.updateMyListing({
212+
_id: listingId,
213+
soldAt: isSold ? null : new Date(),
214+
})
215+
)
216+
}}
217+
>
218+
{t(isSold ? 'label.available' : 'label.sold')}
219+
</Button>
220+
)}
189221
</div>
190222
<button
191223
className={styles.moreButton}

components/Cards/VehicleCard.module.scss

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
padding: spacing(1);
6868
}
6969
.title {
70+
position: relative;
7071
text-align: left;
7172
font-family: $font-family-secondary;
7273
color: $color-black;
@@ -115,6 +116,7 @@
115116
}
116117
.prices {
117118
display: flex;
119+
align-items: center;
118120
.price {
119121
font-size: $font-size-span;
120122
font-family: $font-family-secondary;
@@ -130,3 +132,26 @@
130132
}
131133
}
132134
}
135+
136+
.soldChip {
137+
background: $color-red;
138+
border-radius: $border-radius-small;
139+
padding: 0 spacing(0.5);
140+
color: $color-white;
141+
font-family: $font-family;
142+
height: 14px;
143+
display: flex;
144+
justify-content: center;
145+
align-items: center;
146+
position: absolute;
147+
right: 0;
148+
bottom: 0;
149+
font-size: $font-size-s;
150+
}
151+
152+
.soldButton {
153+
min-height: unset;
154+
font-size: $font-size-s;
155+
padding: spacing(0.5) spacing(1);
156+
margin-left: spacing(0.5);
157+
}

components/Cards/VehicleCardScreen/VehicleCardScreen.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useState } from 'react'
22
import BaseScreen from './screens/BaseScreen'
33
import ChatScreen from './screens/ChatScreen'
44
import OfferScreen from './screens/OfferScreen'
5+
import BaseAdminScreen from './screens/BaseAdminScreen'
56

67
const screens = {
78
BASE: 'BASE',
@@ -13,18 +14,28 @@ type Props = {
1314
price?: number
1415
title: string
1516
listingId: string
17+
isAdmin?: boolean
1618
}
1719

1820
const VehicleCardScreen: React.FunctionComponent<Props> = ({
1921
price,
2022
title,
2123
listingId,
24+
isAdmin,
2225
}) => {
2326
const [screen, setScreen] = useState(screens.BASE)
2427

2528
const onChatCancel = () => {
2629
setScreen(screens.BASE)
2730
}
31+
32+
if (isAdmin) {
33+
return (
34+
<>
35+
<BaseAdminScreen listingId={listingId} visible={screen === screens.BASE} />
36+
</>
37+
)
38+
}
2839
return (
2940
<>
3041
<ChatScreen
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from 'react'
2+
import { useTranslation } from 'react-i18next'
3+
import classNames from 'classnames'
4+
import { FiLink2, FiEdit3 } from 'react-icons/fi'
5+
import { Button } from '~components'
6+
import { toast } from 'react-toastify'
7+
import styles from './BaseScreen.module.scss'
8+
9+
const BaseAdminScreen = ({ visible, listingId }) => {
10+
const { t } = useTranslation()
11+
return (
12+
<div
13+
className={classNames(styles.overlayContent, visible && styles.visible)}
14+
>
15+
<Button
16+
type={Button.types.GHOST}
17+
fluid
18+
isInternalLink
19+
href={`/listings/${listingId}`}
20+
className={styles.overlayButton}
21+
>
22+
<FiEdit3 />
23+
&nbsp;
24+
{t('button.edit')}
25+
</Button>
26+
<Button
27+
type={Button.types.GHOST}
28+
fluid
29+
onClick={() =>
30+
toast.success(`Link copied! 😍`, {
31+
autoClose: 1800,
32+
})
33+
}
34+
className={styles.overlayButton}
35+
>
36+
<FiLink2 />
37+
&nbsp;
38+
{t('button.share')}
39+
</Button>
40+
</div>
41+
)
42+
}
43+
44+
export default BaseAdminScreen

components/Carousels/ListingsCarousel.module.scss

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,6 @@
11
@import 'vars.scss';
22

33

4-
@keyframes slideTopFadeIn {
5-
0% {
6-
transform: translateY(-100%);
7-
opacity: 0;
8-
}
9-
75% {
10-
transform: translateY(20%);
11-
}
12-
100% {
13-
transform: translateY(0);
14-
opacity: 1;
15-
}
16-
}
17-
184

195
.container {
206
position: relative;
@@ -28,10 +14,6 @@
2814
@media (min-width: $tablet) {
2915
min-height: 30vh;
3016
}
31-
article {
32-
animation-fill-mode: forwards;
33-
animation: slideTopFadeIn 0.3s ease-in;
34-
}
3517
}
3618

3719
.wrapper {

components/Navigation/NavigationDrawer.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react'
22
import { map } from 'lodash'
33
import { useTranslation } from 'react-i18next'
44
import classNames from 'classnames'
5+
import useViewport from '~hooks/useViewport'
56
import styles from './NavigationDrawer.module.scss'
67
import LogoWhite from '~public/logo-white.svg'
78
import NavigationContent from './NavigationContent'
@@ -12,6 +13,10 @@ import RussianIcon from '~public/icons/language/ru.svg'
1213
import { BaseDrawer, BaseButton, LanguageList } from '~components'
1314

1415
const NavigationDrawer = () => {
16+
const { isMobile } = useViewport()
17+
if (!isMobile) {
18+
return null;
19+
}
1520
return (
1621
<BaseDrawer>
1722
<div className={styles.drawer}>

i18n/translations/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"welcome": "👋 Welcome",
1919
"deleteSuccessful": "🗑️ Delete successful!",
2020
"listingCreated": "🙌 Listing created",
21+
"listingUpdated": "Listing updated! 🙌",
2122
"link_copied": "Link copied!",
2223
"offerSent": "Offer sent! 🙌"
2324
},
@@ -100,6 +101,8 @@
100101
"recommended": "Recommended",
101102
"urgent": "Urgent",
102103
"phone": "Phone",
104+
"sold": "Sold",
105+
"available": "Available",
103106
"firstName": "First name",
104107
"username": "Username",
105108
"lastName": "Last name",
@@ -225,6 +228,7 @@
225228
"signIn": "Sign in",
226229
"findMore": "Find more",
227230
"signUp": "Sign up",
231+
"edit": "Edit",
228232
"signUpWithGoogle": "Sign up with Google",
229233
"signUpWithFacebook": "Sign up with Facebook",
230234
"signInWithGoogle": "Sign in with Google",

pages/listings/index.tsx

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import {
66
myListingsSelector,
77
myListingsLoadingSelector,
88
} from '../../store/listing/selectors'
9-
import ListignsCreator from '../../store/listing/creators'
10-
import { Layout, Loader, VehicleCard } from '../../components'
9+
import ListingsCreator from '../../store/listing/creators'
10+
import { Layout, Loader, VehicleCard, Input } from '../../components'
1111
import { getVehicleCardProps } from '~/utils/helpers'
1212

1313
const ListingsPage = () => {
@@ -16,20 +16,40 @@ const ListingsPage = () => {
1616
const dispatch = useDispatch()
1717

1818
useEffect(() => {
19-
dispatch(ListignsCreator.fetchMyListings())
19+
dispatch(ListingsCreator.fetchMyListings())
2020
}, [])
2121
return (
22-
<Layout background="gray" fullscreen className={styles.container}>
22+
<Layout fullscreen className={styles.container}>
2323
<h1>Listings</h1>
2424
<div className={styles.listings}>
25-
{map(listings, (listing) => (
26-
<VehicleCard
27-
key={listing._id}
28-
{...getVehicleCardProps(listing)}
29-
listingId={listing._id}
30-
/>
31-
))}
32-
<Loader loading={loading} />
25+
{map(listings, (listing) => {
26+
console.log('listing', listing);
27+
return (
28+
<div key={listing._id} className={styles.listing}>
29+
<div className={styles.title}>
30+
<h4>BMW 6, 317BNV</h4>
31+
<div className={styles.titleDetails}>
32+
<span>Expires in 13 days</span>
33+
<span>Featured until 29.09.2021</span>
34+
</div>
35+
</div>
36+
37+
<div className={styles.content}>
38+
<form>
39+
{/* @ts-ignore */}
40+
<Input name="price" label="Price" type="number" value={listing.price} />
41+
</form>
42+
<VehicleCard
43+
{...getVehicleCardProps(listing)}
44+
listingId={listing._id}
45+
isSold={!!listing.soldAt}
46+
isAdmin
47+
/>
48+
</div>
49+
</div>
50+
)
51+
})}
52+
<Loader loading={loading} centered />
3353
</div>
3454
</Layout>
3555
)

0 commit comments

Comments
 (0)