shubhjn commited on
Commit
d985d08
·
1 Parent(s): d0916ba

feat: Introduce core scraping types, Google Maps scraper, and dashboard pages for business and task management.

Browse files
app/api/businesses/route.ts CHANGED
@@ -37,12 +37,17 @@ export async function GET(request: Request) {
37
  conditions.push(eq(businesses.emailStatus, status));
38
  }
39
 
 
 
 
40
  // Get total count
41
  const [{ count }] = await db
42
  .select({ count: sql<number>`count(*)` })
43
  .from(businesses)
44
  .where(and(...conditions));
45
 
 
 
46
  const totalPages = Math.ceil(count / limit);
47
 
48
  const results = await db
 
37
  conditions.push(eq(businesses.emailStatus, status));
38
  }
39
 
40
+ console.log(`🔍 Fetching businesses for UserID: ${userId}`);
41
+ console.log(` Filters - Category: ${category}, Status: ${status}, Page: ${page}, Limit: ${limit}`);
42
+
43
  // Get total count
44
  const [{ count }] = await db
45
  .select({ count: sql<number>`count(*)` })
46
  .from(businesses)
47
  .where(and(...conditions));
48
 
49
+ console.log(` Found ${count} total businesses matching criteria`);
50
+
51
  const totalPages = Math.ceil(count / limit);
52
 
53
  const results = await db
