Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
EXPO_PUBLIC_POSTHOG_KEY=phc_your_project_token_here
EXPO_PUBLIC_POSTHOG_HOST=https://your-posthog-host
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ expo-env.d.ts
# @end expo-cli

ios/
android/
android/
.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const appConfig = require("./app.json");

module.exports = () => ({
...appConfig.expo,
extra: {
...appConfig.expo.extra,
posthogKey: process.env.EXPO_PUBLIC_POSTHOG_KEY,
posthogHost: process.env.EXPO_PUBLIC_POSTHOG_HOST,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { parseTitle } from "@/lib/text";
import { Colors } from "@/constants/Colors";
import { Comments } from "@/components/comments/comments";
import { getItemDetailsQueryKey, getItemQueryFn } from "@/constants/item";
import { posthog } from "@/lib/posthog";

export default function ItemDetails() {
const { itemId } = useLocalSearchParams();
Expand Down Expand Up @@ -72,7 +73,12 @@ export default function ItemDetails() {
marginBottom: typeof item.text === "string" ? 0 : 24,
}}
>
<Pressable onPress={() => router.push(`/users/${item.by}`)}>
<Pressable
onPress={() => {
posthog?.capture("profile_opened", { source: "story_details" });
router.push(`/users/${item.by}`);
}}
>
<Text
style={{
fontSize: 16,
Expand Down Expand Up @@ -163,6 +169,9 @@ export default function ItemDetails() {
<Pressable
style={[styles.baseButton, styles.link]}
onPress={() => {
posthog?.capture("story_link_opened", {
source: "story_details",
});
Linking.openURL(item.url);
}}
>
Expand Down Expand Up @@ -194,7 +203,10 @@ export default function ItemDetails() {
gap: 4,
marginBottom: 24,
}}
onPress={() => router.push(`../${parentItem.id}`)}
onPress={() => {
posthog?.capture("parent_thread_opened");
router.push(`../${parentItem.id}`);
}}
>
<View
style={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,44 @@ import {
useSafeAreaInsets,
} from "react-native-safe-area-context";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { PostHogProvider } from "posthog-react-native";

import { Colors } from "@/constants/Colors";
import { posthog } from "@/lib/posthog";

const queryClient = new QueryClient();

export default function Layout() {
const safeArea = useSafeAreaInsets();
const stack = (
<Stack
screenOptions={{
headerBackground: () => (
<View
style={{
backgroundColor: Colors.accent,
height: safeArea.top,
}}
/>
),
headerTintColor: "#f1f1f1",
headerBackButtonDisplayMode: "minimal",
headerStyle: {
backgroundColor: Colors.accent,
},
}}
/>
);

return (
<>
<QueryClientProvider client={queryClient}>
<SafeAreaProvider style={{ backgroundColor: "#fff5ee" }}>
<Stack
screenOptions={{
headerBackground: () => (
<View
style={{
backgroundColor: Colors.accent,
height: safeArea.top,
}}
/>
),
headerTintColor: "#f1f1f1",
headerBackButtonDisplayMode: "minimal",
headerStyle: {
backgroundColor: Colors.accent,
},
}}
/>
{posthog ? (
<PostHogProvider client={posthog}>{stack}</PostHogProvider>
) : (
stack
)}
</SafeAreaProvider>
</QueryClientProvider>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ListFilter, LucideIcon } from "lucide-react-native";

import { Colors } from "@/constants/Colors";
import { StoryType } from "@/constants/stories";
import { posthog } from "@/lib/posthog";

export type Option = {
id: StoryType;
Expand Down Expand Up @@ -86,6 +87,9 @@ export const StoriesSelect = ({
key={item.id}
style={[styles.option, isSelected && styles.optionSelected]}
onPress={() => {
posthog?.capture("story_feed_selected", {
story_type: item.id,
});
onChange(item.id);
setIsOpen(false);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { MessageSquareText } from "lucide-react-native";
import type { Item } from "@/shared/types";
import { Colors } from "@/constants/Colors";
import { getItemDetailsQueryKey, getItemQueryFn } from "@/constants/item";
import { posthog } from "@/lib/posthog";

export const Comment = (item: Item) => {
const QC = useQueryClient();
Expand All @@ -33,7 +34,10 @@ export const Comment = (item: Item) => {
<View style={{ flexDirection: "row", gap: 8, alignItems: "center" }}>
<Pressable
disabled={pathname.startsWith(`/users/${item.by}`)}
onPress={() => router.push(`/users/${item.by}`)}
onPress={() => {
posthog?.capture("profile_opened", { source: "comment" });
router.push(`/users/${item.by}`);
}}
>
<Text
style={{
Expand Down Expand Up @@ -99,6 +103,9 @@ export const Comment = (item: Item) => {
<Pressable
style={[styles.baseButton, styles.button]}
onPress={async () => {
posthog?.capture("story_details_opened", {
entry_point: "comment_replies",
});
await QC.prefetchQuery({
queryKey: getItemDetailsQueryKey(item.id),
queryFn: getItemQueryFn,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Link2, MessageSquareText } from "lucide-react-native";

import type { Item } from "@/shared/types";
import { getItemDetailsQueryKey, getItemQueryFn } from "@/constants/item";
import { posthog } from "@/lib/posthog";

export const Post = ({ id, title, url, score, text, kids }: Item) => {
const QC = useQueryClient();
Expand All @@ -22,7 +23,10 @@ export const Post = ({ id, title, url, score, text, kids }: Item) => {
return text === undefined;
}, [text]);

const navigateToDetails = async () => {
const navigateToDetails = async (
entryPoint: "story_title" | "comments_button"
) => {
posthog?.capture("story_details_opened", { entry_point: entryPoint });
await QC.prefetchQuery({
queryKey: getItemDetailsQueryKey(id),
queryFn: getItemQueryFn,
Expand All @@ -34,8 +38,12 @@ export const Post = ({ id, title, url, score, text, kids }: Item) => {
<View style={{ gap: 12 }}>
<Pressable
onPress={async () => {
if (isExternal) Linking.openURL(url);
else await navigateToDetails();
if (isExternal) {
posthog?.capture("story_link_opened", { source: "story_title" });
Linking.openURL(url);
} else {
await navigateToDetails("story_title");
}
}}
>
<Text style={{ color: "black", fontSize: 20, fontWeight: 500 }}>
Expand Down Expand Up @@ -66,7 +74,7 @@ export const Post = ({ id, title, url, score, text, kids }: Item) => {
<Pressable
style={[styles.baseButton, styles.button]}
onPress={async () => {
await navigateToDetails();
await navigateToDetails("comments_button");
}}
>
<MessageSquareText color="black" width={16} />
Expand All @@ -86,6 +94,7 @@ export const Post = ({ id, title, url, score, text, kids }: Item) => {
<Pressable
style={[styles.baseButton, styles.link]}
onPress={() => {
posthog?.capture("story_link_opened", { source: "link_button" });
Linking.openURL(url);
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Spinner } from "@/components/Spinner";
import type { Item } from "@/shared/types";
import { getItemDetails } from "@/api/endpoints";
import { ITEMS_PER_PAGE } from "@/constants/pagination";
import { posthogLogs } from "@/lib/posthog-logs";
import {
type StoryType,
MAP_STORY_TYPE_TO_STORY_ENDPOINTS,
Expand All @@ -29,11 +30,27 @@ export const Posts = ({ storyType }: { storyType: StoryType }) => {
const storyListQuery = useQuery({
queryKey: ["storyIds", storyType],
queryFn: async () => {
const getItemIds = MAP_STORY_TYPE_TO_STORY_ENDPOINTS[storyType];
const res = await getItemIds();
const topStories = await res.json();
const startedAt = Date.now();

return topStories;
try {
const getItemIds = MAP_STORY_TYPE_TO_STORY_ENDPOINTS[storyType];
const res = await getItemIds();
const topStories = await res.json();

posthogLogs.info("story feed loaded", {
story_type: storyType,
story_count: topStories.length,
duration_ms: Date.now() - startedAt,
});

return topStories;
} catch (error) {
posthogLogs.error("story feed load failed", {
story_type: storyType,
duration_ms: Date.now() - startedAt,
});
throw error;
}
},
});

Expand All @@ -42,18 +59,36 @@ export const Posts = ({ storyType }: { storyType: StoryType }) => {
queryFn: async ({ pageParam = 0 }) => {
if (!storyListQuery.data) return [];

const startedAt = Date.now();
const pageIds = storyListQuery.data.slice(
pageParam,
pageParam + ITEMS_PER_PAGE
);
const detailsResponses = await Promise.all(
pageIds.map((id) => getItemDetails(id))
);
const posts = await Promise.all(
detailsResponses.map((res) => res.json())
);

return posts;
try {
const detailsResponses = await Promise.all(
pageIds.map((id) => getItemDetails(id))
);
const posts = await Promise.all(
detailsResponses.map((res) => res.json())
);

posthogLogs.info("story page loaded", {
story_type: storyType,
page_offset: pageParam,
story_count: posts.length,
duration_ms: Date.now() - startedAt,
});

return posts;
} catch (error) {
posthogLogs.error("story page load failed", {
story_type: storyType,
page_offset: pageParam,
duration_ms: Date.now() - startedAt,
});
throw error;
}
},
getNextPageParam: (lastPage, allPages) => {
if (!storyListQuery.data) return undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { posthog } from "@/lib/posthog";

type LogAttributes = Record<string, string | number | boolean>;

export const posthogLogs = {
info: (message: string, attributes?: LogAttributes) =>
posthog?.logger.info(message, attributes),
error: (message: string, attributes?: LogAttributes) =>
posthog?.logger.error(message, attributes),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Constants from "expo-constants";
import PostHog from "posthog-react-native";

const extra = Constants.expoConfig?.extra;
const projectToken = extra?.posthogKey as string | undefined;
const host = extra?.posthogHost as string | undefined;

if (__DEV__ && !projectToken) {
console.error(
"EXPO_PUBLIC_POSTHOG_KEY variable required by PostHog is missing or un-configured, this causes events to be silently missed. This error stops appearing once EXPO_PUBLIC_POSTHOG_KEY is configured",
);
}

if (__DEV__ && !host) {
console.error(
"EXPO_PUBLIC_POSTHOG_HOST variable required by PostHog is missing or un-configured, this causes events to be silently missed. This error stops appearing once EXPO_PUBLIC_POSTHOG_HOST is configured",
);
}

export const posthog =
projectToken && host
? new PostHog(projectToken, {
host,
captureAppLifecycleEvents: true,
logs: {
serviceName: "hacker-native",
},
errorTracking: {
autocapture: {
uncaughtExceptions: true,
unhandledRejections: true,
},
},
})
: undefined;
Loading
Loading