From 55ce52f7a8bdd49e7bdde0c0e4458e38487b9245 Mon Sep 17 00:00:00 2001
From: sunith vs
Date: Fri, 7 Nov 2025 00:07:28 +0530
Subject: [PATCH 1/3] feat(profile): migrate user data handling from Nocodb to
Supabase
---
www/app/[username]/page.tsx | 5 +-
www/components/ProfileSection.tsx | 23 +++++-
www/example.env | 4 +-
www/lib/api.ts | 114 ++++++++++++++++++++++++++----
4 files changed, 127 insertions(+), 19 deletions(-)
diff --git a/www/app/[username]/page.tsx b/www/app/[username]/page.tsx
index 7997e0e7..d5b64de5 100644
--- a/www/app/[username]/page.tsx
+++ b/www/app/[username]/page.tsx
@@ -16,10 +16,13 @@ export const maxDuration = 60;
export default async function Page({
params,
+ searchParams,
}: {
params: Promise<{ username: string }>;
+ searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}) {
const { username } = await params;
+ const urlSearchParams = await searchParams;
if (!username) return null;
@@ -40,7 +43,7 @@ export default async function Page({
}>
-
+
diff --git a/www/components/ProfileSection.tsx b/www/components/ProfileSection.tsx
index ab5bb892..c066d08d 100644
--- a/www/components/ProfileSection.tsx
+++ b/www/components/ProfileSection.tsx
@@ -1,7 +1,7 @@
import Image from "next/image";
import { Github, Globe, Linkedin, Twitter, User, BookOpen, Instagram } from "lucide-react";
import { ProfileSkeleton } from "@/components/skeletons/profile-skeleton";
-import { addUserToNocodb, getUserProfile } from "@/lib/api";
+import { addUserToSupabase, getUserProfile } from "@/lib/api";
import ClientResumeButton from "@/components/ClientResumeButton";
import {
Tooltip,
@@ -38,9 +38,26 @@ const detectProvider = (url: string): string => {
return 'generic';
};
-export async function ProfileSection({ username }: { username: string }) {
+export async function ProfileSection({
+ username,
+ searchParams
+}: {
+ username: string;
+ searchParams?: { [key: string]: string | string[] | undefined };
+}) {
const user = await getUserProfile(username);
- await addUserToNocodb(user);
+
+ // Convert search params to URLSearchParams for easier handling
+ const urlSearchParams = new URLSearchParams();
+ if (searchParams) {
+ Object.entries(searchParams).forEach(([key, value]) => {
+ if (value && typeof value === 'string') {
+ urlSearchParams.set(key, value);
+ }
+ });
+ }
+
+ await addUserToSupabase(user, urlSearchParams);
if (!user) return ;
diff --git a/www/example.env b/www/example.env
index a00b9b57..b4141f2b 100644
--- a/www/example.env
+++ b/www/example.env
@@ -4,5 +4,5 @@ NEXT_PUBLIC_X_API_KEY=
NEXT_PUBLIC_CLARITY_ID=
NEXT_PUBLIC_GA_MEASUREMENT_ID=
-NOCODB_TABLE_ID=
-NOCODB_API_KEY=
\ No newline at end of file
+SUPABASE_URL=
+SUPABASE_KEY=
\ No newline at end of file
diff --git a/www/lib/api.ts b/www/lib/api.ts
index 5ff7cf9d..288b0bd4 100644
--- a/www/lib/api.ts
+++ b/www/lib/api.ts
@@ -9,6 +9,15 @@ import { parseStringPromise } from "xml2js";
const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL;
const API_KEY = process.env.NEXT_PUBLIC_X_API_KEY;
+// Utility function to detect provider from URL
+const detectProvider = (url: string): string => {
+ const urlLower = url.toLowerCase();
+ if (urlLower.includes('medium.com')) return 'medium';
+ if (urlLower.includes('instagram.com')) return 'instagram';
+ if (urlLower.includes('huggingface.co')) return 'huggingface';
+ return 'generic';
+};
+
/**
* Fetch resource with Next.js caching
*/
@@ -228,29 +237,108 @@ export const getLinkedInProfileData = async (
};
/**
- * API to add user to Nocodb table for analytics
+ * API to add user to Supabase via edge function for analytics
*/
-export const addUserToNocodb = async (user: Profile | null) => {
+export const addUserToSupabase = async (user: Profile | null, searchParams?: URLSearchParams) => {
if (!user) return;
- const url = `https://app.nocodb.com/api/v2/tables/${process.env.NOCODB_TABLE_ID}/records`;
- const headers = {
- accept: "application/json",
- "xc-token": process.env.NOCODB_API_KEY || "",
- "Content-Type": "application/json",
- };
+
+ const supabaseUrl = process.env.SUPABASE_URL;
+ const supabaseAnonKey = process.env.SUPABASE_KEY;
+
+ if (!supabaseUrl || !supabaseAnonKey) {
+ console.error("Supabase configuration missing");
+ return;
+ }
- const data = {
+ const url = `${supabaseUrl}/functions/v1/devb-io`;
+
+ // Map user data to match Supabase function whitelist
+ const mappedData: Record = {
name: user.username,
- socials: user.social_accounts,
+ "full name": user.name,
+ "devb profile": `https://devb.io/${user.username}`,
+ github: `https://github.com/${user.username}`,
+ };
+
+ // Add query parameters if available
+ if (searchParams) {
+ // UTM parameters
+ const utmSource = searchParams.get('utm_source');
+ const utmMedium = searchParams.get('utm_medium');
+ const utmCampaign = searchParams.get('utm_campaign');
+ const utmTerm = searchParams.get('utm_term');
+ const utmContent = searchParams.get('utm_content');
+
+ // Referral parameter
+ const ref = searchParams.get('ref');
+
+ // Add to mapped data if they exist
+ if (utmSource) mappedData['utm_source'] = utmSource;
+ if (utmMedium) mappedData['utm_medium'] = utmMedium;
+ if (utmCampaign) mappedData['utm_campaign'] = utmCampaign;
+ if (utmTerm) mappedData['utm_term'] = utmTerm;
+ if (utmContent) mappedData['utm_content'] = utmContent;
+ if (ref) mappedData['ref'] = ref;
+ }
+
+ // Counter for generic URLs
+ let genericCounter = 1;
+
+ // Add social accounts based on provider
+ user.social_accounts?.forEach((account) => {
+ const provider = account.provider.toLowerCase();
+
+ // If provider is generic, detect the actual platform
+ const actualProvider = provider === "generic" ? detectProvider(account.url) : provider;
+
+ switch (actualProvider) {
+ case "linkedin":
+ mappedData["Linkedin"] = account.url;
+ break;
+ case "twitter":
+ mappedData["twitter"] = account.url;
+ break;
+ case "medium":
+ mappedData["Medium"] = account.url;
+ break;
+ case "instagram":
+ mappedData["instagram"] = account.url;
+ break;
+ case "huggingface":
+ // Could add huggingface to whitelist if needed
+ break;
+ case "generic":
+ // Check if it's a devb.io link
+ if (account.url.includes("devb.io")) {
+ mappedData["devb"] = account.url;
+ } else {
+ // For other generic URLs, number them
+ mappedData[`generic ${genericCounter}`] = account.url;
+ genericCounter++;
+ }
+ break;
+ }
+ });
+
+ const headers = {
+ "Content-Type": "application/json",
+ "Authorization": `Bearer ${supabaseAnonKey}`,
};
try {
- await fetch(url, {
+ const response = await fetch(url, {
method: "POST",
headers: headers,
- body: JSON.stringify(data),
+ body: JSON.stringify(mappedData),
});
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const result = await response.json();
+ console.log("User data sent to Supabase:", result);
} catch (error) {
- console.error("Error:", error);
+ console.error("Error sending data to Supabase:", error);
}
};
From c7e99a4528b41611fb26a106b0112eb0822ceb3b Mon Sep 17 00:00:00 2001
From: sunith vs
Date: Fri, 7 Nov 2025 00:21:30 +0530
Subject: [PATCH 2/3] fix(about): default about fallback issue
---
www/app/page.tsx | 4 ++--
www/components/ProfileSection.tsx | 5 ++++-
www/components/github-modal/client.tsx | 24 ++++++++++++++++++++----
www/components/profile-card/client.tsx | 2 +-
4 files changed, 27 insertions(+), 8 deletions(-)
diff --git a/www/app/page.tsx b/www/app/page.tsx
index d8650ad9..a4024423 100644
--- a/www/app/page.tsx
+++ b/www/app/page.tsx
@@ -175,8 +175,8 @@ export default async function Home() {
diff --git a/www/components/ProfileSection.tsx b/www/components/ProfileSection.tsx
index c066d08d..6fbde454 100644
--- a/www/components/ProfileSection.tsx
+++ b/www/components/ProfileSection.tsx
@@ -57,7 +57,10 @@ export async function ProfileSection({
});
}
- await addUserToSupabase(user, urlSearchParams);
+ // Run Supabase call in background without blocking UI
+ addUserToSupabase(user, urlSearchParams).catch((error) => {
+ console.error('Background analytics call failed:', error);
+ });
if (!user) return ;
diff --git a/www/components/github-modal/client.tsx b/www/components/github-modal/client.tsx
index 1b6724bc..54931a72 100644
--- a/www/components/github-modal/client.tsx
+++ b/www/components/github-modal/client.tsx
@@ -43,12 +43,27 @@ export default function GitHubModal({ onClose }: GitHubModalProps) {
const router = useRouter();
const [loading, setLoading] = useState(false);
- const redirectToProfilePage = async () => {
+ const redirectToProfilePage = () => {
if (!profile) return;
setLoading(true);
- await router.push(`/${profile?.login}?ref=modal`);
+
+ // Get current search params and preserve them
+ const currentParams = new URLSearchParams(window.location.search);
+ currentParams.set('ref', 'modal');
+
+ // Use window.location for instant navigation
+ window.location.href = `/${profile?.login}?${currentParams.toString()}`;
+ };
- // no need to setLoading(false) because navigation will replace this page
+ const redirectToProfilePageFromCard = () => {
+ if (!profile) return;
+
+ // Get current search params and preserve them
+ const currentParams = new URLSearchParams(window.location.search);
+ currentParams.set('ref', 'modelv2');
+
+ // Use window.location for instant navigation
+ window.location.href = `/${profile?.login}?${currentParams.toString()}`;
};
// Debounce the username input to prevent excessive API calls
const debouncedUsername = useDebounce(username, 500);
@@ -152,7 +167,8 @@ export default function GitHubModal({ onClose }: GitHubModalProps) {
@{username}
{bio}
-
+
Date: Fri, 7 Nov 2025 00:28:38 +0530
Subject: [PATCH 3/3] refactor(github-modal): remove unused router import
---
www/components/github-modal/client.tsx | 2 --
1 file changed, 2 deletions(-)
diff --git a/www/components/github-modal/client.tsx b/www/components/github-modal/client.tsx
index 54931a72..c2905794 100644
--- a/www/components/github-modal/client.tsx
+++ b/www/components/github-modal/client.tsx
@@ -3,7 +3,6 @@
import { useCallback, useEffect, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import Image from "next/image";
-import { useRouter } from "next/navigation";
import { Github, Loader, X } from "lucide-react";
import { cn } from "@/lib/utils";
@@ -40,7 +39,6 @@ export default function GitHubModal({ onClose }: GitHubModalProps) {
const [isValidating, setIsValidating] = useState(false);
const [error, setError] = useState("");
const [profile, setProfile] = useState(null);
- const router = useRouter();
const [loading, setLoading] = useState(false);
const redirectToProfilePage = () => {