Skip to content

Commit

Permalink
Location fetching (#843)
Browse files Browse the repository at this point in the history
* location component

* fetching with @nanostores/query

* layouts reorg

* typescript plugins cleanup

* location component unit test cases

* fetch only when visible
  • Loading branch information
kremalicious authored Oct 4, 2023
1 parent 956e203 commit 3b25ae2
Show file tree
Hide file tree
Showing 51 changed files with 350 additions and 861 deletions.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

This file was deleted.

Binary file not shown.
Binary file not shown.
Binary file not shown.
262 changes: 52 additions & 210 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"preview": "astro preview",
"typecheck:astro": "astro check",
"typecheck:tsc": "tsc --noEmit --pretty",
"typecheck": "astro sync && run-p typecheck:astro typecheck:tsc",
"typecheck": "npm run typecheck:astro && npm run typecheck:tsc",
"prebuild": "run-p --silent --continue-on-error create:symlinks create:icons move:downloads",
"test:unit": "vitest run --config './test/vitest.config.ts' --coverage",
"test:e2e": "playwright test --config './test/playwright.config.ts'",
Expand All @@ -35,7 +35,7 @@
"@astrojs/react": "^3.0.2",
"@astrojs/rss": "^3.0.0",
"@astrojs/sitemap": "^3.0.0",
"@astrojs/ts-plugin": "^1.1.3",
"@nanostores/query": "^0.2.4",
"@nanostores/react": "^0.7.1",
"@rainbow-me/rainbowkit": "^1.0.11",
"astro": "3.2.2",
Expand Down Expand Up @@ -95,7 +95,6 @@
"svgo": "^3.0.2",
"ts-node": "^10.9.1",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1",
"unist-util-visit": "^5.0.0",
"vite-tsconfig-paths": "^4.2.1",
"vitest": "^0.34.6"
Expand Down
4 changes: 2 additions & 2 deletions src/components/Footer/Networks.astro
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type Props = {
const { links } = Astro.props
---

<p>
<section class={styles.networks}>
{
links.map((link: string) => (
<a class={styles.link} href={link} title={link} rel="me">
Expand All @@ -25,4 +25,4 @@ const { links } = Astro.props
</a>
))
}
</p>
</section>
4 changes: 4 additions & 0 deletions src/components/Footer/Networks.module.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.networks {
margin-top: calc(var(--spacer) / 2);
}

.link {
text-align: center;
width: 2rem;
Expand Down
5 changes: 4 additions & 1 deletion src/components/Footer/Vcard.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
import { Image } from 'astro:assets'
import Networks from './Networks.astro'
import Location from '../Location'
import avatar from '@images/avatar.jpg'
import config from '@config/blog.config'
import styles from './Vcard.module.css'
Expand All @@ -19,10 +20,12 @@ const links = [mastodon, github, rss, jsonfeed]
/>

<p class={styles.description}>
{config.siteDescription.replace(name, '')}{' '}
{config.siteDescription.replace(name, '')}<br />
<a class="fn" rel="author" href={url}>
{name}
</a>
</p>

<Location client:visible />

<Networks links={links} />
5 changes: 2 additions & 3 deletions src/components/Footer/Vcard.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@

.description {
font-size: var(--font-size-h5);
margin-top: 0;
margin-bottom: calc(var(--spacer) / var(--line-height));
margin: 0;
}

.description a {
display: block;
display: inline-block;
}
2 changes: 1 addition & 1 deletion src/components/Footer/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const { name, url, github } = config.author
<Github />
View source
</a>
<a href="/thanks/" class={styles.btc}>
<a href="/thanks/">
<Bitcoin />
Say Thanks
</a>
Expand Down
25 changes: 7 additions & 18 deletions src/components/Footer/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,31 @@
position: fixed;
width: 100%;
border: 0;
will-change: transform;
z-index: 0;
bottom: 0;
box-shadow: none;
}
}

.btc code {
font-size: 0.6rem;
background: none;
color: var(--link-color);
padding: 0;
.copyright {
margin-top: calc(var(--spacer) / var(--line-height));
font-size: var(--font-size-mini);
color: var(--text-color-light);
}

.copyright p {
margin-bottom: 0;
font-size: var(--font-size-mini);
}

.copyright a,
.copyright button code {
.copyright a {
color: var(--text-color-light);
}

.copyright a,
.copyright button {
margin-left: var(--spacer);
}

.copyright a:hover,
.copyright button code {
.copyright a:hover {
color: var(--link-color);
}

.copyright a:first-child,
.copyright button:first-child {
.copyright a:first-child {
margin-left: 0;
}

Expand Down
23 changes: 23 additions & 0 deletions src/components/Location/Flag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import styles from './index.module.css'

type Props = {
country: {
name: string
code: string
}
}

export function Flag({ country }: Props) {
// offset between uppercase ascii and regional indicator symbols
const OFFSET = 127397

const emoji = country?.code.replace(/./g, (char) =>
String.fromCodePoint(char.charCodeAt(0) + OFFSET)
)

return (
<span role="img" aria-label={country?.name} className={styles.emoji}>
{emoji}
</span>
)
}
32 changes: 32 additions & 0 deletions src/components/Location/LocationItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { ReactElement } from 'react'
import { Flag } from './Flag'

type LocationProps = {
country: string
countryCode: string
city: string
time: string
showFlag?: boolean
}

export function LocationItem({
country,
countryCode,
city,
time,
showFlag = true
}: LocationProps): ReactElement<LocationProps> {
return (
<>
{showFlag && (
<Flag
country={{
code: countryCode,
name: country
}}
/>
)}
{city} <span>{time}</span>
</>
)
}
41 changes: 41 additions & 0 deletions src/components/Location/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.location {
font-size: var(--font-size-mini);
min-height: 36px;
}

.location span {
font-style: italic;
}

.emoji {
display: inline-block;
font-size: 1em;
line-height: 1em;
vertical-align: 'middle';
margin-right: calc(var(--spacer) / 6);
}

.next {
display: inline-block;
margin-left: calc(var(--spacer) / 3);
}

.loading {
display: inline-block;
margin-left: calc(var(--spacer) / 3);
animation: flicker 0.3s linear infinite;
}

@keyframes flicker {
0% {
opacity: 0;
}

50% {
opacity: 1;
}

100% {
opacity: 0;
}
}
94 changes: 94 additions & 0 deletions src/components/Location/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {
describe,
it,
expect,
beforeAll,
afterAll,
vi,
type SpyInstance
} from 'vitest'
import { render, screen } from '@testing-library/react'
import * as nanostores from '@nanostores/react'
import Location from '.'

const mockData = {
now: {
country: 'USA',
country_code: 'US',
city: 'New York'
},
next: {
country: 'Canada',
country_code: 'CA',
city: 'Toronto',
date_start: '2023-10-05'
}
}

describe('Location component', () => {
let useStoreSpy: SpyInstance

beforeAll(() => {
vi.mock('@nanostores/react')
useStoreSpy = vi.spyOn(nanostores, 'useStore')
})

afterAll(() => {
vi.restoreAllMocks()
})

it('renders the location items correctly', () => {
useStoreSpy.mockImplementationOnce(() => ({
data: mockData,
loading: false,
error: null
}))

render(<Location />)

expect(screen.getByLabelText('USA')).toBeInTheDocument()
expect(screen.getByText('New York')).toBeInTheDocument()
expect(screen.getByLabelText('Canada')).toBeInTheDocument()
expect(screen.getByText('Toronto')).toBeInTheDocument()
})

it('renders the loading indicator', () => {
useStoreSpy.mockImplementationOnce(() => ({
data: null,
loading: true,
error: null
}))

render(<Location />)

expect(screen.getByText('...')).toBeInTheDocument()
})

it('renders empty when there is no data', () => {
useStoreSpy.mockImplementationOnce(() => ({
data: null,
loading: false,
error: null
}))

render(<Location />)

expect(screen.queryByLabelText('Location')).toBeEmptyDOMElement()
})

it('renders nothing and logs error when error is encountered', () => {
const consoleErrorSpy = vi.spyOn(console, 'error')
useStoreSpy.mockImplementationOnce(() => ({
data: null,
loading: false,
error: 'Error'
}))

render(<Location />)

expect(screen.queryByLabelText('Location')).not.toBeInTheDocument()
expect(consoleErrorSpy).toHaveBeenCalledWith(
'Failed to fetch location: Error'
)
})
})
45 changes: 45 additions & 0 deletions src/components/Location/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useStore } from '@nanostores/react'
import { $location } from '@stores/location'
import { formatDistanceToNowStrict } from 'date-fns'
import { LocationItem } from './LocationItem'
import styles from './index.module.css'

export default function Location() {
const { data, loading, error } = useStore($location)
if (error) {
console.error(`Failed to fetch location: ${error}`)
return null
}

return (
<section aria-label="Location" className={styles.location}>
{loading && !data ? (
<span className={styles.loading}>...</span>
) : data?.now?.city ? (
<>
<LocationItem
country={data.now.country}
countryCode={data.now.country_code}
city={data.now.city}
time="now"
/>

<div className={styles.next}>
{data.next?.city && (
<LocationItem
country={data.next.country}
countryCode={data.next.country_code}
city={data.next.city}
time={formatDistanceToNowStrict(
new Date(data.next.date_start),
{ addSuffix: true }
)}
showFlag={data.now.country !== data.next.country}
/>
)}
</div>
</>
) : null}
</section>
)
}
2 changes: 1 addition & 1 deletion src/components/PostTeaser/index.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
import PostTitle from '../layouts/Post/Title.astro'
import PostTitle from '@layouts/Post/Title.astro'
import styles from './index.module.css'
import Picture from '@components/Picture/index.astro'
import type { CollectionEntry } from 'astro:content'
Expand Down
Loading

1 comment on commit 3b25ae2

@vercel
Copy link

@vercel vercel bot commented on 3b25ae2 Oct 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

blog – ./

blog-kremalicious.vercel.app
blog-git-main-kremalicious.vercel.app
kremalicious.vercel.app

Please sign in to comment.