Skip to content

feat: Add trusted by section with live user count to landing page#104

Merged
cj-vana merged 2 commits intobetafrom
new-pr
Sep 29, 2025
Merged

feat: Add trusted by section with live user count to landing page#104
cj-vana merged 2 commits intobetafrom
new-pr

Conversation

@cj-vana
Copy link
Collaborator

@cj-vana cj-vana commented Sep 29, 2025

User description

Add trust-building section displaying live user statistics from Supabase auth.

  • Create TrustedBy component with animated user counter and use case cards
  • Add fetchUserCount() function to query Supabase for total registered users
  • Create get_total_user_count() RPC migration to access auth.users table
  • Position TrustedBy section between Features and GetStarted on landing page
  • Update README.md with "Trusted Worldwide" section
  • Display four use case categories: Production Companies, Corporate Events, Broadcast & Theater, and Freelancers
  • Implement smooth counter animation for visual engagement
  • Handle errors gracefully with fallback display

PR Type

Enhancement


Description

  • Add TrustedBy component with animated user counter

  • Create Supabase RPC function for user count

  • Display use case categories for different professionals

  • Position section between Features and GetStarted


Diagram Walkthrough

flowchart LR
  A["Landing Page"] --> B["TrustedBy Component"]
  B --> C["fetchUserCount()"]
  C --> D["Supabase RPC"]
  D --> E["auth.users table"]
  B --> F["Use Case Cards"]
  B --> G["Animated Counter"]
Loading

File Walkthrough

Relevant files
Enhancement
supabase.ts
Add user count fetching function                                                 

apps/web/src/lib/supabase.ts

  • Add fetchUserCount() function to query user statistics
  • Implement error handling with null return on failure
  • Use Supabase RPC call to get total user count
+21/-0   
TrustedBy.tsx
Create TrustedBy component with animations                             

apps/web/src/components/TrustedBy.tsx

  • Create new component with animated user counter
  • Display four use case categories with icons
  • Implement smooth counter animation over 2 seconds
  • Add responsive grid layout for use case cards
+130/-0 
Landing.tsx
Integrate TrustedBy into landing page                                       

apps/web/src/pages/Landing.tsx

  • Import and add TrustedBy component to landing page
  • Position between Features and GetStarted sections
+2/-0     
20250929000000_create_get_total_user_count_rpc.sql
Add user count RPC function                                                           

supabase/migrations/20250929000000_create_get_total_user_count_rpc.sql

  • Create RPC function to count users from auth.users table
  • Use SECURITY DEFINER for auth schema access
  • Return bigint count of all registered users
+15/-0   
Documentation
README.md
Add trusted worldwide section                                                       

README.md

  • Add "Trusted Worldwide" section describing global usage
  • Highlight different professional categories using SoundDocs
+4/-0     

Add trust-building section displaying live user statistics from Supabase auth.

  - Create TrustedBy component with animated user counter and use case cards
  - Add fetchUserCount() function to query Supabase for total registered users
  - Create get_total_user_count() RPC migration to access  auth.users table
  - Position TrustedBy section between Features and GetStarted on landing page
  - Update README.md with "Trusted Worldwide" section
  - Display four use case categories: Production Companies, Corporate Events, Broadcast & Theater, and Freelancers
  - Implement smooth counter animation for visual engagement
  - Handle errors gracefully with fallback display
@netlify
Copy link

netlify bot commented Sep 29, 2025

Deploy Preview for sounddocsbeta ready!

Name Link
🔨 Latest commit 747c0f3
🔍 Latest deploy log https://app.netlify.com/projects/sounddocsbeta/deploys/68dae3ddff31d40007861876
😎 Deploy Preview https://deploy-preview-104--sounddocsbeta.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@qodo-code-review
Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 Security concerns

SECURITY DEFINER usage:
The RPC function runs with elevated privileges. Ensure REVOKE ALL ON FUNCTION get_total_user_count() FROM PUBLIC; and explicitly GRANT EXECUTE only to the application role. Also set SET search_path within the function body or define it via function config to mitigate search_path risks. No other immediate concerns found.

⚡ Recommended focus areas for review

Type Safety

The RPC call supabase.rpc("get_total_user_count") likely returns bigint which can arrive as a string; returning it directly as number | null may cause type/runtime mismatches. Consider parsing and validating the RPC response.

export const fetchUserCount = async (): Promise<number | null> => {
  try {
    // Use RPC function to count unique users across all document types
    const { data, error } = await supabase.rpc("get_total_user_count");

    if (error) {
      console.error("Error fetching user count:", error);
      return null;
    }

    return data;
  } catch (err) {
Animation Logic

The counter animation increments from 0 every time userCount updates. For large counts, Math.floor(current) with fixed 60 steps may produce noticeable jumps; also no easing. Consider using requestAnimationFrame with time-based easing and handling re-renders/updates gracefully.

// Animated counter effect
useEffect(() => {
  if (userCount === null) return;

  const duration = 2000; // 2 seconds
  const steps = 60;
  const increment = userCount / steps;
  let current = 0;

  const timer = setInterval(() => {
    current += increment;
    if (current >= userCount) {
      setDisplayCount(userCount);
      clearInterval(timer);
    } else {
      setDisplayCount(Math.floor(current));
    }
  }, duration / steps);

  return () => clearInterval(timer);
}, [userCount]);
Security/Permissions

The RPC uses SECURITY DEFINER to access auth.users and returns total user count. Ensure execution privileges are restricted to needed roles only and set search_path inside the function to avoid search_path hijacking.

CREATE OR REPLACE FUNCTION get_total_user_count()
RETURNS bigint
LANGUAGE sql
SECURITY DEFINER
AS $$
  SELECT COUNT(*)
  FROM auth.users;
$$;

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Sep 29, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Avoid direct database queries on page load

The current implementation fetches the user count from the database on each page
load, which is unscalable. It is recommended to use a scheduled job to
periodically fetch and cache this value, serving the cached data to users to
reduce database load.

Examples:

apps/web/src/components/TrustedBy.tsx [10-18]
    const loadUserCount = async () => {
      const count = await fetchUserCount();
      if (count !== null) {
        setUserCount(count);
      }
    };

    loadUserCount();
  }, []);
