Function bodies 92 total
AuthNavigator function · typescript · L80-L96 (17 LOC)apps/mobile/App.tsx
function AuthNavigator({ hasSeenOnboarding }: { hasSeenOnboarding: boolean }) {
return (
<AuthStack.Navigator
initialRouteName={hasSeenOnboarding ? "Welcome" : "Onboarding"}
screenOptions={{
headerShown: false,
contentStyle: { backgroundColor: "#FFFFFF" },
animation: "slide_from_right",
}}
>
<AuthStack.Screen name="Onboarding" component={OnboardingScreen} />
<AuthStack.Screen name="Welcome" component={WelcomeScreen} />
<AuthStack.Screen name="SignUp" component={SignUpScreen} />
<AuthStack.Screen name="Login" component={LoginScreen} />
</AuthStack.Navigator>
);
}AppNavigator function · typescript · L98-L132 (35 LOC)apps/mobile/App.tsx
function AppNavigator() {
return (
<AppStack.Navigator
screenOptions={{
headerShown: false,
contentStyle: { backgroundColor: "#FFFFFF" },
}}
>
<AppStack.Screen name="Home" component={HomeScreen} />
<AppStack.Screen name="PrepHome" component={PrepHomeScreen} />
<AppStack.Screen name="Pricing" component={PricingScreen} />
<AppStack.Screen name="PricingResults" component={PricingResultsScreen} />
<AppStack.Screen name="ListingBuilder" component={ListingBuilderScreen} />
<AppStack.Screen name="Documents" component={DocumentsScreen} />
<AppStack.Screen name="DocumentViewer" component={DocumentViewerScreen} />
<AppStack.Screen name="Showings" component={ShowingsScreen} />
<AppStack.Screen name="ShowingDetail" component={ShowingDetailScreen} />
<AppStack.Screen name="Offers" component={OffersScreen} />
<AppStack.Screen name="OfferInput" component={OfferInputScreen} />
<AppStack.ScreenRootNavigator function · typescript · L134-L157 (24 LOC)apps/mobile/App.tsx
function RootNavigator() {
const { session, loading: authLoading } = useAuth();
const [hasSeenOnboarding, setHasSeenOnboarding] = useState<boolean | null>(null);
useEffect(() => {
AsyncStorage.getItem(ONBOARDING_KEY)
.then((value) => setHasSeenOnboarding(value === "true"))
.catch(() => setHasSeenOnboarding(false));
}, []);
if (authLoading || hasSeenOnboarding === null) {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: "#FFFFFF" }}>
<ActivityIndicator size="large" color="#2563EB" />
</View>
);
}
return session ? (
<AppNavigator />
) : (
<AuthNavigator hasSeenOnboarding={hasSeenOnboarding} />
);
}App function · typescript · L159-L168 (10 LOC)apps/mobile/App.tsx
export default function App() {
return (
<AuthProvider>
<NavigationContainer>
<RootNavigator />
<StatusBar style="dark" />
</NavigationContainer>
</AuthProvider>
);
}AddressAutocomplete function · typescript · L46-L231 (186 LOC)apps/mobile/src/components/AddressAutocomplete.tsx
export default function AddressAutocomplete({
value,
onChangeText,
onSelect,
onPropertyData,
placeholder = "Enter address",
style,
}: AddressAutocompleteProps) {
const [predictions, setPredictions] = useState<Prediction[]>([]);
const [loading, setLoading] = useState(false);
const [showDropdown, setShowDropdown] = useState(false);
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const abortRef = useRef<AbortController | null>(null);
// Cleanup on unmount
useEffect(() => {
return () => {
if (debounceRef.current) clearTimeout(debounceRef.current);
if (abortRef.current) abortRef.current.abort();
};
}, []);
const fetchPredictions = useCallback(async (input: string) => {
if (input.length < 3) {
setPredictions([]);
setShowDropdown(false);
return;
}
// Cancel any in-flight request
if (abortRef.current) abortRef.current.abort();
abortRef.current = new AbortController();
setLoadTierGate function · typescript · L23-L66 (44 LOC)apps/mobile/src/components/TierGate.tsx
export default function TierGate({ feature, children }: TierGateProps) {
const { loading, canAccess, requiredPlan } = useTierAccess();
const navigation = useNavigation<NavProp>();
if (loading) {
return (
<View style={styles.centered}>
<ActivityIndicator size="large" color={colors.primaryLight} />
</View>
);
}
if (canAccess(feature)) {
return <>{children}</>;
}
const needed = requiredPlan(feature);
const label = PLAN_LABELS[needed];
return (
<View style={styles.centered}>
<TouchableOpacity
style={styles.backButton}
onPress={() => navigation.goBack()}
>
<Text style={styles.backText}>{"\u2190"} Back</Text>
</TouchableOpacity>
<View style={[styles.card, shadows.lg]}>
<Text style={styles.lockIcon}>{"\uD83D\uDD12"}</Text>
<Text style={styles.title}>Upgrade to {label}</Text>
<Text style={styles.description}>
This feature requires the {label} plan or higheAuthProvider function · typescript · L16-L65 (50 LOC)apps/mobile/src/contexts/AuthContext.tsx
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [session, setSession] = useState<Session | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
setLoading(false);
});
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});
return () => subscription.unsubscribe();
}, []);
const signUp = async (email: string, password: string) => {
const { error } = await supabase.auth.signUp({ email, password });
if (error) return { error: error.message };
return { error: null };
};
const signIn = async (email: string, password: string) => {
const { error } = await supabase.auth.signInWithPassword({ email, password });
if (error) return { error: error.message };
return { error: null };
};
const signOut = async ()Open data scored by Repobility · https://repobility.com
useAuth function · typescript · L67-L73 (7 LOC)apps/mobile/src/contexts/AuthContext.tsx
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
}planLevel function · typescript · L46-L48 (3 LOC)apps/mobile/src/hooks/useTierAccess.ts
function planLevel(plan: PlanType): number {
return PLAN_HIERARCHY.indexOf(plan);
}useTierAccess function · typescript · L50-L114 (65 LOC)apps/mobile/src/hooks/useTierAccess.ts
export function useTierAccess() {
const { user } = useAuth();
const [plan, setPlan] = useState<PlanType>("free");
const [loading, setLoading] = useState(true);
useFocusEffect(
useCallback(() => {
if (!user) {
setPlan("free");
setLoading(false);
return;
}
let cancelled = false;
const fetchPlan = async () => {
setLoading(true);
try {
const { data, error } = await supabase
.from("profiles")
.select("plan")
.eq("id", user.id)
.single();
if (!cancelled) {
if (error || !data?.plan) {
setPlan("free");
} else {
setPlan(data.plan as PlanType);
}
}
} catch {
if (!cancelled) {
setPlan("free");
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
};
fetchPlan();
return ()BookShowingScreen function · typescript · L50-L341 (292 LOC)apps/mobile/src/screens/BookShowingScreen.tsx
export default function BookShowingScreen() {
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const route = useRoute<RouteProp<AppStackParamList, 'BookShowing'>>();
const { listingId } = route.params;
const [listing, setListing] = useState<ListingSummary | null>(null);
const [slots, setSlots] = useState<DateGroup[]>([]);
const [loadingSlots, setLoadingSlots] = useState(true);
const [selectedSlot, setSelectedSlot] = useState<TimeSlot | null>(null);
// Form fields
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [phone, setPhone] = useState('');
const [submitting, setSubmitting] = useState(false);
const [success, setSuccess] = useState(false);
const [bookedDetails, setBookedDetails] = useState<{ date: string; time: string } | null>(null);
// Fetch listing summary
useEffect(() => {
(async () => {
try {
const { data } = await supabase
.from('listings')
ClosingGuideScreen function · typescript · L23-L208 (186 LOC)apps/mobile/src/screens/ClosingGuideScreen.tsx
export default function ClosingGuideScreen() {
const { user } = useAuth();
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const route = useRoute<RouteProp<AppStackParamList, "ClosingGuide">>();
const { listingId } = route.params;
const [guide, setGuide] = useState<ClosingGuideStep[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchGuide = useCallback(
async (forceRefresh = false) => {
if (!user || !listingId) return;
setLoading(true);
setError(null);
try {
if (!forceRefresh) {
const { data: cached } = await supabase
.from("closing_guides")
.select("*")
.eq("user_id", user.id)
.eq("listing_id", listingId)
.single();
if (cached?.guide_content) {
setGuide(cached.guide_content as ClosingGuideStep[]);
setLoading(false);
ContactSellerScreen function · typescript · L39-L287 (249 LOC)apps/mobile/src/screens/ContactSellerScreen.tsx
export default function ContactSellerScreen() {
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const route = useRoute<RouteProp<AppStackParamList, "ContactSeller">>();
const { listingId } = route.params;
const { user } = useAuth();
const [listing, setListing] = useState<ListingSummary | null>(null);
const [loadingListing, setLoadingListing] = useState(true);
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [message, setMessage] = useState("");
const [sending, setSending] = useState(false);
const [sent, setSent] = useState(false);
const [error, setError] = useState("");
// Pre-fill user info
useEffect(() => {
if (user?.email) setEmail(user.email);
(async () => {
if (user) {
const { data: profile } = await supabase
.from("profiles")
.select("full_name")
.eq("id", user.id)
.single();
if (profile?.full_name) setName(profformatMessageTime function · typescript · L40-L61 (22 LOC)apps/mobile/src/screens/ConversationScreen.tsx
function formatMessageTime(iso: string): string {
const date = new Date(iso);
const now = new Date();
const isToday =
date.getDate() === now.getDate() &&
date.getMonth() === now.getMonth() &&
date.getFullYear() === now.getFullYear();
const time = date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
if (isToday) return time;
const yesterday = new Date(now);
yesterday.setDate(yesterday.getDate() - 1);
const isYesterday =
date.getDate() === yesterday.getDate() &&
date.getMonth() === yesterday.getMonth() &&
date.getFullYear() === yesterday.getFullYear();
if (isYesterday) return `Yesterday ${time}`;
return `${date.toLocaleDateString()} ${time}`;
}ConversationScreen function · typescript · L67-L341 (275 LOC)apps/mobile/src/screens/ConversationScreen.tsx
export default function ConversationScreen() {
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const route = useRoute<RouteProp<AppStackParamList, "Conversation">>();
const { listingId, otherPartyId, otherPartyName } = route.params;
const { user } = useAuth();
const [messages, setMessages] = useState<Message[]>([]);
const [loading, setLoading] = useState(true);
const [text, setText] = useState("");
const [sending, setSending] = useState(false);
const [listingAddress, setListingAddress] = useState("");
const scrollViewRef = useRef<ScrollView>(null);
// Fetch listing address
useEffect(() => {
(async () => {
try {
const { data } = await supabase
.from("listings")
.select("address")
.eq("id", listingId)
.single();
if (data?.address) setListingAddress(data.address);
} catch (err) {
// Non-critical: address is supplementary info
}
})();
}, [lRepobility · severity-and-effort ranking · https://repobility.com
DocumentViewerScreen function · typescript · L51-L181 (131 LOC)apps/mobile/src/screens/DocumentViewerScreen.tsx
export default function DocumentViewerScreen() {
const route = useRoute<RouteProp<AppStackParamList, "DocumentViewer">>();
const navigation = useNavigation();
const { user } = useAuth();
const { document: doc } = route.params;
const [downloading, setDownloading] = useState(false);
const [sharing, setSharing] = useState(false);
const generatePdf = async () => {
const htmlContent = doc.html_content || `<pre>${doc.content}</pre>`;
const fullHtml = PDF_HTML_WRAPPER(htmlContent, doc.title || "Document");
const { uri } = await Print.printToFileAsync({ html: fullHtml });
// Upload to Supabase Storage
if (user) {
try {
const fileResponse = await fetch(uri);
const blob = await fileResponse.blob();
const filePath = `${user.id}/${doc.id}.pdf`;
await supabase.storage.from("documents").upload(filePath, blob, {
contentType: "application/pdf",
upsert: true,
});
const {
data: { pubListingDetailScreen function · typescript · L49-L281 (233 LOC)apps/mobile/src/screens/ListingDetailScreen.tsx
export default function ListingDetailScreen() {
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const route = useRoute<RouteProp<AppStackParamList, 'ListingDetail'>>();
const { listingId } = route.params;
const [listing, setListing] = useState<Listing | null>(null);
const [loading, setLoading] = useState(true);
const [fetchError, setFetchError] = useState(false);
const [activePhotoIndex, setActivePhotoIndex] = useState(0);
useEffect(() => {
(async () => {
try {
setFetchError(false);
const { data, error } = await supabase
.from('listings')
.select('*')
.eq('id', listingId)
.single();
if (error) {
setFetchError(true);
} else {
setListing(data as Listing);
}
} catch {
setFetchError(true);
} finally {
setLoading(false);
}
})();
}, [listingId]);
const handleShare = async () => {
if (!LoginScreen function · typescript · L23-L309 (287 LOC)apps/mobile/src/screens/LoginScreen.tsx
export default function LoginScreen({ navigation }: Props) {
const { signIn } = useAuth();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const [emailError, setEmailError] = useState("");
const [passwordError, setPasswordError] = useState("");
const [generalError, setGeneralError] = useState("");
const [emailFocused, setEmailFocused] = useState(false);
const [passwordFocused, setPasswordFocused] = useState(false);
// Forgot password state
const [showForgotPassword, setShowForgotPassword] = useState(false);
const [forgotEmail, setForgotEmail] = useState("");
const [forgotEmailFocused, setForgotEmailFocused] = useState(false);
const [forgotLoading, setForgotLoading] = useState(false);
const [forgotError, setForgotError] = useState("");
const [forgotSuccess, setForgotSuccess] = useState(false);
const passwordRef = useRef<TextInput>(null);
const clearErrors = () => {formatPrice function · typescript · L48-L53 (6 LOC)apps/mobile/src/screens/MarketplaceScreen.tsx
function formatPrice(price: number): string {
if (price >= 1_000_000) {
return `$${(price / 1_000_000).toFixed(2)}M`;
}
return `$${(price / 1_000).toFixed(0)}k`;
}matchesPriceFilter function · typescript · L55-L70 (16 LOC)apps/mobile/src/screens/MarketplaceScreen.tsx
function matchesPriceFilter(price: number, filter: PriceFilter): boolean {
switch (filter) {
case 'Any':
return true;
case 'Under 300k':
return price < 300_000;
case '300k-500k':
return price >= 300_000 && price < 500_000;
case '500k-750k':
return price >= 500_000 && price < 750_000;
case '750k+':
return price >= 750_000;
default:
return true;
}
}MarketplaceScreen function · typescript · L76-L328 (253 LOC)apps/mobile/src/screens/MarketplaceScreen.tsx
export default function MarketplaceScreen() {
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const [listings, setListings] = useState<Listing[]>([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [fetchError, setFetchError] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [propertyFilter, setPropertyFilter] = useState<PropertyFilter>('All');
const [priceFilter, setPriceFilter] = useState<PriceFilter>('Any');
const fetchListings = useCallback(async () => {
try {
setFetchError(false);
const { data, error } = await supabase
.from('listings')
.select('*')
.eq('status', 'active')
.order('created_at', { ascending: false });
if (error) {
setFetchError(true);
} else {
setListings((data as Listing[]) || []);
}
} catch {
setFetchError(true);
} finally {
sformatTimestamp function · typescript · L38-L51 (14 LOC)apps/mobile/src/screens/MessagesScreen.tsx
function formatTimestamp(iso: string): string {
const date = new Date(iso);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1) return "Just now";
if (diffMins < 60) return `${diffMins}m ago`;
if (diffHours < 24) return `${diffHours}h ago`;
if (diffDays < 7) return `${diffDays}d ago`;
return date.toLocaleDateString();
}truncate function · typescript · L53-L56 (4 LOC)apps/mobile/src/screens/MessagesScreen.tsx
function truncate(text: string, max: number): string {
if (text.length <= max) return text;
return text.slice(0, max).trimEnd() + "...";
}Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
MessagesScreen function · typescript · L62-L275 (214 LOC)apps/mobile/src/screens/MessagesScreen.tsx
export default function MessagesScreen() {
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const { user } = useAuth();
const [conversations, setConversations] = useState<Conversation[]>([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [fetchError, setFetchError] = useState(false);
const fetchConversations = useCallback(async () => {
if (!user) return;
try {
// Fetch messages involving the current user, grouped by listing + other party
const { data: messages, error } = await supabase
.from("messages")
.select("id, listing_id, sender_id, receiver_id, content, created_at, read, listing:listings(address)")
.or(`sender_id.eq.${user.id},receiver_id.eq.${user.id}`)
.order("created_at", { ascending: false });
if (error) {
setFetchError(true);
setLoading(false);
return;
}
if (!messages || meScoreCircle function · typescript · L22-L31 (10 LOC)apps/mobile/src/screens/OfferAnalysisScreen.tsx
function ScoreCircle({ score }: { score: number }) {
const color = score >= 8 ? colors.success : score >= 5 ? colors.warning : colors.error;
const bgColor = score >= 8 ? colors.accentLight : score >= 5 ? colors.amberLight : colors.errorLight;
return (
<View style={[styles.scoreCircle, { borderColor: color, backgroundColor: bgColor }]}>
<Text style={[styles.scoreNumber, { color }]}>{score}</Text>
<Text style={[styles.scoreDenom, { color: color + "99" }]}>/10</Text>
</View>
);
}OfferAnalysisScreen function · typescript · L33-L261 (229 LOC)apps/mobile/src/screens/OfferAnalysisScreen.tsx
export default function OfferAnalysisScreen() {
const { user } = useAuth();
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const route = useRoute<RouteProp<AppStackParamList, "OfferAnalysis">>();
const { listingId, offerId, analysis: passedAnalysis, offerInput } = route.params;
const [analysis, setAnalysis] = useState<OfferAnalysis | null>(passedAnalysis || null);
const [offer, setOffer] = useState<Offer | null>(null);
const [saving, setSaving] = useState(false);
const [saved, setSaved] = useState(!!offerId);
const [loading, setLoading] = useState(!!offerId && !passedAnalysis);
useEffect(() => {
if (offerId && !passedAnalysis) {
loadOffer();
}
}, [offerId]);
const loadOffer = async () => {
try {
const { data } = await supabase
.from("offers")
.select("*")
.eq("id", offerId)
.single();
if (data) {
setOffer(data);
setAnalysis({
score: dataScoreBadge function · typescript · L22-L35 (14 LOC)apps/mobile/src/screens/OfferCompareScreen.tsx
function ScoreBadge({ score, isBest }: { score: number; isBest: boolean }) {
const color = score >= 8 ? colors.success : score >= 5 ? colors.warning : colors.error;
const bg = isBest ? colors.accentLight : "transparent";
return (
<View
style={[
styles.cellScoreBadge,
{ backgroundColor: bg, borderColor: color },
]}
>
<Text style={[styles.cellScoreText, { color }]}>{score}/10</Text>
</View>
);
}OfferCompareScreen function · typescript · L122-L282 (161 LOC)apps/mobile/src/screens/OfferCompareScreen.tsx
export default function OfferCompareScreen() {
const { user } = useAuth();
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const route = useRoute<RouteProp<AppStackParamList, "OfferCompare">>();
const { listingId } = route.params;
const [offers, setOffers] = useState<Offer[]>([]);
const [loading, setLoading] = useState(true);
useFocusEffect(
useCallback(() => {
fetchOffers();
}, [user])
);
const fetchOffers = async () => {
if (!user) return;
try {
const { data } = await supabase
.from("offers")
.select("*")
.eq("listing_id", listingId)
.eq("user_id", user.id)
.order("created_at", { ascending: true });
setOffers(data ?? []);
} catch (err) {
Alert.alert("Error", "Failed to load offers. Please try again.");
} finally {
setLoading(false);
}
};
const bestOverallIndex = offers.length
? offers.reduce(
(best, o, i) => ((o.scOfferInputScreen function · typescript · L28-L285 (258 LOC)apps/mobile/src/screens/OfferInputScreen.tsx
export default function OfferInputScreen() {
const { user } = useAuth();
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const route = useRoute<RouteProp<AppStackParamList, "OfferInput">>();
const { listingId } = route.params;
const [listingPrice, setListingPrice] = useState<number>(0);
const [propertyAddress, setPropertyAddress] = useState("");
const [offeredPrice, setOfferedPrice] = useState("");
const [financingType, setFinancingType] = useState<FinancingType>("conventional");
const [downPaymentPct, setDownPaymentPct] = useState("");
const [inspectionContingency, setInspectionContingency] = useState(true);
const [appraisalContingency, setAppraisalContingency] = useState(true);
const [closingDate, setClosingDate] = useState("");
const [sellerConcessions, setSellerConcessions] = useState("");
const [notes, setNotes] = useState("");
const [analyzing, setAnalyzing] = useState(false);
useEffect(() => {
fetchListing()ScoreBadge function · typescript · L21-L29 (9 LOC)apps/mobile/src/screens/OffersScreen.tsx
function ScoreBadge({ score }: { score: number }) {
const color = score >= 8 ? colors.success : score >= 5 ? colors.warning : colors.error;
const bg = score >= 8 ? colors.accentLight : score >= 5 ? colors.amberLight : colors.errorLight;
return (
<View style={[styles.scoreBadge, { backgroundColor: bg, borderColor: color }]}>
<Text style={[styles.scoreBadgeText, { color }]}>{score}/10</Text>
</View>
);
}OffersScreen function · typescript · L31-L207 (177 LOC)apps/mobile/src/screens/OffersScreen.tsx
export default function OffersScreen() {
const { user } = useAuth();
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const [offers, setOffers] = useState<Offer[]>([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [listingId, setListingId] = useState<string | null>(null);
const [fetchError, setFetchError] = useState(false);
useFocusEffect(
useCallback(() => {
fetchOffers();
}, [user])
);
const fetchOffers = async () => {
if (!user) return;
setFetchError(false);
try {
const { data: listing } = await supabase
.from("listings")
.select("id, price")
.eq("user_id", user.id)
.order("created_at", { ascending: false })
.limit(1)
.single();
if (listing) {
setListingId(listing.id);
const { data, error } = await supabase
.from("offers")
.select("*")
.eAbout: code-quality intelligence by Repobility · https://repobility.com
OnboardingScreen function · typescript · L55-L156 (102 LOC)apps/mobile/src/screens/OnboardingScreen.tsx
export default function OnboardingScreen({ navigation }: Props) {
const scrollRef = useRef<ScrollView>(null);
const [activeIndex, setActiveIndex] = useState(0);
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
const offsetX = event.nativeEvent.contentOffset.x;
const index = Math.round(offsetX / SCREEN_WIDTH);
if (index !== activeIndex) {
setActiveIndex(index);
}
};
const handleNext = () => {
if (activeIndex < PAGES.length - 1) {
scrollRef.current?.scrollTo({
x: SCREEN_WIDTH * (activeIndex + 1),
animated: true,
});
} else {
finishOnboarding();
}
};
const handleSkip = () => {
finishOnboarding();
};
const finishOnboarding = async () => {
try {
await AsyncStorage.setItem(ONBOARDING_KEY, "true");
} catch {
// Continue even if storage fails
}
navigation.replace("Welcome");
};
const isLastPage = activeIndex === PAGES.length - 1;
return (
PrepHomeScreen function · typescript · L153-L411 (259 LOC)apps/mobile/src/screens/PrepHomeScreen.tsx
export default function PrepHomeScreen() {
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const { user } = useAuth();
const [checklist, setChecklist] = useState<ChecklistState>(() => {
const init: ChecklistState = {};
PREP_ITEMS.forEach((item) => (init[item.id] = false));
return init;
});
const [expandedId, setExpandedId] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
// Derived
const completedCount = useMemo(
() => Object.values(checklist).filter(Boolean).length,
[checklist],
);
const totalCount = PREP_ITEMS.length;
const allDone = completedCount === totalCount;
const progressPct = totalCount > 0 ? completedCount / totalCount : 0;
// ---- Supabase: load ----
useEffect(() => {
if (!user) return;
(async () => {
try {
const { data, error } = await supabase
.from('home_prep_checklist')
formatPrice function · typescript · L67-L75 (9 LOC)apps/mobile/src/screens/PricingResultsScreen.tsx
function formatPrice(price: number): string {
return (
"$" +
price.toLocaleString("en-US", {
minimumFractionDigits: 0,
maximumFractionDigits: 0,
})
);
}PricingResultsScreen function · typescript · L77-L287 (211 LOC)apps/mobile/src/screens/PricingResultsScreen.tsx
export default function PricingResultsScreen() {
const navigation =
useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const route = useRoute();
const { input, result } = route.params as {
input: PricingInput;
result: PricingResult;
};
const { user } = useAuth();
const [selected, setSelected] = useState<PriceType>("recommended");
const [saving, setSaving] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const selectedPrice = result[
PRICE_OPTIONS.find((o) => o.type === selected)!.key
] as number;
const handleConfirm = async () => {
if (!user) return;
setSaving(true);
setErrorMessage(null);
try {
const { error: insertError } = await supabase
.from("pricing_results")
.insert({
user_id: user.id,
address: input.address,
sqft: input.sqft,
bedrooms: input.bedrooms,
bathrooms: input.bathrooms,
year_builtPricingScreen function · typescript · L37-L295 (259 LOC)apps/mobile/src/screens/PricingScreen.tsx
export default function PricingScreen() {
const navigation =
useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const [address, setAddress] = useState("");
const [sqft, setSqft] = useState("");
const [bedrooms, setBedrooms] = useState("");
const [bathrooms, setBathrooms] = useState("");
const [yearBuilt, setYearBuilt] = useState("");
const [condition, setCondition] = useState<PropertyCondition>("Good");
const [features, setFeatures] = useState("");
const [loading, setLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const handleSubmit = async () => {
setErrorMessage(null);
if (!address || !sqft || !bedrooms || !bathrooms || !yearBuilt) {
setErrorMessage("Please fill in all property details to get an accurate pricing estimate.");
return;
}
const input: PricingInput = {
address,
sqft: parseInt(sqft, 10),
bedrooms: parseInt(bedrooms, 10),
bathrooms:ProfileScreen function · typescript · L36-L244 (209 LOC)apps/mobile/src/screens/ProfileScreen.tsx
export default function ProfileScreen() {
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const { user, signOut } = useAuth();
const [fullName, setFullName] = useState("");
const [phone, setPhone] = useState("");
const [plan, setPlan] = useState<Plan>("free");
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const email = user?.email ?? "";
const fetchProfile = useCallback(async () => {
if (!user) return;
try {
const { data } = await supabase
.from("profiles")
.select("full_name, phone, plan")
.eq("id", user.id)
.single();
if (data) {
setFullName(data.full_name ?? "");
setPhone(data.phone ?? "");
if (data.plan) setPlan(data.plan as Plan);
}
} catch {
// Continue with defaults if profile fetch fails
} finally {
setLoading(false);
}
}, [user]);
useFocusEffect(
useCallback(() => formatTime function · typescript · L21-L27 (7 LOC)apps/mobile/src/screens/ShowingDetailScreen.tsx
function formatTime(time: string): string {
const [h, m] = time.split(":");
const hour = parseInt(h, 10);
const ampm = hour >= 12 ? "PM" : "AM";
const hour12 = hour % 12 || 12;
return `${hour12}:${m} ${ampm}`;
}ShowingDetailScreen function · typescript · L29-L190 (162 LOC)apps/mobile/src/screens/ShowingDetailScreen.tsx
export default function ShowingDetailScreen() {
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const route = useRoute<RouteProp<AppStackParamList, "ShowingDetail">>();
const { user } = useAuth();
const [showing, setShowing] = useState<Showing | null>(null);
const [loading, setLoading] = useState(true);
const [cancelling, setCancelling] = useState(false);
const fetchShowing = useCallback(async () => {
try {
const { data } = await supabase
.from("showings")
.select("*")
.eq("id", route.params.showingId)
.single();
if (data) setShowing(data);
} catch {
// Will show "Showing not found" state
} finally {
setLoading(false);
}
}, [route.params.showingId]);
useEffect(() => {
fetchShowing();
}, [fetchShowing]);
const cancelShowing = () => {
Alert.alert("Cancel Showing", "Are you sure you want to cancel this showing?", [
{ text: "No", style: "cancel" }Open data scored by Repobility · https://repobility.com
getNext30Days function · typescript · L26-L35 (10 LOC)apps/mobile/src/screens/ShowingsScreen.tsx
function getNext30Days(): Date[] {
const days: Date[] = [];
const today = new Date();
for (let i = 0; i < 30; i++) {
const d = new Date(today);
d.setDate(today.getDate() + i);
days.push(d);
}
return days;
}formatDate function · typescript · L37-L39 (3 LOC)apps/mobile/src/screens/ShowingsScreen.tsx
function formatDate(date: Date): string {
return date.toISOString().split("T")[0];
}formatTime function · typescript · L51-L57 (7 LOC)apps/mobile/src/screens/ShowingsScreen.tsx
function formatTime(time: string): string {
const [h, m] = time.split(":");
const hour = parseInt(h, 10);
const ampm = hour >= 12 ? "PM" : "AM";
const hour12 = hour % 12 || 12;
return `${hour12}:${m} ${ampm}`;
}ShowingsScreen function · typescript · L73-L451 (379 LOC)apps/mobile/src/screens/ShowingsScreen.tsx
export default function ShowingsScreen() {
const { user } = useAuth();
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const [listingId, setListingId] = useState<string | null>(null);
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
const [timeWindows, setTimeWindows] = useState<TimeWindow[]>([]);
const [existingSlots, setExistingSlots] = useState<ShowingAvailability[]>([]);
const [showings, setShowings] = useState<Showing[]>([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [saving, setSaving] = useState(false);
const [copied, setCopied] = useState(false);
const [showTimePicker, setShowTimePicker] = useState<{ index: number; field: "startTime" | "endTime" } | null>(null);
const days = useRef(getNext30Days()).current;
const fetchData = useCallback(async () => {
if (!user) return;
try {
const { data: listing } = await supabase
SignUpScreen function · typescript · L22-L340 (319 LOC)apps/mobile/src/screens/SignUpScreen.tsx
export default function SignUpScreen({ navigation }: Props) {
const { signUp } = useAuth();
const [fullName, setFullName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const [nameError, setNameError] = useState("");
const [emailError, setEmailError] = useState("");
const [passwordError, setPasswordError] = useState("");
const [confirmError, setConfirmError] = useState("");
const [generalError, setGeneralError] = useState("");
const [nameFocused, setNameFocused] = useState(false);
const [emailFocused, setEmailFocused] = useState(false);
const [passwordFocused, setPasswordFocused] = useState(false);
const [confirmFocusSyndicationScreen function · typescript · L157-L320 (164 LOC)apps/mobile/src/screens/SyndicationScreen.tsx
export default function SyndicationScreen() {
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const { user } = useAuth();
const [listing, setListing] = useState<ListingSummary | null>(null);
const [expandedKey, setExpandedKey] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!user) return;
(async () => {
try {
const { data } = await supabase
.from('listings')
.select('*')
.eq('user_id', user.id)
.order('created_at', { ascending: false })
.limit(1)
.maybeSingle();
if (data) setListing(data as ListingSummary);
} catch (err) {
Alert.alert("Error", "Failed to load listing data. Please try again.");
} finally {
setLoading(false);
}
})();
}, [user]);
const toggleExpand = useCallback((key: string) => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeIUpgradeScreen function · typescript · L88-L316 (229 LOC)apps/mobile/src/screens/UpgradeScreen.tsx
export default function UpgradeScreen() {
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const { user } = useAuth();
const [modalVisible, setModalVisible] = useState(false);
const [selectedPlan, setSelectedPlan] = useState<Plan>("seller_pro");
const [email, setEmail] = useState("");
const [saving, setSaving] = useState(false);
const [confirmed, setConfirmed] = useState(false);
const [currentPlan, setCurrentPlan] = useState<Plan>("free");
useFocusEffect(
useCallback(() => {
if (!user) return;
(async () => {
try {
const { data } = await supabase
.from("profiles")
.select("plan")
.eq("id", user.id)
.single();
if (data?.plan) setCurrentPlan(data.plan as Plan);
} catch {
// Continue with default plan if fetch fails
}
})();
}, [user])
);
const handleUpgrade = (plan: Plan) => {
setSelectedPlan(plan);
WelcomeScreen function · typescript · L34-L97 (64 LOC)apps/mobile/src/screens/WelcomeScreen.tsx
export default function WelcomeScreen({ navigation }: Props) {
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
<View style={styles.content}>
{/* Brand */}
<View style={styles.brandSection}>
<Text style={styles.brandName}>Chiavo</Text>
<Text style={styles.tagline}>
Sell Your Home.{"\n"}Save Thousands.
</Text>
</View>
{/* Value Props */}
<View style={styles.featuresSection}>
{FEATURES.map((feature, index) => (
<View key={index} style={styles.featureRow}>
<View style={styles.featureIconContainer}>
<Text style={styles.featureIcon}>{feature.icon}</Text>
</View>
<View style={styles.featureText}>
<Text style={styles.featureTitle}>{feature.title}</Text>
<Text style={styles.featureSubtitle}>{feature.subtitle}</Text>
</View>
Repobility · severity-and-effort ranking · https://repobility.com
normalizeStepName function · typescript · L85-L87 (3 LOC)apps/web/src/app/api/closing/guide/route.ts
function normalizeStepName(step: string): string {
return step.toLowerCase().replace(/[^a-z\s]/g, "").trim();
}POST function · typescript · L160-L229 (70 LOC)apps/web/src/app/api/closing/guide/route.ts
export async function POST(request: NextRequest) {
try {
const body: GuideRequest = await request.json();
const { state, remaining_steps, property_address, sale_price } = body;
if (!remaining_steps || remaining_steps.length === 0) {
return json(
{ error: "At least one remaining step is required" },
400
);
}
let result: { guide: GuideStep[]; source?: string };
try {
const message = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 2500,
messages: [
{
role: "user",
content: `You are a friendly real estate closing advisor helping a FSBO (For Sale By Owner) seller understand what to expect during the closing process. Generate a personalized closing guide.
Property Details:
- Address: ${property_address || "Not specified"}
- Sale Price: $${sale_price ? sale_price.toLocaleString() : "Not specified"}
- State: ${state || "Not specified"}
The json function · typescript · L9-L11 (3 LOC)apps/web/src/app/api/_cors.ts
export function json(data: unknown, status = 200) {
return NextResponse.json(data, { status, headers: CORS_HEADERS });
}page 1 / 2next ›