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
8 changes: 8 additions & 0 deletions app.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = ({ config }) => ({
...config,
extra: {
...config.extra,
posthogProjectToken: process.env.POSTHOG_PROJECT_TOKEN,
posthogHost: process.env.POSTHOG_HOST || 'https://us.i.posthog.com',
},
})
15 changes: 12 additions & 3 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
"slug": "Subora",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"icon": "./assets/icons/logo.png",
"scheme": "subora",
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"ios": {
"bundleIdentifier": "com.devdotced.subora",
"supportsTablet": true
},
"android": {
"package": "com.devdotced.subora",
"adaptiveIcon": {
"backgroundColor": "#E6F4FE",
"foregroundImage": "./assets/images/android-icon-foreground.png",
Expand All @@ -23,7 +25,7 @@
},
"web": {
"output": "static",
"favicon": "./assets/images/favicon.png"
"favicon": "./assets/icons/logo.png"
},
"plugins": [
"expo-secure-store",
Expand All @@ -32,7 +34,7 @@
[
"expo-splash-screen",
{
"image": "./assets/images/avatar.png",
"image": "./assets/images/splash-pattern.png",
"imageWidth": 200,
"resizeMode": "contain",
"backgroundColor": "#ffffff",
Expand All @@ -58,6 +60,13 @@
"experiments": {
"typedRoutes": true,
"reactCompiler": true
},
"extra": {
"posthogHost": "https://us.i.posthog.com",
"router": {},
"eas": {
"projectId": "650d9d64-e1e8-45b8-a9d1-050283965b80"
}
}
}
}
11 changes: 8 additions & 3 deletions app/(auth)/sign-in.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useSignIn } from "@clerk/expo";
import { type Href, Link, useRouter } from "expo-router";
import { styled } from "nativewind";
import React, { useState } from "react";
import { usePostHog } from "posthog-react-native";
import {
ActivityIndicator,
Image,
Expand Down Expand Up @@ -45,6 +46,7 @@ const MIN_PASSWORD_LENGTH = 8;
export default function SignIn() {
const { signIn, errors, fetchStatus } = useSignIn();
const router = useRouter();
const posthog = usePostHog();

// Basic sign in state
const [emailAddress, setEmailAddress] = useState("");
Expand Down Expand Up @@ -93,6 +95,7 @@ export default function SignIn() {
}

if (signIn.status === "complete") {
posthog.capture("user_signed_in", { method: "password" });
await signIn.finalize({
navigate: ({ session, decorateUrl }) => {
if (session?.currentTask) {
Expand Down Expand Up @@ -122,6 +125,7 @@ export default function SignIn() {
await signIn.mfa.verifyEmailCode({ code });

if (signIn.status === "complete") {
posthog.capture("user_signed_in", { method: "mfa" });
await signIn.finalize({
navigate: ({ session, decorateUrl }) => {
if (session?.currentTask) {
Expand Down Expand Up @@ -181,18 +185,19 @@ export default function SignIn() {
// Step 3: Submit new password
const handleSubmitNewPassword = async () => {
if (!canSubmitNewPassword) return;

const { error } = await signIn.resetPasswordEmailCode.submitPassword({
password: newPassword,
signOutOfOtherSessions: true,
});

if (error) {
console.error(JSON.stringify(error, null, 2));
return;
}

if (signIn.status === 'complete') {
posthog.capture("password_reset_completed");
const { error: finalizeError } = await signIn.finalize({
navigate: async ({ session, decorateUrl }) => {
if (session?.currentTask) {
Expand Down Expand Up @@ -500,7 +505,7 @@ export default function SignIn() {
<View className="auth-field">
<View className="flex-row items-center justify-between">
<Text className="auth-label">Password</Text>
<Pressable onPress={() => setForgotPasswordStep("email")}>
<Pressable onPress={() => { setForgotPasswordStep("email"); posthog.capture("password_reset_started"); }}>
<Text className="text-sm font-sans-semibold text-accent">Forgot?</Text>
</Pressable>
</View>
Expand Down
3 changes: 3 additions & 0 deletions app/(auth)/sign-up.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useAuth, useSignUp } from "@clerk/expo";
import { type Href, Link, useRouter } from "expo-router";
import { styled } from "nativewind";
import React, { useState } from "react";
import { usePostHog } from "posthog-react-native";
import {
ActivityIndicator,
Image,
Expand Down Expand Up @@ -30,6 +31,7 @@ export default function SignUp() {
const { signUp, errors, fetchStatus } = useSignUp();
const { isSignedIn } = useAuth();
const router = useRouter();
const posthog = usePostHog();

const [emailAddress, setEmailAddress] = useState("");
const [password, setPassword] = useState("");
Expand Down Expand Up @@ -72,6 +74,7 @@ export default function SignUp() {
await signUp.verifications.verifyEmailCode({ code });

if (signUp.status === "complete") {
posthog.capture("user_signed_up");
await signUp.finalize({
navigate: ({ session, decorateUrl }) => {
if (session?.currentTask) {
Expand Down
38 changes: 31 additions & 7 deletions app/(tabs)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,38 @@ import {
HOME_BALANCE,
HOME_SUBSCRIPTIONS,
UPCOMING_SUBSCRIPTIONS,
subscribeToSubscriptions,
} from "@/constants/data";
import icons from "@/constants/icons";
import "@/global.css";
import { formatCurrency } from "@/lib/utils/currency";
import CreateSubscriptionModal from "@/components/CreateSubscriptionModal";
import { useUser } from "@clerk/expo";
import dayjs from "dayjs";
import { useRouter } from "expo-router";
import { styled } from "nativewind";
import { useState } from "react";
import { FlatList, Image, Text, View } from "react-native";
import { useState, useEffect } from "react";
import { usePostHog } from "posthog-react-native";
import { FlatList, Image, Text, View, TouchableOpacity } from "react-native";
import { SafeAreaView as RNSafeAreaView } from "react-native-safe-area-context";

const SafeAreaView = styled(RNSafeAreaView);

export default function App() {
const router = useRouter();
const { user } = useUser();
const posthog = usePostHog();
const [expandedSubscriptionId, setExpandedSubscriptionId] = useState<
string | null
>(null);
const [subscriptions, setSubscriptions] = useState(HOME_SUBSCRIPTIONS);
const [isModalVisible, setModalVisible] = useState(false);

useEffect(() => {
return subscribeToSubscriptions(() => {
setSubscriptions([...HOME_SUBSCRIPTIONS]);
});
}, []);

return (
<SafeAreaView className="flex-1 bg-background p-5">
Expand All @@ -42,7 +54,9 @@ export default function App() {
{user?.firstName ?? user?.username ?? "User"}
</Text>
</View>
<Image source={icons.add} className="home-add-icon" />
<TouchableOpacity onPress={() => setModalVisible(true)}>
<Image source={icons.add} className="home-add-icon" />
</TouchableOpacity>
</View>

<View className="home-balance-card">
Expand Down Expand Up @@ -85,16 +99,22 @@ export default function App() {
/>
</>
)}
data={HOME_SUBSCRIPTIONS}
data={subscriptions}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<SubscriptionCard
{...item}
expanded={expandedSubscriptionId === item.id}
onPress={() => {
setExpandedSubscriptionId((currentId) =>
currentId === item.id ? null : item.id,
);
setExpandedSubscriptionId((currentId) => {
const isExpanding = currentId !== item.id;
if (isExpanding) {
posthog.capture("subscription_card_expanded", {
subscription_id: item.id,
});
}
return isExpanding ? item.id : null;
});
}}
/>
)}
Expand All @@ -105,6 +125,10 @@ export default function App() {
}
contentContainerClassName="pb-28"
/>
<CreateSubscriptionModal
visible={isModalVisible}
onClose={() => setModalVisible(false)}
/>
</SafeAreaView>
);
}
4 changes: 4 additions & 0 deletions app/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import "@/global.css";
import { useAuth, useUser } from "@clerk/expo";
import { styled } from "nativewind";
import React from "react";
import { usePostHog } from "posthog-react-native";
import { Image, ScrollView, Text, TouchableOpacity, View } from "react-native";
import { SafeAreaView as RNSafeAreaView } from "react-native-safe-area-context";

Expand Down Expand Up @@ -29,10 +30,13 @@ const InfoRow = ({
const Settings = () => {
const { signOut } = useAuth();
const { user } = useUser();
const posthog = usePostHog();

const handleSignOut = async () => {
try {
await signOut();
posthog.capture("user_signed_out");
posthog.reset();
} catch (e) {
console.error(e);
}
Expand Down
93 changes: 88 additions & 5 deletions app/(tabs)/subscriptions.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,98 @@
import SubscriptionCard from "@/components/SubscriptionCard";
import { HOME_SUBSCRIPTIONS, subscribeToSubscriptions } from "@/constants/data";
import { styled } from "nativewind";
import React from "react";
import { Text } from "react-native";
import React, { useState, useEffect } from "react";
import {
FlatList,
Keyboard,
KeyboardAvoidingView,
Platform,
Text,
TextInput,
TouchableWithoutFeedback,
View,
} from "react-native";
import { SafeAreaView as RNSafeAreaView } from "react-native-safe-area-context";

const SafeAreaView = styled(RNSafeAreaView);

const Subscriptions = () => {
const [searchQuery, setSearchQuery] = useState("");
const [expandedId, setExpandedId] = useState<string | null>(null);
const [subscriptions, setSubscriptions] = useState(HOME_SUBSCRIPTIONS);

useEffect(() => {
return subscribeToSubscriptions(() => {
setSubscriptions([...HOME_SUBSCRIPTIONS]);
});
}, []);

const filteredSubscriptions = subscriptions.filter((sub) =>
sub.name.toLowerCase().includes(searchQuery.toLowerCase()),
);

const handlePress = (id: string) => {
setExpandedId((prev) => (prev === id ? null : id));
};
Comment thread
devcedrick marked this conversation as resolved.

return (
<SafeAreaView>
<Text>Subscriptions</Text>
</SafeAreaView>
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === "ios" ? "padding" : undefined}
>
<SafeAreaView className="flex-1 bg-background">
<FlatList
keyboardShouldPersistTaps="handled"
data={filteredSubscriptions}
keyExtractor={(item) => item.id}
contentContainerStyle={{ padding: 20, paddingBottom: 125, gap: 15 }}
ListHeaderComponent={
<View className="pb-2">
<Text className="list-title mb-4 text-xl">Subscriptions</Text>
<View className="flex-row items-center rounded-2xl border border-border bg-card px-5 py-1">
<TextInput
className="flex-1 text-base font-sans-medium text-primary h-full"
placeholder="Search subscriptions..."
placeholderTextColor="rgba(0, 0, 0, 0.6)"
value={searchQuery}
onChangeText={setSearchQuery}
/>
</View>
</View>
}
renderItem={({ item }) => (
<SubscriptionCard
name={item.name}
price={item.price}
currency={item.currency}
icon={item.icon}
billing={item.billing}
color={item.color}
category={item.category}
plan={item.plan}
paymentMethod={item.paymentMethod}
renewalDate={item.renewalDate}
startDate={item.startDate}
status={item.status}
expanded={expandedId === item.id}
onPress={() => {
Keyboard.dismiss();
handlePress(item.id);
}}
/>
)}
ListEmptyComponent={
<View className="items-center mt-10">
<Text className="font-sans-medium text-muted-foreground text-base">
No subscriptions found
</Text>
</View>
}
/>
</SafeAreaView>
</KeyboardAvoidingView>
</TouchableWithoutFeedback>
);
};

Expand Down
8 changes: 7 additions & 1 deletion app/(tabs)/subscriptions/[id].tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { Link, useLocalSearchParams } from "expo-router";
import { styled } from "nativewind";
import React from "react";
import React, { useEffect } from "react";
import { Text } from "react-native";
import { SafeAreaView as RNSafeArea } from "react-native-safe-area-context";
import { usePostHog } from "posthog-react-native";

const SafeAreaView = styled(RNSafeArea);

const SubscriptionDetails = () => {
const { id } = useLocalSearchParams<{ id: string }>();
const posthog = usePostHog();

useEffect(() => {
posthog.capture("subscription_details_viewed", { subscription_id: id });
}, [id, posthog]);
Comment on lines +14 to +16
return (
<SafeAreaView className="items-center justify-center flex-1">
<Text>Subscription Details: {id}</Text>
Expand Down
Loading