app/dashboard/businesses/page.tsx CHANGED
@@ -71,7 +71,12 @@ export default function BusinessesPage() {
71
  const handleSendEmail = async (business: Business) => {
72
  const toastId = toast.loading(`Sending email to ${business.name}...`);
73
  try {
74
- await sendEmailApi("/api/email/send", { businessId: business.id });
 
 
 
 
 
75
  toast.success(`Email sent to ${business.email}`, { id: toastId });
76
 
77
  // Update local state
 
71
  const handleSendEmail = async (business: Business) => {
72
  const toastId = toast.loading(`Sending email to ${business.name}...`);
73
  try {
74
+ const result = await sendEmailApi("/api/email/send", { businessId: business.id });
75
+
76
+ if (!result) {
77
+ throw new Error("Failed to send email");
78
+ }
79
+
80
  toast.success(`Email sent to ${business.email}`, { id: toastId });
81
 
82
  // Update local state
app/dashboard/page.tsx CHANGED
@@ -151,7 +151,12 @@ export default function DashboardPage() {
151
  const handleSendEmail = async (business: Business) => {
152
  const toastId = toast.loading(`Sending email to ${business.name}...`);
153
  try {
154
- await sendEmailApi("/api/email/send", { businessId: business.id });
 
 
 
 
 
155
  toast.success(`Email sent to ${business.email}`, { id: toastId });
156
 
157
  setBusinesses(prev => prev.map(b =>
 
151
  const handleSendEmail = async (business: Business) => {
152
  const toastId = toast.loading(`Sending email to ${business.name}...`);
153
  try {
154
+ const result = await sendEmailApi("/api/email/send", { businessId: business.id });
155
+
156
+ if (!result) {
157
+ throw new Error("Failed to send email");
158
+ }
159
+
160
  toast.success(`Email sent to ${business.email}`, { id: toastId });
161
 
162
  setBusinesses(prev => prev.map(b =>
app/dashboard/tasks/page.tsx CHANGED
@@ -83,6 +83,8 @@ export default function TasksPage() {
83
  if (result) {
84
  toast.success(`Task ${action}d successfully`);
85
  await fetchTasks();
 
 
86
  }
87
  } catch (error) {
88
  toast.error(`Failed to ${action} task`);
@@ -126,6 +128,8 @@ export default function TasksPage() {
126
  toast.success(`Priority updated to ${priority}`);
127
  // No need to fetch if optimistic update matches, but safe to fetch
128
  // await fetchTasks();
 
 
129
  }
130
  } catch (error) {
131
  toast.error("Failed to update priority");
@@ -143,9 +147,13 @@ export default function TasksPage() {
143
  if (!confirmDeleteId) return;
144
 
145
  try {
146
- await deleteTask(`/api/tasks?id=${confirmDeleteId.id}&type=${confirmDeleteId.type}`);
147
- toast.success("Task deleted successfully");
148
- await fetchTasks();
 
 
 
 
149
  } catch (error) {
150
  toast.error("Failed to delete task");
151
  console.error("Error deleting task:", error);
 
83
  if (result) {
84
  toast.success(`Task ${action}d successfully`);
85
  await fetchTasks();
86
+ } else {
87
+ throw new Error("API returned null");
88
  }
89
  } catch (error) {
90
  toast.error(`Failed to ${action} task`);
 
128
  toast.success(`Priority updated to ${priority}`);
129
  // No need to fetch if optimistic update matches, but safe to fetch
130
  // await fetchTasks();
131
+ } else {
132
+ throw new Error("API returned null");
133
  }
134
  } catch (error) {
135
  toast.error("Failed to update priority");
 
147
  if (!confirmDeleteId) return;
148
 
149
  try {
150
+ const result = await deleteTask(`/api/tasks?id=${confirmDeleteId.id}&type=${confirmDeleteId.type}`);
151
+ if (result !== null) {
152
+ toast.success("Task deleted successfully");
153
+ await fetchTasks();
154
+ } else {
155
+ throw new Error("Failed to delete task");
156
+ }
157
  } catch (error) {
158
  toast.error("Failed to delete task");
159
  console.error("Error deleting task:", error);
components/active-task-card.tsx CHANGED
@@ -39,6 +39,8 @@ export function ActiveTaskCard({
39
  if (result) {
40
  toast.success(`Task ${action}d successfully`);
41
  onStatusChange?.();
 
 
42
  }
43
  } catch (error) {
44
  toast.error(`Failed to ${action} task`);
 
39
  if (result) {
40
  toast.success(`Task ${action}d successfully`);
41
  onStatusChange?.();
42
+ } else {
43
+ throw new Error("Failed to control task");
44
  }
45
  } catch (error) {
46
  toast.error(`Failed to ${action} task`);
lib/auth.ts CHANGED
@@ -94,6 +94,8 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
94
  .set({
95
  accessToken: account?.access_token,
96
  refreshToken: account?.refresh_token,
 
 
97
  updatedAt: new Date(),
98
  })
99
  .where(eq(users.id, existingUser.id));
@@ -115,21 +117,34 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
115
  }
116
  },
117
  async session({ session, token }) {
118
- if (session.user) {
119
- if (token.role === "admin") {
120
- session.user.role = "admin";
121
- session.user.id = "admin-user";
122
- } else if (session.user.email) {
123
  const dbUser = await db.query.users.findFirst({
124
  where: eq(users.email, session.user.email),
125
  });
126
 
127
- if (dbUser) {
128
- session.user.id = dbUser.id;
129
- session.user.accessToken = dbUser.accessToken || undefined;
130
- session.user.role = "user";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  }
132
- }
133
  }
134
  return session;
135
  },
 
94
  .set({
95
  accessToken: account?.access_token,
96
  refreshToken: account?.refresh_token,
97
+ name: user.name,
98
+ image: user.image,
99
  updatedAt: new Date(),
100
  })
101
  .where(eq(users.id, existingUser.id));
 
117
  }
118
  },
119
  async session({ session, token }) {
120
+ // console.log("🔐 Session Callback - Token:", JSON.stringify(token, null, 2));
121
+
122
+ // Always prioritize DB lookup for logged in users
123
+ if (session.user && session.user.email) {
124
+ try {
125
  const dbUser = await db.query.users.findFirst({
126
  where: eq(users.email, session.user.email),
127
  });
128
 
129
+ console.log("👤 DB User found:", dbUser ? dbUser.id : "null");
130
+
131
+ if (dbUser) {
132
+ // Use Database Truth (which is synced from Google on login)
133
+ session.user.id = dbUser.id;
134
+ session.user.name = dbUser.name || session.user.name;
135
+ session.user.image = dbUser.image || session.user.image;
136
+ session.user.role = "user";
137
+ session.user.accessToken = dbUser.accessToken || undefined;
138
+ } else {
139
+ // Only use admin fallback if NOT found in DB (unlikely for Google login)
140
+ if (token.role === "admin") {
141
+ session.user.role = "admin";
142
+ session.user.id = "admin-user";
143
+ }
144
+ }
145
+ } catch (error) {
146
+ console.error("Error seeking DB user in session:", error);
147
  }
 
148
  }
149
  return session;
150
  },
lib/queue.ts CHANGED
@@ -222,15 +222,18 @@ export const scrapingWorker = new Worker(
222
  ...b,
223
  userId,
224
  category: b.category || "Unknown",
225
- emailStatus: null,
226
  }));
227
 
228
  // Use INSERT ON CONFLICT DO NOTHING approach
229
  try {
230
  await db.insert(businesses).values(businessesToInsert).onConflictDoNothing();
231
- } catch {
 
232
  // Fallback if onConflictDoNothing is not supported by driver or schema setup
233
- await db.insert(businesses).values(businessesToInsert).catch(() => { });
 
 
234
  }
235
 
236
  totalFound += results.length;
 
222
  ...b,
223
  userId,
224
  category: b.category || "Unknown",
225
+ emailStatus: b.emailStatus || null,
226
  }));
227
 
228
  // Use INSERT ON CONFLICT DO NOTHING approach
229
  try {
230
  await db.insert(businesses).values(businessesToInsert).onConflictDoNothing();
231
+ } catch (e: any) {
232
+ console.error(" ❌ Failed to insert businesses (onConflictDoNothing):", e.message);
233
  // Fallback if onConflictDoNothing is not supported by driver or schema setup
234
+ await db.insert(businesses).values(businessesToInsert).catch((err) => {
235
+ console.error(" ❌ Fallback insert also failed:", err.message);
236
+ });
237
  }
238
 
239
  totalFound += results.length;
lib/scrapers/google-maps.ts CHANGED
@@ -30,6 +30,7 @@ export const googleMapsScraper: ScraperSource = {
30
  imageUrl: undefined, // Not available from scraper
31
  source: "google-maps",
32
  sourceUrl: business.website || undefined,
 
33
  }));
34
 
35
  console.log(`✅ Google Maps: Found ${businesses.length} businesses`);
 
30
  imageUrl: undefined, // Not available from scraper
31
  source: "google-maps",
32
  sourceUrl: business.website || undefined,
33
+ emailStatus: business.emailStatus || "pending",
34
  }));
35
 
36
  console.log(`✅ Google Maps: Found ${businesses.length} businesses`);
lib/scrapers/types.ts CHANGED
@@ -28,6 +28,7 @@ export interface BusinessData {
28
  };
29
  source: string; // Source where this data was found
30
  sourceUrl?: string; // Original listing URL
 
31
  }
32
 
33
  export interface ScraperSource {
 
28
  };
29
  source: string; // Source where this data was found
30
  sourceUrl?: string; // Original listing URL
31
+ emailStatus?: string | null;
32
  }
33
 
34
  export interface ScraperSource {