← back to dramaasetterr__selfly-

Function bodies 92 total

All specs Real LLM only Function bodies
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.Screen
RootNavigator 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();

    setLoad
TierGate 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 highe
AuthProvider 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(prof
formatMessageTime 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
      }
    })();
  }, [l
Repobility · 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: { pub
ListingDetailScreen 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 {
      s
formatTimestamp 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 || me
ScoreCircle 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: data
ScoreBadge 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.sc
OfferInputScreen 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("*")
          .e
About: 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_built
PricingScreen 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 [confirmFocus
SyndicationScreen 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.easeI
UpgradeScreen 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 ›