Making Your First Contribution
This guide will walk you through making your first contribution to the Lonely Planet frontend.
Before You Start
Section titled “Before You Start”- Complete the Environment Setup
- Read the Core Concepts
- Join the #lp-frontend Slack channel
Contribution Flow
Section titled “Contribution Flow”1. Branch Creation
Section titled “1. Branch Creation”Create a feature branch from main:
git checkout maingit pull origin maingit checkout -b fl-lpweb-1234 # Use your initials + ticket numberBranch naming convention:
- Use your initials (e.g.,
flfor “First Last”) - Include JIRA ticket number (e.g.,
lpweb-1234) - For non-ticket work, use descriptive name (e.g.,
fl-fix-typo)
2. Development Process
Section titled “2. Development Process”-
Start Local Server
Terminal window npm run dev -
Make Changes
- Follow our Component Patterns
- Use TypeScript for type safety
- Add error handling with Airbrake
- Include loading states where needed
-
Verify Changes
Terminal window # Run type checkingnpm run typecheck# Run lintingnpm run lint# Check formattingnpm run format
3. Example Contribution
Section titled “3. Example Contribution”Let’s walk through a typical component addition:
---interface Props { title: string; description?: string;}
const { title, description } = Astro.props;---
<div class="py-4"> <h2 class="text-20 font-bold">{title}</h2> {description && <p class="mt-2 text-tertiary">{description}</p>}</div>For interactive features:
import { createSignal, type Component } from "solid-js";
interface Props { initialValue: string;}
export const InteractiveFeature: Component<Props> = (props) => { const [value, setValue] = createSignal(props.initialValue);
const handleUpdate = async () => { try { await updateValue(value()); } catch (err) { const { airbrakeClient } = await import("#utils/airbrake/browser"); airbrakeClient.notify({ error: err, context: { component: "InteractiveFeature" } }); } };
return ( <div> <input value={value()} onInput={(e) => setValue(e.currentTarget.value)} /> <button onClick={handleUpdate}>Update</button> </div> );};Usage in a page:
---import { Feature } from "#components/feature";import { InteractiveFeature } from "#components/feature/InteractiveFeature";---
<Feature title="Static Content" /><InteractiveFeature client:visible initialValue="Dynamic Content" />4. Commit Changes
Section titled “4. Commit Changes”Follow our commit message format:
# Format: [type]: descriptiongit commit -m "feat: add interactive feature component"Commit types:
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style changesrefactor: Code refactoringtest: Adding testschore: Maintenance tasks
5. Create Pull Request
Section titled “5. Create Pull Request”-
Push your branch:
Terminal window git push origin fl-lpweb-1234 -
Create PR on GitHub:
- Use the PR template
- Link JIRA ticket
- Add meaningful description
- Include QA steps
Example PR description:
## Changes
- Added new InteractiveFeature component- Implemented error handling with Airbrake- Added loading states
## QA Steps
1. Visit http://local.lonelyplanet.com:3000/example2. Verify feature loads correctly3. Test error handling by disconnecting network4. Verify loading states appear appropriately
## Screenshots
[Add relevant screenshots]6. QA Environment
Section titled “6. QA Environment”Your PR will automatically create a QA environment:
https://lp-frontend-astro-[branch-name].managed.nonprod.lonelyplanet.comUse this URL for testing and share it with reviewers.
Code Review Checklist
Section titled “Code Review Checklist”Before requesting review, verify:
- TypeScript types are defined
- Error handling is implemented
- Loading states are added
- Components follow naming conventions
- Code is properly formatted
- Tests pass (if applicable)
- PR description is complete
- QA environment is working
Common Feedback
Section titled “Common Feedback”-
Component Organization
// ❌ Don't: Mix concernsconst Component = () => {const [state, setState] = createSignal();const utils = makeUtils();return <div>{state()}</div>;};// ✅ Do: Separate concernsimport { useUtils } from "./utils";const Component = () => {const [state, setState] = createSignal();const utils = useUtils();return <div>{state()}</div>;}; -
Error Handling
We use different strategies for different types of errors:
// 1. Expected Errors (e.g., validation, network timeouts)// These are logged to stdout via pino and appear in our log aggregationtry {await validateInput(value);} catch (err) {console.error("Input validation failed", {value,error: err.message,component: "ValidationForm",});return { error: "Please check your input" };}// 2. Unexpected Errors (e.g., runtime errors, unhandled cases)// These need monitoring and alerts via Airbraketry {await criticalOperation();} catch (err) {const { airbrakeClient } = await import("#utils/airbrake/browser");airbrakeClient.notify({error: err,context: {component: "ComponentName",severity: "error",},});}// 3. Development-only Debugging// These should never reach productionif (import.meta.env.DEV) {console.log("Debug info:", debugData);}Key principles:
- Use
console.errorfor expected errors that need logging - Use Airbrake for unexpected errors that need monitoring
- Include relevant context in all error reporting
- Keep development-only logging behind env checks or remove prior to committing
- Use
-
Loading States
// ❌ Don't: Leave users guessingconst Component = () => {const [data, setData] = createSignal();return <div>{data()}</div>;};// ✅ Do: Show loading states and errorsconst Component = () => {const [data, setData] = createSignal();const [loading, setLoading] = createSignal(true);const [error, setError] = createSignal();return (<Showwhen={!loading()}fallback={<LoadingSpinner />}><Showwhen={!error()}fallback={<ErrorMessage error={error()} />}><div>{data()}</div></Show></Show>);};
Getting Help
Section titled “Getting Help”If you’re stuck:
- Look for similar examples in the codebase
- Ask in #lp-frontend Slack channel