Add a badge on locations for new photos
- The last updated date is taken from the latest photo of each location (without any other API call, just some data manipulation) - Manipulation of data in the preload request instead of the code
This commit is contained in:
9
src/atoms/Badge.svelte
Normal file
9
src/atoms/Badge.svelte
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<script>
|
||||||
|
// Props
|
||||||
|
export let text
|
||||||
|
export let size = 'small'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="badge badge--{size}">
|
||||||
|
<span>{text}</span>
|
||||||
|
</div>
|
||||||
@@ -1,19 +1,30 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { dateOlderThan } from 'utils/functions'
|
||||||
|
// Components
|
||||||
|
import Badge from 'atoms/Badge'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
export let location
|
export let location
|
||||||
|
|
||||||
// Variables
|
// Variables
|
||||||
const { name, slug, country } = location
|
const { name, slug, country, last_updated } = location
|
||||||
|
const timeLimit = 7 * 24 * 60 * 60 * 1000 // d*h*m*s*ms = 1 week
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="location" role="listitem">
|
<div class="location" role="listitem">
|
||||||
<a href="/location/{country.slug}/{slug}" rel="prefetch" sapper-noscroll>
|
<a href="/location/{country.slug}/{slug}" rel="prefetch" sapper-noscroll>
|
||||||
<img src={country.flag.full_url} alt="Flag of {country.name}">
|
<img src={country.flag.full_url} alt="Flag of {country.name}">
|
||||||
|
|
||||||
<div class="anim-mask mask-city">
|
<div class="anim-mask mask-city">
|
||||||
<h3 class="location__city">{name}</h3>
|
<h3 class="location__city">{name}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="anim-mask mask-country">
|
<div class="anim-mask mask-country">
|
||||||
<p class="location__country style-caps">{country.name}</p>
|
<p class="location__country style-caps">{country.name}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if dateOlderThan(last_updated, timeLimit)}
|
||||||
|
<Badge size="small" text="new" />
|
||||||
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script context="module">
|
<script context="module">
|
||||||
export async function preload (page, session) {
|
export async function preload (page, session) {
|
||||||
const req = await this.fetch(apiEndpoints.gql, {
|
await this.fetch(apiEndpoints.gql, {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -56,16 +56,58 @@
|
|||||||
illu_mobile { full_url }
|
illu_mobile { full_url }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
photos (filter: { status_eq: "published" }) {
|
||||||
|
data {
|
||||||
|
created_on
|
||||||
|
location { id }
|
||||||
|
}
|
||||||
|
}
|
||||||
}`})
|
}`})
|
||||||
})
|
})
|
||||||
const result = await req.json()
|
.then(res => res.json())
|
||||||
if (req.ok) {
|
.then(res => {
|
||||||
// Set data into store
|
const { data } = res
|
||||||
site.set(result.data.site.data[0])
|
|
||||||
continents.set(result.data.continents.data)
|
// Manipulate countries data
|
||||||
countries.set(result.data.countries.data)
|
data.countries.data.forEach(country => {
|
||||||
locations.set(result.data.locations.data)
|
const matchingContinent = data.continents.data.find(continent => continent.id === country.continent.id)
|
||||||
}
|
// Replace continent with request data
|
||||||
|
country.continent = matchingContinent
|
||||||
|
// Add countries to each continents
|
||||||
|
matchingContinent.countries = []
|
||||||
|
matchingContinent.countries.push(country)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Replace each location's country with request data
|
||||||
|
data.locations.data.forEach(location => {
|
||||||
|
location.country = data.countries.data.find(country => country.id === location.country.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Filter and keep only the latest photo for each location
|
||||||
|
// https://stackoverflow.com/questions/61636965/
|
||||||
|
const latestPhotos = Object.values(data.photos.data.reduce((photos, photo) => {
|
||||||
|
if (photos.hasOwnProperty(photo.location.id)) {
|
||||||
|
if (new Date(photos[photo.location.id].created_on) < new Date(photo.created_on)) {
|
||||||
|
photos[photo.location.id] = photo
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
photos[photo.location.id] = photo
|
||||||
|
}
|
||||||
|
return photos
|
||||||
|
}, {}))
|
||||||
|
|
||||||
|
// Add last updated date to each location
|
||||||
|
data.locations.data.forEach(location => {
|
||||||
|
const latestPhoto = latestPhotos.find(photo => photo.location.id === location.id)
|
||||||
|
location.last_updated = latestPhoto.created_on
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set data into store
|
||||||
|
site.set(data.site.data[0])
|
||||||
|
continents.set(data.continents.data)
|
||||||
|
countries.set(data.countries.data)
|
||||||
|
locations.set(data.locations.data)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -112,30 +154,6 @@
|
|||||||
'/fonts/M-Light.woff2',
|
'/fonts/M-Light.woff2',
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Manipulate data
|
|
||||||
*/
|
|
||||||
if (process.browser) {
|
|
||||||
if ($countries) {
|
|
||||||
$countries.forEach(country => {
|
|
||||||
const matchingContinent = $continents.find(continent => continent.id === country.continent.id)
|
|
||||||
// Replace continent with request data
|
|
||||||
country.continent = matchingContinent
|
|
||||||
// Add countries to each continents
|
|
||||||
matchingContinent.countries = []
|
|
||||||
matchingContinent.countries.push(country)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($locations) {
|
|
||||||
// Replace each location's country with request data
|
|
||||||
$locations.forEach(location => {
|
|
||||||
location.country = $countries.find(country => country.id === location.country.id)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" global>
|
<style lang="scss" global>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
$color-primary: #3C0576;
|
$color-primary: #3C0576;
|
||||||
$color-primary-darker: #2D0458;
|
$color-primary-darker: #2D0458;
|
||||||
$color-secondary: #FF6C89;
|
$color-secondary: #FF6C89;
|
||||||
|
$color-secondary-light: #FFB3C2;
|
||||||
$color-secondary-bright: #FF0536;
|
$color-secondary-bright: #FF0536;
|
||||||
$color-text: #333;
|
$color-text: #333;
|
||||||
$color-tertiary: #FFE0C5;
|
$color-tertiary: #FFE0C5;
|
||||||
|
|||||||
27
src/style/atoms/_badge.scss
Normal file
27
src/style/atoms/_badge.scss
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
** Badge
|
||||||
|
*/
|
||||||
|
.badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: $color-primary-darker;
|
||||||
|
font-family: $font-sans-sb;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
background-color: $color-secondary-light;
|
||||||
|
box-shadow: 0 0 0 4px rgba($color-tertiary, 0.15);
|
||||||
|
border-radius: 50vh;
|
||||||
|
|
||||||
|
// Small size
|
||||||
|
&--small {
|
||||||
|
height: 14px;
|
||||||
|
padding: 0 4px;
|
||||||
|
font-size: rem(7px);
|
||||||
|
line-height: 14px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-top: 0.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,18 +35,29 @@
|
|||||||
font-size: rem(48px);
|
font-size: rem(48px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.mask-city {
|
|
||||||
height: 40px;
|
|
||||||
margin: 24px 0 16px;
|
|
||||||
|
|
||||||
@include breakpoint (md) {
|
// Masks
|
||||||
height: 64px;
|
.mask-city {
|
||||||
margin-bottom: 24px;
|
height: 40px;
|
||||||
}
|
margin: 16px 0 12px;
|
||||||
|
|
||||||
|
@include breakpoint (md) {
|
||||||
|
height: 64px;
|
||||||
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
.mask-country {
|
}
|
||||||
height: 16px;
|
.mask-country {
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Badge
|
||||||
|
.badge {
|
||||||
|
margin-top: 16px;
|
||||||
|
|
||||||
|
@include breakpoint (sm) {
|
||||||
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Hover
|
// Hover
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
@import "atoms/link";
|
@import "atoms/link";
|
||||||
@import "atoms/switcher";
|
@import "atoms/switcher";
|
||||||
@import "atoms/counter";
|
@import "atoms/counter";
|
||||||
|
@import "atoms/badge";
|
||||||
|
|
||||||
// Molecules
|
// Molecules
|
||||||
@import "molecules/location";
|
@import "molecules/location";
|
||||||
|
|||||||
@@ -185,6 +185,16 @@ export const relativeTime = (originDate, limit = 0) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Check if date is older than
|
||||||
|
*/
|
||||||
|
export const dateOlderThan = (originDate, limit) => {
|
||||||
|
const date = new Date(originDate)
|
||||||
|
const diff = Number(new Date()) - date
|
||||||
|
return diff < limit
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Controls Anime.js parallax
|
** Controls Anime.js parallax
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user