In many underserved and minority skin communities, the risk of UV-related damage is high. However, finding sunscreen that truly matches their complexion is difficult as they are not the focus of much research in the field. To address this gap, SPFMatch is an accessible tool focused on helping people of color discover sunscreen products that meet their unique needs.
The application provides personalized sunscreen recommendations by considering skin tone, sensitivity, lifestyle, and user preferences. Instead of relying on trial and error or unclear marketing, users will receive guidance that helps them make informed decisions about their health. Our goal is to make the process simple, intuitive, and empowering so that users feel confident in protecting their skin.
Visit our project website to learn more about the team behind SPFMatch as well as the development process!
SPFMatch is accessible at spfmatch.com.
For development, clone the repository. Run npm run dev in the root folder to start the development server.
The application is organized into several key modules:
Core Application (App.tsx)
- Single-page application with centralized state management
- Manages navigation between pages (home, quiz, results, resources, reminder)
- Stores quiz answers and calculated Fitzpatrick type
- Passes state and callbacks down through props
Components (/src/components/)
HomePage- Landing page with application introductionQuizPage- 11-question assessment for skin type determinationResultsPage- Displays personalized sunscreen recommendationsResourcesPage- Educational resources about sun protectionReminderPage- UV-based reapplication timer with geolocationNavigation- Shared navigation bar across all pages
Constants & Configuration (/src/constants/)
quiz.ts- Question definitions and scoring systemskinTypes.ts- Skin type information and characteristics
Utilities (/src/utils/)
fitzpatrick.ts- Fitzpatrick type calculation logic- Scoring algorithm maps quiz responses (0-40 points) to types I-VI
UI Components (/src/assets/)
- Reusable shadcn/ui-style components (button, card, badge, alert)
- Tailwind CSS utility functions
Types (/src/types/)
- TypeScript interfaces for quiz, sunscreen products, and resources
- Ensures type safety across the application
- User takes quiz →
QuizPagecollects answers - Answers passed to
fitzpatrick.ts→ calculates Fitzpatrick type - Type and skin characteristics →
ResultsPagefilters recommendations - Recommendations sourced from
/public/data/sunscreen-database.json(synced from Google Sheets)
- TypeScript - Primary language for type-safe React components
- HTML - JSX/TSX markup within React components
- CSS - Tailwind CSS utility classes for styling
The backend uses a serverless architecture with Google Apps Script for triggers and GitHub Actions for data processing:
Google Apps Script
- Manual trigger functions for syncing data from Google Sheets
- Sends
repository_dispatchevents to GitHub Actions workflows - Functions:
triggerGitHubSync(),triggerResourcesSync(),triggerFullSync()
GitHub Actions Workflows (/.github/workflows/)
Sync Workflows:
sync-sunscreen-data.yml- Fetches sunscreen data from Google Sheetssync-resources.yml- Fetches educational resources from Google Sheets- Transform scripts (
/.github/scripts/) convert sheet data to JSON - Commits transformed data back to repository
- Includes retry logic for handling race conditions
Deploy Workflow:
deploy.yml- Builds and deploys to GitHub Pages- Triggered by: pushes to main, manual dispatch, sync workflow completions
- Builds Vite app and uploads to GitHub Pages
Data Transformation Scripts (/.github/scripts/)
transform-sunscreen-data.cjs- Parses sunscreen sheet, groups by Fitzpatrick type and skin typetransform-resources.cjs- Parses resources sheet into simple title/link format- Output: Static JSON files in
/public/data/
Google Sheets → Apps Script → GitHub API (repository_dispatch)
↓
GitHub Actions (fetch & transform) → Commit JSON → Push to main
↓
Deploy Workflow → Build Vite App → GitHub Pages
- API keys stored as GitHub Secrets - Not exposed in frontend code
- Static JSON generation - No runtime API calls from browser
- GitHub PAT - Secured in Google Apps Script (not committed)
- Repository dispatch - Secure trigger mechanism between services
- JavaScript - Google Apps Script for triggers
- JavaScript (Node.js) - GitHub Actions transformation scripts
- Shell/Bash - GitHub Actions workflow orchestration
Files: src/tests.ts and run-tests.js
Tests:
- Fitzpatrick Type I (score 0-7)
- Fitzpatrick Type II (score 8-16)
- Fitzpatrick Type III (score 17-25)
- Fitzpatrick Type IV (score 26-30)
- Fitzpatrick Type VI (score 35+)
- Oily skin type extraction
- Dry skin type extraction
- Combination skin type extraction
- Sensitive skin type extraction
- Default normal skin type extraction
How to Test:
- Run
node run-tests.js
Expected Output: All 10 tests pass with "✅ All tests passed!" message

