-
- Target Job
+
+ Target Job
{selectedJob && (
-
-
{selectedJob.job_title}
-
{selectedJob.industry}
-
{selectedJob.description}
+
+
{selectedJob.job_title}
+
{selectedJob.industry}
+
{selectedJob.description}
{selectedJob.keywords.map((kw, i) => (
- {kw}
+ {kw}
))}
@@ -125,48 +125,48 @@ export default function AIAssistantPage() {
-
- Quick Tips
+
+ Quick Tips
{tips.map((tip, i) => (
handleTipClick(tip)}
- className="flex items-start gap-2.5 bg-[#1a1d2e]/50 hover:bg-[#1a1d2e] border border-[#ffffff08] hover:border-[#f97316]/20 rounded-xl p-3.5 mb-2 cursor-pointer transition-all duration-200 group"
+ className="flex items-start gap-2.5 bg-card/50 hover:bg-card border border-border hover:border-primary/20 rounded-xl p-3.5 mb-2 cursor-pointer transition-all duration-200 group"
>
-
- {tip}
+
+ {tip}
))}
-
- Recent Prompts
+
+ Recent Prompts
{recentPrompts.length > 0 ? (
recentPrompts.map((prompt, i) => (
handleRecentPromptClick(prompt)}
- className="bg-[#1a1d2e]/50 rounded-xl p-3 mb-2 border border-[#ffffff08] text-[#6b7280] text-xs truncate hover:text-[#9ca3af] cursor-pointer transition-colors duration-200"
+ className="bg-card/50 rounded-xl p-3 mb-2 border border-border text-muted-foreground text-xs truncate hover:text-foreground cursor-pointer transition-colors duration-200"
>
{prompt}
))
) : (
-
Your recent prompts will appear here
+
Your recent prompts will appear here
)}
-
+
-
Messages today
-
24
+
Messages today
+
24
-
Tokens used
-
12.4k
+
Tokens used
+
12.4k
diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx
index d061bf2..cb288a3 100644
--- a/src/app/(dashboard)/dashboard/page.tsx
+++ b/src/app/(dashboard)/dashboard/page.tsx
@@ -24,8 +24,10 @@ import {
SelectValue,
} from "@/components/ui/select"
import { DashboardStats } from "@/types"
+import { useWebsiteTheme } from "@/providers/website-theme-provider"
export default function DashboardPage() {
+ const { websiteTheme } = useWebsiteTheme()
const [period, setPeriod] = useState("6months")
const [stats, setStats] = useState
(null)
const pollingRef = useRef(null)
@@ -84,7 +86,7 @@ export default function DashboardPage() {
-
Pipeline Overview
+
Pipeline Overview
{stats
@@ -95,7 +97,7 @@ export default function DashboardPage() {
}
-
Analytics
+
Analytics
@@ -104,12 +106,13 @@ export default function DashboardPage() {
- {/* Daily Bugle watermark */}
-
-
- DAILY BUGLE
-
-
+ {websiteTheme === "spidey" && (
+
+
+ DAILY BUGLE
+
+
+ )}
)
}
diff --git a/src/app/api/ai/giphy/route.ts b/src/app/api/ai/giphy/route.ts
new file mode 100644
index 0000000..be69b06
--- /dev/null
+++ b/src/app/api/ai/giphy/route.ts
@@ -0,0 +1,57 @@
+import { NextRequest, NextResponse } from "next/server"
+import { getSessionUser } from "@/lib/auth"
+
+const GIPHY_API_KEY = process.env.GIPHY_API_KEY
+const GIPHY_BASE = "https://api.giphy.com/v1/gifs"
+
+export async function GET(request: NextRequest) {
+ try {
+ const user = await getSessionUser()
+ if (!user) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
+ }
+
+ if (!GIPHY_API_KEY) {
+ return NextResponse.json({ error: "GIPHY API key not configured" }, { status: 500 })
+ }
+
+ const { searchParams } = new URL(request.url)
+ const type = searchParams.get("type") || "trending"
+ const query = searchParams.get("q") || ""
+ const offset = parseInt(searchParams.get("offset") || "0", 10)
+ const limit = Math.min(parseInt(searchParams.get("limit") || "20", 10), 50)
+
+ let url: string
+ if (type === "search" && query) {
+ url = `${GIPHY_BASE}/search?api_key=${GIPHY_API_KEY}&q=${encodeURIComponent(query)}&limit=${limit}&offset=${offset}&rating=g`
+ } else {
+ url = `${GIPHY_BASE}/trending?api_key=${GIPHY_API_KEY}&limit=${limit}&offset=${offset}&rating=g`
+ }
+
+ const res = await fetch(url)
+ if (!res.ok) {
+ const errBody = await res.text()
+ console.error("GIPHY API error:", res.status, errBody)
+ return NextResponse.json({ error: `GIPHY API error (${res.status}): ${errBody}` }, { status: res.status })
+ }
+
+ const data = await res.json()
+ const gifs = (data.data || []).map((gif: any) => ({
+ id: gif.id,
+ title: gif.title,
+ url: gif.images?.original?.url || "",
+ previewUrl: gif.images?.fixed_width?.url || "",
+ previewHeight: gif.images?.fixed_width?.height || 150,
+ width: gif.images?.original?.width || 0,
+ height: gif.images?.original?.height || 0,
+ }))
+
+ return NextResponse.json({
+ gifs,
+ pagination: data.pagination || { total_count: 0, count: gifs.length, offset },
+ })
+ } catch (error: any) {
+ console.error("GIPHY proxy error:", error)
+ return NextResponse.json({ error: error.message || "Internal server error" }, { status: 500 })
+ }
+}
diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts
index ea1cd8a..5e1fbcc 100644
--- a/src/app/api/auth/login/route.ts
+++ b/src/app/api/auth/login/route.ts
@@ -12,6 +12,7 @@ import {
setSessionContext,
SESSION_COOKIE,
} from "@/lib/auth"
+import { query } from "@/lib/db"
function jsonResponse(data: unknown, status: number) {
return new Response(JSON.stringify(data), {
@@ -114,6 +115,11 @@ export async function POST(request: NextRequest) {
true
)
+ await query(
+ `UPDATE users SET preferences = preferences || $2::jsonb WHERE id = $1`,
+ [dbUser.id, JSON.stringify({ website_theme: "default" })],
+ )
+
const token = await createSession(dbUser.id, dbUser.role_name)
await setSessionContext(dbUser.id, ipAddress)
diff --git a/src/app/api/dashboard/route.ts b/src/app/api/dashboard/route.ts
index f1a2112..940b71b 100644
--- a/src/app/api/dashboard/route.ts
+++ b/src/app/api/dashboard/route.ts
@@ -80,8 +80,8 @@ function countStatuses(leads: any[]) {
return counts
}
-function buildMonthlyBreakdown(leads: any[], period: string) {
- const { start, end } = getPeriodDateRange(period)
+function buildMonthlyBreakdown(leads: any[], period: string, rangeOverride?: { start: Date; end: Date }) {
+ const { start, end } = rangeOverride || getPeriodDateRange(period)
const result: { label: string; total: number; open: number; contacted: number; pending: number; closed: number; ignored: number }[] = []
const current = new Date(start)
const isMonthly = period === "6months" || period === "12months"
@@ -170,7 +170,7 @@ export async function GET(request: NextRequest) {
updatedAt: r.updated_at,
}))
- const monthlyBreakdown = buildMonthlyBreakdown(currentLeads, period)
+ const monthlyBreakdown = buildMonthlyBreakdown(currentLeads, period, yearParam ? { start, end } : undefined)
const trends = {
totalLeads: computeTrend(currentCounts.open + currentCounts.contacted + currentCounts.pending + currentCounts.closed + currentCounts.ignored,
diff --git a/src/app/api/gifs/route.ts b/src/app/api/gifs/route.ts
index 8ecf18e..1b7c5b6 100644
--- a/src/app/api/gifs/route.ts
+++ b/src/app/api/gifs/route.ts
@@ -1,8 +1,8 @@
import { NextRequest, NextResponse } from "next/server"
import { getSessionUser } from "@/lib/auth"
-const TENOR_API_KEY = process.env.TENOR_API_KEY || ""
-const TENOR_BASE = "https://tenor.googleapis.com/v2"
+const GIPHY_API_KEY = process.env.GIPHY_API_KEY
+const GIPHY_BASE = "https://api.giphy.com/v1/gifs"
export async function GET(request: NextRequest) {
try {
@@ -12,41 +12,78 @@ export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const q = searchParams.get("q") || ""
const limit = Math.min(parseInt(searchParams.get("limit") || "20", 10), 50)
- const pos = searchParams.get("pos") || ""
+ const pos = parseInt(searchParams.get("pos") || "0", 10) || 0
- if (!TENOR_API_KEY) {
+ if (!GIPHY_API_KEY) {
+ console.error("Missing GIPHY_API_KEY environment variable")
return NextResponse.json({ results: [], noKey: true })
}
- const endpoint = q
- ? `${TENOR_BASE}/search?q=${encodeURIComponent(q)}&key=${TENOR_API_KEY}&limit=${limit}&media_filter=minimal`
- : `${TENOR_BASE}/featured?key=${TENOR_API_KEY}&limit=${limit}&media_filter=minimal`
+ let data: any
- const url = pos ? `${endpoint}&pos=${pos}` : endpoint
+ if (q) {
+ const url = `${GIPHY_BASE}/search?api_key=${GIPHY_API_KEY}&q=${encodeURIComponent(q)}&limit=${limit}&offset=${pos}&rating=g`
+ const res = await fetch(url, { signal: AbortSignal.timeout(8000) })
+ if (!res.ok) {
+ const errBody = await res.text()
+ console.error("GIPHY API search error:", res.status, errBody)
+ return NextResponse.json({ results: [], error: `GIPHY error: ${errBody}` }, { status: 502 })
+ }
+ data = await res.json()
+ } else {
+ const trendingUrl = `${GIPHY_BASE}/trending?api_key=${GIPHY_API_KEY}&limit=${limit}&offset=${pos}&rating=g`
+ const trendingRes = await fetch(trendingUrl, { signal: AbortSignal.timeout(8000) })
- const res = await fetch(url, { signal: AbortSignal.timeout(8000) })
- if (!res.ok) {
- return NextResponse.json({ results: [], error: "Tenor API error" }, { status: 502 })
+ if (trendingRes.ok) {
+ data = await trendingRes.json()
+ if (!data.data?.length && pos === 0) {
+ console.log("Trending returned no results, falling back to default search")
+ const fallbackUrl = `${GIPHY_BASE}/search?api_key=${GIPHY_API_KEY}&q=funny&limit=${limit}&offset=${pos}&rating=g`
+ const fallbackRes = await fetch(fallbackUrl, { signal: AbortSignal.timeout(8000) })
+ if (!fallbackRes.ok) {
+ const errBody = await fallbackRes.text()
+ console.error("GIPHY fallback search error:", fallbackRes.status, errBody)
+ return NextResponse.json({ results: [], error: `GIPHY error: ${errBody}` }, { status: 502 })
+ }
+ data = await fallbackRes.json()
+ }
+ } else {
+ const errBody = await trendingRes.text()
+ console.error("GIPHY trending error:", trendingRes.status, errBody)
+ console.log("Trending failed, falling back to default search")
+ const fallbackUrl = `${GIPHY_BASE}/search?api_key=${GIPHY_API_KEY}&q=funny&limit=${limit}&offset=${pos}&rating=g`
+ const fallbackRes = await fetch(fallbackUrl, { signal: AbortSignal.timeout(8000) })
+ if (!fallbackRes.ok) {
+ const fallbackErr = await fallbackRes.text()
+ console.error("GIPHY fallback search error:", fallbackRes.status, fallbackErr)
+ return NextResponse.json({ results: [], error: `GIPHY error: ${fallbackErr}` }, { status: 502 })
+ }
+ data = await fallbackRes.json()
+ }
}
-
- const data = await res.json()
-
- const results = (data.results || []).map((item: any) => {
- const media = item.media_formats?.gif || item.media_formats?.tinygif || {}
- const preview = item.media_formats?.tinygif || item.media_formats?.gif || {}
+ const results = (data.data || []).map((item: any) => {
+ const dims = item.images?.original
return {
id: item.id,
title: item.title || "",
- url: media.url || "",
- previewUrl: preview.url || "",
- width: media.dims?.[0] || 200,
- height: media.dims?.[1] || 200,
+ url: dims?.url || "",
+ previewUrl: item.images?.fixed_width?.url || "",
+ width: dims?.width ? parseInt(dims.width, 10) : 200,
+ height: dims?.height ? parseInt(dims.height, 10) : 200,
}
})
- return NextResponse.json({ results, next: data.next || "" })
- } catch (error) {
- console.error("GIF API error:", error)
- return NextResponse.json({ results: [], error: "Failed to fetch GIFs" }, { status: 500 })
+ const nextOffset = pos + results.length
+ const total = data.pagination?.total_count || 0
+ const hasMore = total ? nextOffset < total : results.length === limit
+ const nextPos = hasMore ? String(nextOffset) : ""
+
+ return NextResponse.json({ results, next: nextPos })
+ } catch (error: any) {
+ console.error("GIF proxy error:", error)
+ const message = error?.message === "Failed to fetch"
+ ? "Could not reach the GIF service."
+ : "Failed to fetch GIFs."
+ return NextResponse.json({ results: [], error: message }, { status: 500 })
}
}
diff --git a/src/app/api/system/monitor/route.ts b/src/app/api/system/monitor/route.ts
index 0051f42..baecb2e 100644
--- a/src/app/api/system/monitor/route.ts
+++ b/src/app/api/system/monitor/route.ts
@@ -8,9 +8,6 @@ let prevTime = Date.now()
export async function GET() {
const sessionUser = await getSessionUser()
if (!sessionUser) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
- if (sessionUser.role !== "admin" && sessionUser.role !== "super_admin") {
- return NextResponse.json({ error: "Forbidden" }, { status: 403 })
- }
const now = Date.now()
const elapsed = now - prevTime
diff --git a/src/app/call/[roomId]/page.tsx b/src/app/call/[roomId]/page.tsx
index 780d963..f438469 100644
--- a/src/app/call/[roomId]/page.tsx
+++ b/src/app/call/[roomId]/page.tsx
@@ -53,24 +53,24 @@ export default function CallRoomPage({ params }: { params: Promise<{ roomId: str
if (!joined) {
return (
-
-
+
+
{connectionTimeout ? (
<>
-
This call is no longer available.
+
This call is no longer available.
>
) : !isReady ? (
<>
-
-
Connecting to call...
+
+
Connecting to call...
>
) : (
<>
-
Join Call
+
Join Call
{micError && (
-
{micError}
+
{micError}
)}
setDisplayName(e.target.value)}
onKeyDown={(e) => { if (e.key === "Enter") handleJoin() }}
placeholder="Enter your name"
- className="bg-[#F5F5F5] dark:bg-[#1A1A1A] border border-[#E0E0E0] dark:border-[#333333] text-[#111111] dark:text-white placeholder-[#AAAAAA] dark:placeholder-[#555555] rounded-xl px-4 py-2.5 text-sm w-full mb-4 outline-none transition-colors focus:border-[#CC0000] dark:focus:border-[#FF4444]"
+ className="bg-[#FAFAF6] dark:bg-[#1A1A1A] border border-[#D4D8CC] dark:border-[#333333] text-[#2D3020] dark:text-white placeholder-[#8A9078] dark:placeholder-[#555555] rounded-xl px-4 py-2.5 text-sm w-full mb-4 outline-none transition-colors focus:border-[#C84B4B] dark:focus:border-[#FF4444]"
/>