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:
2020-05-06 17:12:07 +02:00
parent 3230cfa0d0
commit cd609cd710
8 changed files with 132 additions and 44 deletions

9
src/atoms/Badge.svelte Normal file
View File

@@ -0,0 +1,9 @@
<script>
// Props
export let text
export let size = 'small'
</script>
<div class="badge badge--{size}">
<span>{text}</span>
</div>

View File

@@ -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>

View File

@@ -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>
@@ -157,4 +175,4 @@
<Transition /> <Transition />
<AnalyticsTracker {stores} id={process.env.CONFIG.GA_TRACKER_ID} /> <AnalyticsTracker {stores} id={process.env.CONFIG.GA_TRACKER_ID} />

View File

@@ -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;

View 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;
}
}
}

View File

@@ -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 {

View File

@@ -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";

View File

@@ -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
*/ */