Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions .github/workflows/lighthouse.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Lighthouse CI

on:
pull_request:
branches: ['**']
paths:
- 'apps/guides/**'
- 'apps/landing/**'
- 'packages/web-ui/**'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
lighthouse-guides:
name: Lighthouse (guides)
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/guides
steps:
- uses: actions/checkout@v6

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
cache: true

- name: Install dependencies
env:
PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN: ${{ secrets.PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN }}
run: bun install --frozen-lockfile
working-directory: .

- name: Build
run: bun run build

- name: Lighthouse — desktop
run: bunx lhci autorun --config=.lighthouserc.js
continue-on-error: true

- name: Lighthouse — mobile
run: bunx lhci autorun --config=.lighthouserc.mobile.js
continue-on-error: true

lighthouse-landing:
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
name: Lighthouse (landing)
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/landing
steps:
- uses: actions/checkout@v6

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
cache: true

- name: Install dependencies
env:
PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN: ${{ secrets.PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN }}
run: bun install --frozen-lockfile
working-directory: .

- name: Build
run: bun run build

- name: Lighthouse — desktop
run: bunx lhci autorun --config=.lighthouserc.js
continue-on-error: true

- name: Lighthouse — mobile
run: bunx lhci autorun --config=.lighthouserc.mobile.js
continue-on-error: true
4 changes: 2 additions & 2 deletions apps/expo/app/(app)/(tabs)/profile/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { cn } from 'expo-app/lib/cn';
import { hasUnsyncedChanges } from 'expo-app/lib/hasUnsyncedChanges';
import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme';
import { useTranslation } from 'expo-app/lib/hooks/useTranslation';
import { TestIds } from 'expo-app/lib/testIds';
import { testIds } from 'expo-app/lib/testIds';
import { buildPackTemplateItemImageUrl } from 'expo-app/lib/utils/buildPackTemplateItemImageUrl';
import * as FileSystem from 'expo-file-system/legacy';
import { Link, router, Stack } from 'expo-router';
Expand Down Expand Up @@ -290,7 +290,7 @@ function ListFooterComponent() {
<>
<View className="ios:px-0 px-4 pt-8">
<Button
testID={TestIds.SignOutButton}
testID={testIds.profile.signOutBtn}
disabled={isSigningOut}
onPress={() => {
if (hasUnsyncedChanges()) {
Expand Down
4 changes: 2 additions & 2 deletions apps/expo/features/trips/components/TripForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Icon } from 'expo-app/components/Icon';
import { usePacks } from 'expo-app/features/packs/hooks/usePacks';
import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme';
import { useTranslation } from 'expo-app/lib/hooks/useTranslation';
import { TestIds } from 'expo-app/lib/testIds';
import { testIds } from 'expo-app/lib/testIds';
import { Stack, useRouter } from 'expo-router';
import { useEffect, useMemo, useState } from 'react';
import { Modal, Pressable, Text, View } from 'react-native';
Expand Down Expand Up @@ -386,7 +386,7 @@ export const TripForm = ({ trip }: { trip?: Trip }) => {
<form.Subscribe selector={(s) => [s.canSubmit, s.isSubmitting]}>
{([canSubmit, isSubmitting]) => (
<Pressable
testID={TestIds.SubmitTripButton}
testID={testIds.trips.submitBtn}
onPress={() => form.handleSubmit()}
disabled={!canSubmit || isSubmitting}
className={`mt-6 rounded-lg px-4 py-3.5 ${
Expand Down
39 changes: 39 additions & 0 deletions apps/guides/.lighthouserc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/** @type {import('@lhci/cli').LhciConfig} */
module.exports = {
ci: {
collect: {
staticDistDir: './out',
numberOfRuns: 3,
settings: {
formFactor: 'desktop',
screenEmulation: {
mobile: false,
width: 1350,
height: 940,
deviceScaleFactor: 1,
disabled: false,
},
throttling: { rttMs: 40, throughputKbps: 10240, cpuSlowdownMultiplier: 1 },
},
},
assert: {
assertions: {
'categories:performance': ['error', { minScore: 0.8 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
'categories:best-practices': ['error', { minScore: 0.9 }],
'categories:seo': ['error', { minScore: 0.9 }],
'first-contentful-paint': ['error', { maxNumericValue: 2000 }],
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'total-blocking-time': ['error', { maxNumericValue: 300 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
'meta-description': 'error',
'document-title': 'error',
'html-has-lang': 'error',
'image-alt': 'error',
},
},
upload: {
target: 'temporary-public-storage',
},
},
};
39 changes: 39 additions & 0 deletions apps/guides/.lighthouserc.mobile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/** @type {import('@lhci/cli').LhciConfig} */
module.exports = {
ci: {
collect: {
staticDistDir: './out',
numberOfRuns: 3,
settings: {
formFactor: 'mobile',
screenEmulation: {
mobile: true,
width: 390,
height: 844,
deviceScaleFactor: 3,
disabled: false,
},
throttling: { rttMs: 150, throughputKbps: 1638.4, cpuSlowdownMultiplier: 4 },
},
},
assert: {
assertions: {
'categories:performance': ['error', { minScore: 0.8 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
'categories:best-practices': ['error', { minScore: 0.9 }],
'categories:seo': ['error', { minScore: 0.9 }],
'first-contentful-paint': ['error', { maxNumericValue: 3000 }],
'largest-contentful-paint': ['error', { maxNumericValue: 4000 }],
'total-blocking-time': ['error', { maxNumericValue: 600 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
'meta-description': 'error',
'document-title': 'error',
'html-has-lang': 'error',
'image-alt': 'error',
},
},
upload: {
target: 'temporary-public-storage',
},
},
};
110 changes: 110 additions & 0 deletions apps/guides/app/guide/[slug]/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { getAllPosts, getPostBySlug } from 'guides-app/lib/mdx-static';
import { ImageResponse } from 'next/og';

export const dynamic = 'force-static';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';

export async function generateStaticParams() {
return getAllPosts().map((post) => ({ slug: post.slug }));
}

export default async function Image({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = getPostBySlug(slug);

const title = post?.title ?? 'PackRat Guides';
const description = post?.description ?? 'Expert hiking and outdoor guides';
const categories = post?.categories ?? [];

return new ImageResponse(
<div
style={{
background: 'linear-gradient(135deg, #1E3A5F 0%, #1a56a0 60%, #0284C7 100%)',
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
fontFamily: 'system-ui, -apple-system, sans-serif',
padding: '64px',
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '12px',
}}
>
<div style={{ fontSize: '28px' }}>🏔️</div>
<div
style={{
fontSize: '26px',
fontWeight: 600,
color: 'rgba(255,255,255,0.8)',
}}
>
PackRat Guides
</div>
</div>

<div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
{categories.length > 0 && (
<div style={{ display: 'flex', gap: '12px' }}>
{categories.slice(0, 3).map((cat) => (
<div
key={cat}
style={{
background: 'rgba(255,255,255,0.18)',
color: 'rgba(255,255,255,0.9)',
fontSize: '18px',
fontWeight: 500,
padding: '6px 16px',
borderRadius: '100px',
}}
>
{cat}
</div>
))}
</div>
)}
<div
style={{
fontSize: title.length > 50 ? '44px' : '56px',
fontWeight: 700,
color: 'white',
lineHeight: 1.15,
letterSpacing: '-1px',
maxWidth: '900px',
}}
>
{title}
</div>
<div
style={{
fontSize: '24px',
color: 'rgba(255,255,255,0.8)',
lineHeight: 1.4,
maxWidth: '820px',
}}
>
{description.length > 120 ? `${description.slice(0, 117)}...` : description}
</div>
</div>

<div
style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
color: 'rgba(255,255,255,0.6)',
fontSize: '18px',
}}
>
guides.packratai.com
</div>
</div>,
{ ...size },
);
}
23 changes: 18 additions & 5 deletions apps/guides/app/guide/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Badge } from '@packrat/web-ui/components/badge';
import { Button } from '@packrat/web-ui/components/button';
import { format } from 'date-fns';
import GuideCard from 'guides-app/components/guide-card';
import { siteConfig } from 'guides-app/lib/config';
import {
getAllPosts,
getMdxContent,
Expand All @@ -21,18 +22,30 @@ export async function generateStaticParams() {

export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;

const post = getPostBySlug(slug);

if (!post) {
return {
title: 'Guide Not Found',
};
return { title: 'Guide Not Found' };
}

return {
title: `${post.title} | PackRat Guides`,
title: post.title,
description: post.description,
openGraph: {
type: 'article',
title: post.title,
description: post.description,
url: `${siteConfig.url}/guide/${slug}`,
siteName: 'PackRat Guides',
publishedTime: post.date,
tags: post.categories,
},
Comment on lines +37 to +42
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.description,
creator: '@packratai',
},
};
}

Expand Down
Loading
Loading