Skip to content

Commit 5d14881

Browse files
committed
fix: detect already-loaded images during React hydration
ImageWithPlaceholder now checks if image is already complete on mount, fixing invisible images after page refresh on prerendered pages.
1 parent 2807cfb commit 5d14881

15 files changed

+814
-599
lines changed

src/components/ui/ImageWithPlaceholder.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect, ImgHTMLAttributes } from 'react'
1+
import { useState, useEffect, useRef, ImgHTMLAttributes } from 'react'
22

33
interface ImageWithPlaceholderProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'onLoad' | 'onError'> {
44
/** Image source URL */
@@ -47,6 +47,17 @@ export default function ImageWithPlaceholder({
4747
...props
4848
}: ImageWithPlaceholderProps) {
4949
const [isLoading, setIsLoading] = useState(true)
50+
const imgRef = useRef<HTMLImageElement>(null)
51+
52+
// Check if image is already loaded on mount (hydration case)
53+
// When React hydrates prerendered HTML, the image may already be loaded
54+
// but onLoad won't fire again, leaving the image invisible
55+
useEffect(() => {
56+
const img = imgRef.current
57+
if (img && img.complete && img.naturalWidth > 0) {
58+
setIsLoading(false)
59+
}
60+
}, [])
5061

5162
// Reset loading state when src changes - critical for accordion image switching
5263
useEffect(() => {
@@ -68,6 +79,7 @@ export default function ImageWithPlaceholder({
6879
/>
6980
)}
7081
<img
82+
ref={imgRef}
7183
src={src}
7284
alt={alt}
7385
className={`${className} ${animationClass}`}

src/locales/en/articles.json

Lines changed: 151 additions & 0 deletions
Large diffs are not rendered by default.

src/locales/es/articles.json

Lines changed: 227 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 29 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
1+
import { useTranslation, Trans } from 'react-i18next'
12
import { BlogArticleLayout, ArticleH2, ArticleParagraph, ArticleImage, HowToSchema } from '../components/ui'
23
import { LocalizedLink } from '../components/ui'
34
import medicalScreen from '../assets/images/blog-medical-card-folio-screen.png'
45

56
export default function HowToAddAndStoreYourMedicalCardArticlePage() {
7+
const { t } = useTranslation('articles')
8+
const slug = 'how-to-add-and-store-your-medical-card'
9+
610
return (
711
<BlogArticleLayout
8-
title="How to store your medical card in a digital wallet"
9-
description="Learn how to store your medical card and health insurance in a digital wallet. Discover the benefits, security features, and how to manage family members' health documents."
12+
title={t(`${slug}.title`)}
13+
description={t(`${slug}.description`)}
1014
date="Jan 28, 2024"
1115
category="Product"
12-
slug="how-to-add-and-store-your-medical-card"
16+
slug={slug}
1317
>
1418
<HowToSchema
15-
name="How to store your medical card in a digital wallet"
16-
description="Step-by-step guide to storing your medical insurance card securely in the Folio digital wallet app."
19+
name={t(`${slug}.title`)}
20+
description={t(`${slug}.description`)}
1721
totalTime="PT5M"
1822
steps={[
1923
{ name: "Download Folio app", text: "Install Folio Wallet from the App Store or Google Play Store on your iPhone or Android device." },
@@ -24,51 +28,34 @@ export default function HowToAddAndStoreYourMedicalCardArticlePage() {
2428
{ name: "Save the card", text: "Save the card to your digital wallet. It will be stored securely with end-to-end encryption." },
2529
]}
2630
/>
27-
<ArticleParagraph>
28-
You're at the doctor's office and they ask for your insurance card. You reach for your wallet, flip through receipts and old loyalty cards, and realize you left it at home. Now imagine the same scenario, but this time you pull out your phone, tap twice, and show your insurance details on screen. That's the difference a digital wallet makes for health documents.
29-
</ArticleParagraph>
30-
31-
<ArticleImage src={medicalScreen} alt="Digital wallet app showing stored medical card and health insurance" />
32-
33-
<ArticleParagraph>
34-
Storing your health insurance card digitally isn't just about convenience. It's about having critical information available exactly when you need it, whether that's a routine checkup, an emergency room visit, or filling out forms at a new specialist. Most people carry their phone everywhere. The same can't be said for physical insurance cards.
35-
</ArticleParagraph>
36-
37-
<ArticleH2>How to store your medical card</ArticleH2>
38-
39-
<ArticleParagraph>
40-
The simplest approach is taking a photo of your card and saving it to your camera roll. It works, but photos get buried among hundreds of other images. A better solution is using a dedicated wallet app. <LocalizedLink to="/#get-the-app" className="underline hover:text-[#0a0a0a] transition-colors">Folio</LocalizedLink> lets you scan your insurance card directly, then automatically extracts the text so you can copy your member ID, group number, or policy details without squinting at a tiny image.
41-
</ArticleParagraph>
42-
43-
<ArticleParagraph>
44-
To add your card: open the app, select the health category, and either scan your physical card or import an existing photo. The card gets stored with all other health documents, separate from your payment cards, IDs, and travel documents. When you need it, you search or browse to the health section and it's right there.
45-
</ArticleParagraph>
46-
47-
<ArticleH2>Managing family health documents</ArticleH2>
48-
49-
<ArticleParagraph>
50-
If you're a parent, you're probably carrying insurance cards for your entire family. That's a lot of plastic to keep track of. A digital wallet lets you store everyone's cards in one place, organized by family member. When your kid needs to see the pediatrician, you pull up their specific card instead of shuffling through a stack.
51-
</ArticleParagraph>
31+
<ArticleParagraph>{t(`${slug}.p1`)}</ArticleParagraph>
5232

53-
<ArticleParagraph>
54-
The same applies to vaccination records, prescription lists, or medical history summaries. Having these documents digitally means you can share them instantly with a new doctor or have them ready during a hospital visit. Some parents also store their children's allergy information and emergency contacts alongside insurance details.
55-
</ArticleParagraph>
33+
<ArticleImage src={medicalScreen} alt={t(`${slug}.img_alt`)} />
5634

57-
<ArticleH2>Security matters for health data</ArticleH2>
35+
<ArticleParagraph>{t(`${slug}.p2`)}</ArticleParagraph>
5836

37+
<ArticleH2>{t(`${slug}.h2_1`)}</ArticleH2>
5938
<ArticleParagraph>
60-
Health information is sensitive. Your insurance card contains your name, date of birth, and policy numbers. You don't want that floating around unsecured. A good digital wallet encrypts your documents end-to-end, meaning even if someone accessed the server, they couldn't read your data. Folio adds passkey and biometric protection, so your health documents are locked behind Face ID or fingerprint, not just a simple PIN.
39+
<Trans
40+
i18nKey={`${slug}.p3`}
41+
ns="articles"
42+
components={{
43+
LocalizedLink: <LocalizedLink to="/#get-the-app" className="underline hover:text-[#0a0a0a] transition-colors" />
44+
}}
45+
/>
6146
</ArticleParagraph>
47+
<ArticleParagraph>{t(`${slug}.p4`)}</ArticleParagraph>
6248

63-
<ArticleParagraph>
64-
This level of security actually makes digital storage safer than physical cards. Lose your wallet, and anyone who finds it can see your insurance details. Lose your phone, and your documents remain encrypted and inaccessible without your biometrics.
65-
</ArticleParagraph>
49+
<ArticleH2>{t(`${slug}.h2_2`)}</ArticleH2>
50+
<ArticleParagraph>{t(`${slug}.p5`)}</ArticleParagraph>
51+
<ArticleParagraph>{t(`${slug}.p6`)}</ArticleParagraph>
6652

67-
<ArticleH2>Beyond insurance cards</ArticleH2>
53+
<ArticleH2>{t(`${slug}.h2_3`)}</ArticleH2>
54+
<ArticleParagraph>{t(`${slug}.p7`)}</ArticleParagraph>
55+
<ArticleParagraph>{t(`${slug}.p8`)}</ArticleParagraph>
6856

69-
<ArticleParagraph>
70-
Once you've digitized your insurance card, consider adding related documents: vaccination records, prescription lists, doctor's notes, or medical test results. Having everything in one searchable location means no more digging through email attachments or paper folders before appointments. You can also store travel health insurance separately for trips abroad, ensuring you have the right coverage details wherever you are.
71-
</ArticleParagraph>
57+
<ArticleH2>{t(`${slug}.h2_4`)}</ArticleH2>
58+
<ArticleParagraph>{t(`${slug}.p9`)}</ArticleParagraph>
7259
</BlogArticleLayout>
7360
)
7461
}
Lines changed: 30 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
1+
import { useTranslation, Trans } from 'react-i18next'
12
import { BlogArticleLayout, ArticleH2, ArticleParagraph, ArticleImage, HowToSchema } from '../components/ui'
23
import { LocalizedLink } from '../components/ui'
34
import shareTravelImage from '../assets/images/blog-share-travel-folio.png'
45

56
export default function HowToShareTravelPlansArticlePage() {
7+
const { t } = useTranslation('articles')
8+
const slug = 'how-to-share-your-travel-plans-with-friends-using-a-trip-planner-app'
9+
610
return (
711
<BlogArticleLayout
8-
title="Your friend booked the hotel. Now they're stuck in traffic. Do you have the confirmation?"
9-
description="Group trips fall apart when one person has all the bookings. Here's how to make sure everyone has access to everything."
12+
title={t(`${slug}.title`)}
13+
description={t(`${slug}.description`)}
1014
date="Jul 18, 2025"
1115
category="Product"
12-
slug="how-to-share-your-travel-plans-with-friends-using-a-trip-planner-app"
16+
slug={slug}
1317
>
1418
<HowToSchema
15-
name="How to share travel plans with friends using Folio"
16-
description="Step-by-step guide to sharing trip bookings and travel documents with friends and family using shared folders in Folio."
19+
name={t(`${slug}.title`)}
20+
description={t(`${slug}.description`)}
1721
totalTime="PT10M"
1822
steps={[
1923
{ name: "Download Folio app", text: "Install Folio Wallet from the App Store or Google Play Store on your phone." },
@@ -24,60 +28,37 @@ export default function HowToShareTravelPlansArticlePage() {
2428
{ name: "Access documents during the trip", text: "During your trip, anyone can show any document at check-in, train stations, or venues. No need to wait for the person who booked." },
2529
]}
2630
/>
27-
<ArticleParagraph>
28-
You're standing at the hotel front desk. Your friend booked the room, but their flight was delayed. The receptionist is asking for the confirmation number. You check your messages: nothing. You search your email: nothing. The booking is in your friend's inbox, on a phone that's currently somewhere over the Atlantic.
29-
</ArticleParagraph>
30-
31-
<ArticleParagraph>
32-
This is the single point of failure problem. Group trips spread information across multiple people's inboxes. When the person with the booking isn't there, everyone else is stuck.
33-
</ArticleParagraph>
31+
<ArticleParagraph>{t(`${slug}.p1`)}</ArticleParagraph>
32+
<ArticleParagraph>{t(`${slug}.p2`)}</ArticleParagraph>
3433

35-
<ArticleH2>Why forwarding doesn't work</ArticleH2>
36-
37-
<ArticleParagraph>
38-
The obvious solution is forwarding emails. Someone books the hotel, forwards the confirmation to everyone. Someone else books train tickets, forwards those too. In theory, everyone has everything.
39-
</ArticleParagraph>
40-
41-
<ArticleParagraph>
42-
In practice, it falls apart. The forwarded email gets buried under 50 other messages. Someone's phone is on airplane mode and the attachment won't load. The person who needs the confirmation is scrolling through a group chat trying to find a screenshot from three weeks ago. Updates don't sync. Someone changed the reservation but forgot to forward the new confirmation.
43-
</ArticleParagraph>
44-
45-
<ArticleH2>Shared folders: everyone has everything</ArticleH2>
46-
47-
<ArticleParagraph>
48-
The real solution is shared access to documents, not copies of documents. When you add a hotel booking to a shared folder, everyone with access sees it immediately. Not a forwarded copy that might be outdated, but the actual document you're looking at.
49-
</ArticleParagraph>
34+
<ArticleH2>{t(`${slug}.h2_1`)}</ArticleH2>
35+
<ArticleParagraph>{t(`${slug}.p3`)}</ArticleParagraph>
36+
<ArticleParagraph>{t(`${slug}.p4`)}</ArticleParagraph>
5037

38+
<ArticleH2>{t(`${slug}.h2_2`)}</ArticleH2>
39+
<ArticleParagraph>{t(`${slug}.p5`)}</ArticleParagraph>
5140
<ArticleParagraph>
52-
<LocalizedLink to="/#get-the-app" className="underline hover:text-[#737373] transition-colors">Folio</LocalizedLink> works this way. Create a folder for your trip. Import your bookings: forward the confirmation email, upload the PDF, photograph the printout. Invite your travel companions. Now everyone sees the same timeline with the same documents.
41+
<Trans
42+
i18nKey={`${slug}.p6`}
43+
ns="articles"
44+
components={{
45+
LocalizedLink: <LocalizedLink to="/#get-the-app" className="underline hover:text-[#737373] transition-colors" />
46+
}}
47+
/>
5348
</ArticleParagraph>
5449

5550
<ArticleImage
5651
src={shareTravelImage}
57-
alt="Folio app showing import options, shared trip timeline with hotel, flights, and activities, and train ticket details for two passengers"
52+
alt={t(`${slug}.img_alt`)}
5853
/>
5954

60-
<ArticleParagraph>
61-
The flight shows both travelers tagged. The train ticket shows seats for two people. The hotel confirmation is visible to everyone in the folder. When you're at the front desk and your friend is delayed, you pull up the same confirmation they would show. No searching. No "can you screenshot that?"
62-
</ArticleParagraph>
63-
64-
<ArticleH2>How it works in practice</ArticleH2>
55+
<ArticleParagraph>{t(`${slug}.p7`)}</ArticleParagraph>
6556

66-
<ArticleParagraph>
67-
Before your trip: one person creates a shared folder and invites the others. As bookings come in, anyone can add them. Forward the confirmation email to your Folio address, or upload the PDF directly. The app extracts dates, times, confirmation numbers, and builds a timeline.
68-
</ArticleParagraph>
69-
70-
<ArticleParagraph>
71-
During your trip: anyone can show any document. At the train station, either person can pull up the tickets with the QR code. At the museum, anyone has the entrance tickets. If plans change and someone adds a new restaurant reservation, it appears for everyone immediately.
72-
</ArticleParagraph>
73-
74-
<ArticleParagraph>
75-
The practical difference: the person who booked doesn't need to be present. If one friend is running late, the others check in to the hotel. If someone's phone dies, the other person has the train tickets. No one is the single point of failure anymore.
76-
</ArticleParagraph>
77-
78-
<ArticleParagraph>
79-
Group trips work better when everyone has access to everything. Not screenshots that get lost, not forwarded emails that get buried, but shared documents that stay current. Less "can you send me that again?" More actually enjoying the trip together.
80-
</ArticleParagraph>
57+
<ArticleH2>{t(`${slug}.h2_3`)}</ArticleH2>
58+
<ArticleParagraph>{t(`${slug}.p8`)}</ArticleParagraph>
59+
<ArticleParagraph>{t(`${slug}.p9`)}</ArticleParagraph>
60+
<ArticleParagraph>{t(`${slug}.p10`)}</ArticleParagraph>
61+
<ArticleParagraph>{t(`${slug}.p11`)}</ArticleParagraph>
8162
</BlogArticleLayout>
8263
)
8364
}

0 commit comments

Comments
 (0)