Skip to content

Making Your First Contribution

This guide will walk you through making your first contribution to the Lonely Planet frontend.

  1. Complete the Environment Setup
  2. Read the Core Concepts
  3. Join the #lp-frontend Slack channel

Create a feature branch from main:

Terminal window
git checkout main
git pull origin main
git checkout -b fl-lpweb-1234 # Use your initials + ticket number

Branch naming convention:

  • Use your initials (e.g., fl for “First Last”)
  • Include JIRA ticket number (e.g., lpweb-1234)
  • For non-ticket work, use descriptive name (e.g., fl-fix-typo)
  1. Start Local Server

    Terminal window
    npm run dev
  2. Make Changes

    • Follow our Component Patterns
    • Use TypeScript for type safety
    • Add error handling with Airbrake
    • Include loading states where needed
  3. Verify Changes

    Terminal window
    # Run type checking
    npm run typecheck
    # Run linting
    npm run lint
    # Check formatting
    npm run format

Let’s walk through a typical component addition:

src/components/feature/index.astro
---
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:

src/components/feature/InteractiveFeature.tsx
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" />

Follow our commit message format:

Terminal window
# Format: [type]: description
git commit -m "feat: add interactive feature component"

Commit types:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • style: Code style changes
  • refactor: Code refactoring
  • test: Adding tests
  • chore: Maintenance tasks
  1. Push your branch:

    Terminal window
    git push origin fl-lpweb-1234
  2. 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/example
2. Verify feature loads correctly
3. Test error handling by disconnecting network
4. Verify loading states appear appropriately
## Screenshots
[Add relevant screenshots]

Your PR will automatically create a QA environment:

https://lp-frontend-astro-[branch-name].managed.nonprod.lonelyplanet.com

Use this URL for testing and share it with reviewers.

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
  1. Component Organization

    // ❌ Don't: Mix concerns
    const Component = () => {
    const [state, setState] = createSignal();
    const utils = makeUtils();
    return <div>{state()}</div>;
    };
    // ✅ Do: Separate concerns
    import { useUtils } from "./utils";
    const Component = () => {
    const [state, setState] = createSignal();
    const utils = useUtils();
    return <div>{state()}</div>;
    };
  2. 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 aggregation
    try {
    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 Airbrake
    try {
    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 production
    if (import.meta.env.DEV) {
    console.log("Debug info:", debugData);
    }

    Key principles:

    • Use console.error for 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
  3. Loading States

    // ❌ Don't: Leave users guessing
    const Component = () => {
    const [data, setData] = createSignal();
    return <div>{data()}</div>;
    };
    // ✅ Do: Show loading states and errors
    const Component = () => {
    const [data, setData] = createSignal();
    const [loading, setLoading] = createSignal(true);
    const [error, setError] = createSignal();
    return (
    <Show
    when={!loading()}
    fallback={<LoadingSpinner />}
    >
    <Show
    when={!error()}
    fallback={<ErrorMessage error={error()} />}
    >
    <div>{data()}</div>
    </Show>
    </Show>
    );
    };

If you’re stuck:

  1. Look for similar examples in the codebase
  2. Ask in #lp-frontend Slack channel