apps/web/src/lib/supabase.ts [145-160]
export const fetchUserCount = async (): Promise<number | null> => {
  try {
    // Use RPC function to count unique users across all document types
    const { data, error } = await supabase.rpc("get_total_user_count");

    if (error) {
      console.error("Error fetching user count:", error);
      return null;
    }


 ... (clipped 6 lines)

Solution Walkthrough:

Before:

// In TrustedBy.tsx component
useEffect(() => {
  // On every component mount (i.e., page visit)
  const loadUserCount = async () => {
    // Directly fetch from the database via RPC
    const count = await fetchUserCount(); 
    setUserCount(count);
  };
  loadUserCount();
}, []);

// In supabase.ts
export const fetchUserCount = async () => {
  // Calls an RPC that runs: SELECT COUNT(*) FROM auth.users
  const { data } = await supabase.rpc("get_total_user_count");
  return data;
};

After:

// In a new scheduled job (e.g., cron)
async function updateUserCountCache() {
  // Runs periodically (e.g., every hour)
  const { data } = await supabase.rpc("get_total_user_count");
  // Store the result in a fast cache or a dedicated table
  await cache.set('total_user_count', data); 
}

// In an API endpoint to serve the cached value
async function getCachedUserCount() {
  return await cache.get('total_user_count');
}

// In TrustedBy.tsx component
useEffect(() => {
  // Fetch the fast, cached value instead of hitting the main DB
  const count = await getCachedUserCount(); 
  setUserCount(count);
}, []);
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical scalability issue in querying the database on every page load and proposes a robust architectural solution using caching, which is standard practice for this scenario.

High
General
Use requestAnimationFrame for smoother animation
Suggestion Impact:The commit replaced the setInterval logic with a requestAnimationFrame-driven animation loop, including timestamp-based progress calculation and cleanup via cancelAnimationFrame.

code diff:

+  // Animated counter effect using requestAnimationFrame for smoother animation
   useEffect(() => {
     if (userCount === null) return;
 
     const duration = 2000; // 2 seconds
-    const steps = 60;
-    const increment = userCount / steps;
-    let current = 0;
+    let animationFrameId: number;
+    let startTime: number | undefined;
 
-    const timer = setInterval(() => {
-      current += increment;
-      if (current >= userCount) {
-        setDisplayCount(userCount);
-        clearInterval(timer);
-      } else {
-        setDisplayCount(Math.floor(current));
+    const animate = (timestamp: number) => {
+      if (startTime === undefined) {
+        startTime = timestamp;
       }
-    }, duration / steps);
 
-    return () => clearInterval(timer);
+      const elapsed = timestamp - startTime;
+      const progress = Math.min(elapsed / duration, 1);
+      const currentDisplayCount = Math.floor(progress * userCount);
+
+      setDisplayCount(currentDisplayCount);
+
+      if (progress < 1) {
+        animationFrameId = requestAnimationFrame(animate);
+      }
+    };
+
+    animationFrameId = requestAnimationFrame(animate);
+
+    return () => cancelAnimationFrame(animationFrameId);
   }, [userCount]);

Replace setInterval with requestAnimationFrame for the counter animation to
improve smoothness and performance.

apps/web/src/components/TrustedBy.tsx [20-40]

 // Animated counter effect
 useEffect(() => {
   if (userCount === null) return;
 
   const duration = 2000; // 2 seconds
-  const steps = 60;
-  const increment = userCount / steps;
-  let current = 0;
+  let animationFrameId: number;
+  let startTime: number;
 
-  const timer = setInterval(() => {
-    current += increment;
-    if (current >= userCount) {
-      setDisplayCount(userCount);
-      clearInterval(timer);
-    } else {
-      setDisplayCount(Math.floor(current));
+  const animate = (timestamp: number) => {
+    if (startTime === undefined) {
+      startTime = timestamp;
     }
-  }, duration / steps);
 
-  return () => clearInterval(timer);
+    const elapsed = timestamp - startTime;
+    const progress = Math.min(elapsed / duration, 1);
+    const currentDisplayCount = Math.floor(progress * userCount);
+
+    setDisplayCount(currentDisplayCount);
+
+    if (progress < 1) {
+      animationFrameId = requestAnimationFrame(animate);
+    }
+  };
+
+  animationFrameId = requestAnimationFrame(animate);
+
+  return () => cancelAnimationFrame(animationFrameId);
 }, [userCount]);

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that using requestAnimationFrame is better for animations than setInterval, leading to a smoother user experience and better performance.

Low
  • Update

@cj-vana cj-vana merged commit 50a93af into beta Sep 29, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant