Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 16 additions & 22 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,21 @@ import {
import { Plus } from "lucide-react";
import AddWebsite from "@/components/add-website";
import useUser from "@/hooks/useUser";
import { useEffect, useState } from "react";
import { WebsiteCard } from "@/components/website-card";
import Loading from "@/components/loading";
import { Website } from "@/types";
import { fetchWebsitesWithAnalytics } from "@/actions/fetchAnalytics";
import { supabase } from "@/config/supabase";

interface WebsiteWithAnalytics extends Website {
visitors_24h?: number;
pageviews_24h?: number;
}
import useSWR from 'swr';

export default function DashboardPage() {
const { user } = useUser();
const [websites, setWebsites] = useState<WebsiteWithAnalytics[]>([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
const loadWebsites = async () => {
if (!user?.id) return;
const { data: websites, isLoading } = useSWR(
user?.id ? ['websites', user.id] : null,
async () => {
if (!user?.id) return [];

try {
setLoading(true);
const websitesData = await fetchWebsitesWithAnalytics(user.id);

// Fetch 24h stats for each website
Expand Down Expand Up @@ -62,18 +54,20 @@ export default function DashboardPage() {
})
);

setWebsites(websitesWith24hStats);
return websitesWith24hStats;
} catch (error) {
console.error("Error loading websites:", error);
} finally {
setLoading(false);
return [];
}
};

loadWebsites();
}, [user]);
},
{
refreshInterval: 30000, // Refresh every 30 seconds
revalidateOnFocus: false,
dedupingInterval: 10000
}
);

if (loading) return <Loading text="Getting your websites..." />;
if (isLoading) return <Loading text="Getting your websites..." />;

return (
<div className="min-h-screen bg-neutral-900/10 p-4 sm:p-6 lg:p-8">
Expand All @@ -93,7 +87,7 @@ export default function DashboardPage() {
</DialogContent>
</Dialog>
</div>
{websites.length === 0 ? (
{!websites || websites.length === 0 ? (
<div className="text-center py-12">
<p className="text-neutral-400">No websites added yet.</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ send_request()`,
></div>
</div>
<div className="relative mt-12">
<ul className="columns-1 sm:columns-2 max-w-[70rem] mx-auto dark">
<ul className="columns-1 sm:columns-2 max-w-[70rem] mx-auto dark [&>*]:break-inside-avoid-column">
{TWEETS.map((tweet, idx) => {
const tweetId = tweet.split("/").pop();
return (
Expand Down
166 changes: 77 additions & 89 deletions app/site/[website]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,126 +35,114 @@ import useUser from "@/hooks/useUser";
import { useRouter } from "next/navigation";
import { supabase } from "@/config/supabase";
import StatsBox from "@/components/24hour-stats-box";
import useSWR from 'swr';

export default function AnalyticsPage() {
const { website } = useParams();
const { user, loading: userLoading } = useUser();
const router = useRouter();

const [pageViews, setPageViews] = useState<PageView[]>([]);
const [totalVisits, setTotalVisits] = useState<Visit[]>([]);
const [customEvents, setCustomEvents] = useState<CustomEvent[]>([]);
const [loading, setLoading] = useState(true);
const [groupedPageViews, setGroupedPageViews] = useState<GroupedView[]>([]);
const [groupedPageSources, setGroupedPageSources] = useState<GroupedSource[]>(
[]
);
const [groupedCustomEvents, setGroupedCustomEvents] = useState<
Record<string, number>
>({});
const [activeCustomEventTab, setActiveCustomEventTab] = useState("");
const [filterValue, setFilterValue] = useState("0");
const [isAuthorized, setIsAuthorized] = useState<boolean | null>(null);
const [showStatsBox, setShowStatsBox] = useState(true);

useEffect(() => {
if (!userLoading && !user) {
router.push("/sign-in");
}
}, [user, userLoading, router]);

const checkWebsiteOwnership = useCallback(async () => {
if (!user) return;
// Website ownership check with SWR
const { data: authorized, isLoading: authLoading } = useSWR(
user ? ['websiteOwnership', user.id, website] : null,
async () => {
try {
const { data, error } = await supabase
.from("websites")
.select("id")
.eq("name", website)
.eq("user_id", user!.id)
.single();

try {
const { data, error } = await supabase
.from("websites")
.select("id")
.eq("name", website)
.eq("user_id", user.id)
.single();
if (error || !data) {
router.push("/dashboard");
return false;
}

if (error || !data) {
setIsAuthorized(false);
return true;
} catch (error) {
console.error("Error checking website ownership:", error);
router.push("/dashboard");
return false;
}

setIsAuthorized(true);
return true;
} catch (error) {
console.error("Error checking website ownership:", error);
setIsAuthorized(false);
router.push("/dashboard");
return false;
},
{
revalidateOnFocus: false,
dedupingInterval: 60000,
}
}, [user, website, router]);

const handleFilterChange = useCallback(
async (value: string) => {
if (!isAuthorized) return;
);

setLoading(true);
// Analytics data fetching with SWR
const { data: analyticsData, isLoading: dataLoading, mutate: refreshAnalytics } = useSWR(
authorized ? ['analytics', website, filterValue] : null,
async () => {
try {
const result = await fetchViews(website as string, value);
const result = await fetchViews(website as string, filterValue);
if (result.error) {
console.error(result.error);
return;
return null;
}

setPageViews(result.pageViews || []);
setTotalVisits(result.visits || []);
setCustomEvents(result.customEvents || []);
setGroupedPageViews(groupPageViews(result.pageViews || []));
setGroupedPageSources(groupPageSources(result.visits || []));

const newGroupedEvents = (result.customEvents || []).reduce<
Record<string, number>
>((acc, event) => {
if (event?.event_name) {
acc[event.event_name] = (acc[event.event_name] || 0) + 1;
}
return acc;
}, {});
setGroupedCustomEvents(newGroupedEvents);
setFilterValue(value);
return {
pageViews: result.pageViews || [],
visits: result.visits || [],
customEvents: result.customEvents || [],
groupedPageViews: groupPageViews(result.pageViews || []),
groupedPageSources: groupPageSources(result.visits || []),
groupedCustomEvents: (result.customEvents || []).reduce<Record<string, number>>(
(acc, event) => {
if (event?.event_name) {
acc[event.event_name] = (acc[event.event_name] || 0) + 1;
}
return acc;
},
{}
),
};
} catch (error) {
console.error("Error updating views:", error);
} finally {
setLoading(false);
return null;
}
},
[website, isAuthorized]
{
refreshInterval: 30000, // Refresh every 30 seconds
revalidateOnFocus: false,
dedupingInterval: 10000,
}
);

useEffect(() => {
if (user) {
checkWebsiteOwnership();
if (!userLoading && !user) {
router.push("/sign-in");
}
}, [checkWebsiteOwnership, user]);
}, [user, userLoading, router]);

useEffect(() => {
if (isAuthorized === true) {
handleFilterChange("0");
}
}, [isAuthorized, handleFilterChange]);
const handleFilterChange = useCallback(
(value: string) => {
setFilterValue(value);
refreshAnalytics();
},
[refreshAnalytics]
);

if (userLoading || isAuthorized === null || loading) {
if (userLoading || authLoading || dataLoading) {
return <Loading text="Getting your data..." />;
}

if (!isAuthorized) {
if (!authorized) {
return null;
}

if (pageViews?.length === 0 && !loading) {
if (!analyticsData?.pageViews || analyticsData.pageViews.length === 0) {
return <NoPageViewsState website={website as string} />;
}

const pageViews24h = pageViews.filter(
const pageViews24h = analyticsData.pageViews.filter(
(pv) => new Date(pv.created_at) > new Date(Date.now() - 24 * 60 * 60 * 1000)
).length;
const totalVisits24h = totalVisits.filter(
const totalVisits24h = analyticsData.visits.filter(
(v) => new Date(v.created_at) > new Date(Date.now() - 24 * 60 * 60 * 1000)
).length;

Expand All @@ -165,8 +153,8 @@ export default function AnalyticsPage() {
<StatsBox
pageViews24h={pageViews24h}
totalVisits24h={totalVisits24h}
pageViews={pageViews.length}
totalVisits={totalVisits.length}
pageViews={analyticsData.pageViews.length}
totalVisits={analyticsData.visits.length}
onClose={() => setShowStatsBox(false)}
/>
)}
Expand Down Expand Up @@ -240,10 +228,10 @@ export default function AnalyticsPage() {

<TabsContent value="general">
<GeneralAnalytics
pageViews={pageViews}
totalVisits={totalVisits}
groupedPageViews={groupedPageViews}
groupedPageSources={groupedPageSources}
pageViews={analyticsData.pageViews}
totalVisits={analyticsData.visits}
groupedPageViews={analyticsData.groupedPageViews}
groupedPageSources={analyticsData.groupedPageSources}
filterValue={filterValue}
website={website as string}
/>
Expand All @@ -258,10 +246,10 @@ export default function AnalyticsPage() {

<TabsContent value="custom-events">
<SiteCustomEvents
customEvents={customEvents}
groupedCustomEvents={groupedCustomEvents}
activeCustomEventTab={activeCustomEventTab}
setActiveCustomEventTab={setActiveCustomEventTab}
customEvents={analyticsData.customEvents}
groupedCustomEvents={analyticsData.groupedCustomEvents}
activeCustomEventTab=""
setActiveCustomEventTab={() => {}}
/>
</TabsContent>

Expand Down
2 changes: 1 addition & 1 deletion components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default function Footer() {
</div>

<p className="text-sm text-neutral-500">
© 2024 Analyzr. Complete analytics and monitoring for modern applications.
© 2025 Analyzr. Complete analytics and monitoring for modern applications.
</p>
</footer>
);
Expand Down
2 changes: 1 addition & 1 deletion components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default function Header() {

return (
<header className="border-b px-4 border-neutral-800">
<div className="container mx-auto">
<div className="container mx-auto max-w-screen-xl mx-auto">
<div className="flex items-center justify-between h-16">
<div className="flex items-center space-x-4">
<Link href="/" className="flex-shrink-0">
Expand Down
2 changes: 0 additions & 2 deletions lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,6 @@ export const DETAILED_METRICS = [
export const TWEETS = [
"https://x.com/kelmedev/status/1861146824767107505",
"https://x.com/GbenegbaraM/status/1860760001842717098",
"https://x.com/Adityaguptareal/status/1860702277683515899",
"https://x.com/namish_855/status/1859569277444993512",
"https://x.com/amiswa2005/status/1858299362801033720",
];

Expand Down
Loading