From 85aadf183d880232bb6ced8a3ba30e81fba67e4d Mon Sep 17 00:00:00 2001 From: code-env Date: Sat, 17 May 2025 00:54:56 +0100 Subject: [PATCH 1/7] Refactor database configuration and update package scripts --- drizzle.config.ts | 9 +- package.json | 6 +- public/tracking-script.js | 707 +++++++++++++------------- src/components/Header/main-header.tsx | 235 +++++---- src/lib/db.ts | 6 +- 5 files changed, 482 insertions(+), 481 deletions(-) diff --git a/drizzle.config.ts b/drizzle.config.ts index 6660d25..6d386b9 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,9 +1,10 @@ import { defineConfig } from "drizzle-kit"; + export default defineConfig({ dialect: "postgresql", schema: "./src/lib/schema.ts", out: "./drizzle", - dbCredentials:{ - url: process.env.POSTGRESS_URL! - } -}); \ No newline at end of file + dbCredentials: { + url: process.env.POSTGRESS_URL!, + }, +}); diff --git a/package.json b/package.json index 814607b..6380a6a 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,13 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev --turbopack", "build": "next build", "start": "next start", "lint": "next lint", - "db:generate": "drizzle-kit generate --dialect=postgresql --schema=src/lib/schema.ts --out=./drizzle" + "db:generate": "drizzle-kit generate --dialect=postgresql --schema=src/lib/schema.ts --out=./drizzle", + "db:migrate": "drizzle-kit migrate", + "db:studio": "drizzle-kit studio" }, "dependencies": { "@auth/drizzle-adapter": "^1.9.0", diff --git a/public/tracking-script.js b/public/tracking-script.js index 0c84878..09eab43 100644 --- a/public/tracking-script.js +++ b/public/tracking-script.js @@ -1,373 +1,376 @@ (function () { - "use strict"; - var location = window.location; - var document = window.document; - var scriptElement = document.currentScript; - var dataDomain = scriptElement.getAttribute("data-domain"); - - // Enhanced UTM parameter tracking - let queryString = location.search; - const params = new URLSearchParams(queryString); - var utmParams = { - source: params.get("utm_source"), - medium: params.get("utm_medium"), - campaign: params.get("utm_campaign"), - term: params.get("utm_term"), - content: params.get("utm_content"), - }; - - var endpoint = "https://webtracker.avikmukherjee.tech/api/track"; - var sessionDuration = 30 * 60 * 1000; // 30 minutes in milliseconds - - // Visitor identification (anonymous) - function getVisitorId() { - var visitorId = localStorage.getItem("visitor_id"); - if (!visitorId) { - visitorId = "visitor-" + Math.random().toString(36).substr(2, 16); - localStorage.setItem("visitor_id", visitorId); - } - return visitorId; + "use strict"; + var location = window.location; + var document = window.document; + var scriptElement = document.currentScript; + var dataDomain = scriptElement.getAttribute("data-domain"); + + // Enhanced UTM parameter tracking + let queryString = location.search; + const params = new URLSearchParams(queryString); + var utmParams = { + source: params.get("utm_source"), + medium: params.get("utm_medium"), + campaign: params.get("utm_campaign"), + term: params.get("utm_term"), + content: params.get("utm_content"), + }; + + var endpoint = "https://webtracker.avikmukherjee.tech/api/track"; + var sessionDuration = 30 * 60 * 1000; // 30 minutes in milliseconds + + // Visitor identification (anonymous) + function getVisitorId() { + var visitorId = localStorage.getItem("visitor_id"); + if (!visitorId) { + visitorId = "visitor-" + Math.random().toString(36).substr(2, 16); + localStorage.setItem("visitor_id", visitorId); } - - function generateSessionId() { - return "session-" + Math.random().toString(36).substr(2, 9); - } - - function initializeSession() { - var sessionId = sessionStorage.getItem("session_id"); - var expirationTimestamp = sessionStorage.getItem( + return visitorId; + } + + function generateSessionId() { + return "session-" + Math.random().toString(36).substr(2, 9); + } + + function initializeSession() { + var sessionId = sessionStorage.getItem("session_id"); + var expirationTimestamp = sessionStorage.getItem( + "session_expiration_timestamp" + ); + var isNewSession = false; + + if ( + !sessionId || + !expirationTimestamp || + isSessionExpired(parseInt(expirationTimestamp)) + ) { + // Generate a new session ID + sessionId = generateSessionId(); + // Set the expiration timestamp + expirationTimestamp = Date.now() + sessionDuration; + // Store the session ID and expiration timestamp in sessionStorage + sessionStorage.setItem("session_id", sessionId); + sessionStorage.setItem( "session_expiration_timestamp", + expirationTimestamp + ); + isNewSession = true; + } else { + // Extend the session + expirationTimestamp = Date.now() + sessionDuration; + sessionStorage.setItem( + "session_expiration_timestamp", + expirationTimestamp ); - var isNewSession = false; - - if ( - !sessionId || - !expirationTimestamp || - isSessionExpired(parseInt(expirationTimestamp)) - ) { - // Generate a new session ID - sessionId = generateSessionId(); - // Set the expiration timestamp - expirationTimestamp = Date.now() + sessionDuration; - // Store the session ID and expiration timestamp in sessionStorage - sessionStorage.setItem("session_id", sessionId); - sessionStorage.setItem( - "session_expiration_timestamp", - expirationTimestamp, - ); - isNewSession = true; - } else { - // Extend the session - expirationTimestamp = Date.now() + sessionDuration; - sessionStorage.setItem( - "session_expiration_timestamp", - expirationTimestamp, - ); - } - - if (isNewSession) { - trackSessionStart(); - } - - return { - sessionId: sessionId, - expirationTimestamp: parseInt(expirationTimestamp), - isNewSession: isNewSession, - }; - } - - function isSessionExpired(expirationTimestamp) { - return Date.now() >= expirationTimestamp; } - - function resetActivityTimer() { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - var session = initializeSession(); - // Just updating the expiration time + + if (isNewSession) { + trackSessionStart(); } - - // User activity monitoring - ["mousedown", "keydown", "touchstart", "scroll"].forEach(function (evt) { - document.addEventListener(evt, throttle(resetActivityTimer, 5000), { - passive: true, - }); + + return { + sessionId: sessionId, + expirationTimestamp: parseInt(expirationTimestamp), + isNewSession: isNewSession, + }; + } + + function isSessionExpired(expirationTimestamp) { + return Date.now() >= expirationTimestamp; + } + + function resetActivityTimer() { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + var session = initializeSession(); + // Just updating the expiration time + } + + // User activity monitoring + ["mousedown", "keydown", "touchstart", "scroll"].forEach(function (evt) { + document.addEventListener(evt, throttle(resetActivityTimer, 5000), { + passive: true, }); - - // Throttle function to limit how often a function is called - function throttle(func, limit) { - var lastCall = 0; - return function () { - var now = Date.now(); - if (now - lastCall >= limit) { - lastCall = now; - func.apply(this, arguments); - } - }; - } - - // Function to send tracking events to the endpoint - function trigger(eventName, eventData, options) { - var session = initializeSession(); - var visitorId = getVisitorId(); - var source = ""; - if (document.referrer) { - try { - const referrerUrl = new URL(document.referrer); - source = referrerUrl.hostname; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e) { - // Invalid referrer URL - source = "unknown"; - } - } else { - source = "direct"; + }); + + // Throttle function to limit how often a function is called + function throttle(func, limit) { + var lastCall = 0; + return function () { + var now = Date.now(); + if (now - lastCall >= limit) { + lastCall = now; + func.apply(this, arguments); } - - var payload = { - event: eventName, - url: location.href, - path: location.pathname, - domain: dataDomain, - referrer: document.referrer, - title: document.title, - utm: utmParams, - source: source, - visitor_id: visitorId, - session_id: session.sessionId, - timestamp: new Date().toISOString(), - screen: { - width: window.innerWidth, - height: window.innerHeight, - }, - language: navigator.language, - user_agent: navigator.userAgent, - data: eventData || {}, - }; - - // Using sendBeacon API for more reliable data sending, falling back to XHR - if (navigator.sendBeacon && !options?.forceXHR) { - navigator.sendBeacon(endpoint, JSON.stringify(payload)); - options?.callback?.(); - } else { - sendRequest(payload, options); + }; + } + + // Function to send tracking events to the endpoint + function trigger(eventName, eventData, options) { + var session = initializeSession(); + var visitorId = getVisitorId(); + var source = ""; + if (document.referrer) { + try { + const referrerUrl = new URL(document.referrer); + source = referrerUrl.hostname; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + // Invalid referrer URL + source = "unknown"; } + } else { + source = "direct"; } - - // Function to send HTTP requests - function sendRequest(payload, options) { - var request = new XMLHttpRequest(); - request.open("POST", endpoint, true); - request.setRequestHeader("Content-Type", "application/json"); - request.onreadystatechange = function () { - if (request.readyState === 4) { - options?.callback?.(); - } - }; - request.send(JSON.stringify(payload)); - } - - // Queue of tracking events - var queue = (window.your_tracking && window.your_tracking.q) || []; - - // Enhanced API with more options - window.your_tracking = function (eventName, eventData, options) { - trigger(eventName, eventData, options); - }; - - // Public API methods - window.your_tracking.pageview = function (customData) { - trigger("pageview", customData); - }; - - window.your_tracking.event = function (category, action, label, value) { - trigger("event", { category, action, label, value }); - }; - - window.your_tracking.timing = function (category, variable, time, label) { - trigger("timing", { category, variable, time, label }); + + var payload = { + event: eventName, + url: location.href, + path: location.pathname, + domain: dataDomain, + referrer: document.referrer, + title: document.title, + utm: utmParams, + source: source, + visitor_id: visitorId, + session_id: session.sessionId, + timestamp: new Date().toISOString(), + screen: { + width: window.innerWidth, + height: window.innerHeight, + }, + language: navigator.language, + user_agent: navigator.userAgent, + data: eventData || {}, }; - - // Process queued events - for (var i = 0; i < queue.length; i++) { - var args = queue[i]; - if (typeof args[0] === "string") { - window.your_tracking.apply(this, args); - } + + // Using sendBeacon API for more reliable data sending, falling back to XHR + if (navigator.sendBeacon && !options?.forceXHR) { + navigator.sendBeacon(endpoint, JSON.stringify(payload)); + options?.callback?.(); + } else { + sendRequest(payload, options); } - - function trackPageView() { - window.your_tracking.pageview(); + } + + // Function to send HTTP requests + function sendRequest(payload, options) { + var request = new XMLHttpRequest(); + request.open("POST", endpoint, true); + request.setRequestHeader("Content-Type", "application/json"); + request.onreadystatechange = function () { + if (request.readyState === 4) { + options?.callback?.(); + } + }; + request.send(JSON.stringify(payload)); + } + + // Queue of tracking events + var queue = (window.your_tracking && window.your_tracking.q) || []; + + // Enhanced API with more options + window.your_tracking = function (eventName, eventData, options) { + trigger(eventName, eventData, options); + }; + + // Public API methods + window.your_tracking.pageview = function (customData) { + trigger("pageview", customData); + }; + + window.your_tracking.event = function (category, action, label, value) { + trigger("event", { category, action, label, value }); + }; + + window.your_tracking.timing = function (category, variable, time, label) { + trigger("timing", { category, variable, time, label }); + }; + + // Process queued events + for (var i = 0; i < queue.length; i++) { + var args = queue[i]; + if (typeof args[0] === "string") { + window.your_tracking.apply(this, args); } - - function trackSessionStart() { - var referrerInfo = document.referrer - ? { - url: document.referrer, - domain: new URL(document.referrer).hostname, - } - : null; - - trigger("session_start", { - landing_page: location.href, - referrer: referrerInfo, - }); + } + + function trackPageView() { + window.your_tracking.pageview(); + } + + function trackSessionStart() { + var referrerInfo = document.referrer + ? { + url: document.referrer, + domain: new URL(document.referrer).hostname, + } + : null; + + trigger("session_start", { + landing_page: location.href, + referrer: referrerInfo, + }); + } + + function trackSessionEnd() { + var session = sessionStorage.getItem("session_id"); + if (session) { + trigger( + "session_end", + { + duration: + Date.now() - + parseInt(sessionStorage.getItem("last_activity") || Date.now()), + }, + { forceXHR: true } + ); } - - function trackSessionEnd() { - var session = sessionStorage.getItem("session_id"); - if (session) { - trigger( - "session_end", - { - duration: - Date.now() - - parseInt(sessionStorage.getItem("last_activity") || Date.now()), - }, - { forceXHR: true }, - ); - } + } + + // Track performance metrics + function trackPerformance() { + if (window.performance && window.performance.timing) { + var timing = window.performance.timing; + var performanceData = { + load_time: Math.max(0, timing.loadEventEnd - timing.navigationStart), + dom_ready: Math.max( + 0, + timing.domContentLoadedEventEnd - timing.navigationStart + ), + network_latency: Math.max(0, timing.responseEnd - timing.requestStart), + processing_time: Math.max(0, timing.domComplete - timing.responseEnd), + total_time: Math.max(0, timing.loadEventEnd - timing.navigationStart), + }; + + // Wait for the load event to finish + setTimeout(function () { + trigger("performance", performanceData); + }, 0); } - - // Track performance metrics - function trackPerformance() { - if (window.performance && window.performance.timing) { - var timing = window.performance.timing; - var performanceData = { - load_time: Math.max(0,timing.loadEventEnd - timing.navigationStart), - dom_ready: Math.max(0, timing.domContentLoadedEventEnd - timing.navigationStart), - network_latency: Math.max(0, timing.responseEnd - timing.requestStart), - processing_time: Math.max(0, timing.domComplete - timing.responseEnd), - total_time: Math.max(0, timing.loadEventEnd - timing.navigationStart), - }; - - // Wait for the load event to finish - setTimeout(function () { - trigger("performance", performanceData); - }, 0); - } + } + + // Track outbound links + document.addEventListener("click", function (event) { + var target = event.target.closest("a"); + if (!target) return; + + var href = target.getAttribute("href") || ""; + var isOutbound = + href.indexOf("http") === 0 && !href.includes(location.hostname); + var isDownload = /\.(pdf|zip|docx?|xlsx?|pptx?|rar|tar|gz|exe)$/i.test( + href + ); + + if (isOutbound) { + trigger("outbound_click", { + url: href, + text: target.innerText, + target: target.getAttribute("target"), + }); } - - // Track outbound links - document.addEventListener("click", function (event) { - var target = event.target.closest("a"); - if (!target) return; - - var href = target.getAttribute("href") || ""; - var isOutbound = - href.indexOf("http") === 0 && !href.includes(location.hostname); - var isDownload = /\.(pdf|zip|docx?|xlsx?|pptx?|rar|tar|gz|exe)$/i.test( - href, - ); - - if (isOutbound) { - trigger("outbound_click", { - url: href, - text: target.innerText, - target: target.getAttribute("target"), - }); - } - - if (isDownload) { - trigger("download", { - file: href, - name: href.split("/").pop(), - }); - } - }); - - // Track form submissions - document.addEventListener("submit", function (event) { - var form = event.target; - if (!form || !form.tagName || form.tagName.toLowerCase() !== "form") return; - - var formId = form.id || form.getAttribute("name") || "unknown_form"; - trigger("form_submit", { - form_id: formId, - form_class: form.className, - form_action: form.action, + + if (isDownload) { + trigger("download", { + file: href, + name: href.split("/").pop(), }); + } + }); + + // Track form submissions + document.addEventListener("submit", function (event) { + var form = event.target; + if (!form || !form.tagName || form.tagName.toLowerCase() !== "form") return; + + var formId = form.id || form.getAttribute("name") || "unknown_form"; + trigger("form_submit", { + form_id: formId, + form_class: form.className, + form_action: form.action, }); - - // SPA navigation tracking - var initialPathname = location.pathname; - var lastHistoryState = history.state; - - // Listen for history API changes - var originalPushState = history.pushState; - history.pushState = function () { - originalPushState.apply(this, arguments); - handleUrlChange(); - }; - - var originalReplaceState = history.replaceState; - history.replaceState = function () { - originalReplaceState.apply(this, arguments); - handleUrlChange(); - }; - - function handleUrlChange() { - if ( - location.pathname !== initialPathname || - history.state !== lastHistoryState - ) { - setTimeout(function () { - trackPageView(); - initialPathname = location.pathname; - lastHistoryState = history.state; - }, 50); - } + }); + + // SPA navigation tracking + var initialPathname = location.pathname; + var lastHistoryState = history.state; + + // Listen for history API changes + var originalPushState = history.pushState; + history.pushState = function () { + originalPushState.apply(this, arguments); + handleUrlChange(); + }; + + var originalReplaceState = history.replaceState; + history.replaceState = function () { + originalReplaceState.apply(this, arguments); + handleUrlChange(); + }; + + function handleUrlChange() { + if ( + location.pathname !== initialPathname || + history.state !== lastHistoryState + ) { + setTimeout(function () { + trackPageView(); + initialPathname = location.pathname; + lastHistoryState = history.state; + }, 50); } - - // Track scrolling depth - var scrollDepthMarkers = [25, 50, 75, 90]; - var scrollDepthTracked = {}; - - window.addEventListener( - "scroll", - throttle(function () { - var scrollTop = window.pageYOffset || document.documentElement.scrollTop; - var scrollHeight = - document.documentElement.scrollHeight - - document.documentElement.clientHeight; - var scrollPercent = Math.round((scrollTop / scrollHeight) * 100); - - scrollDepthMarkers.forEach(function (marker) { - if (scrollPercent >= marker && !scrollDepthTracked[marker]) { - scrollDepthTracked[marker] = true; - trigger("scroll_depth", { depth: marker }); - } - }); - }, 1000), - ); - - // Initialize everything - function initialize() { - // Check for existing session - initializeSession(); - - // Track performance - if (document.readyState === "complete") { - trackPerformance(); - } else { - window.addEventListener("load", trackPerformance); - } - - // Track initial page view - trackPageView(); - - // Clean up when the page is unloaded - window.addEventListener("beforeunload", function () { - // Update last activity time to calculate session duration - sessionStorage.setItem("last_activity", Date.now().toString()); - // Use a synchronous request for beforeunload - trackSessionEnd(); + } + + // Track scrolling depth + var scrollDepthMarkers = [25, 50, 75, 90]; + var scrollDepthTracked = {}; + + window.addEventListener( + "scroll", + throttle(function () { + var scrollTop = window.pageYOffset || document.documentElement.scrollTop; + var scrollHeight = + document.documentElement.scrollHeight - + document.documentElement.clientHeight; + var scrollPercent = Math.round((scrollTop / scrollHeight) * 100); + + scrollDepthMarkers.forEach(function (marker) { + if (scrollPercent >= marker && !scrollDepthTracked[marker]) { + scrollDepthTracked[marker] = true; + trigger("scroll_depth", { depth: marker }); + } }); - - // Event listeners for navigation - window.addEventListener("popstate", handleUrlChange); - window.addEventListener("hashchange", handleUrlChange); + }, 1000) + ); + + // Initialize everything + function initialize() { + // Check for existing session + initializeSession(); + + // Track performance + if (document.readyState === "complete") { + trackPerformance(); + } else { + window.addEventListener("load", trackPerformance); } - - // Start tracking - initialize(); - })(); \ No newline at end of file + + // Track initial page view + trackPageView(); + + // Clean up when the page is unloaded + window.addEventListener("beforeunload", function () { + // Update last activity time to calculate session duration + sessionStorage.setItem("last_activity", Date.now().toString()); + // Use a synchronous request for beforeunload + trackSessionEnd(); + }); + + // Event listeners for navigation + window.addEventListener("popstate", handleUrlChange); + window.addEventListener("hashchange", handleUrlChange); + } + + // Start tracking + initialize(); +})(); diff --git a/src/components/Header/main-header.tsx b/src/components/Header/main-header.tsx index b112a69..224a274 100644 --- a/src/components/Header/main-header.tsx +++ b/src/components/Header/main-header.tsx @@ -1,20 +1,28 @@ -'use client' +"use client"; -import { ThemeSwitcher } from '@/components/theme-switcher' -import { Button } from '@/components/ui/button' -import { AnimatePresence, motion } from 'framer-motion' -import { ArrowRight, ChartBar, LayoutDashboard, LogOut, Menu, X } from 'lucide-react' -import { signOut, useSession } from 'next-auth/react' -import Image from 'next/image' -import Link from 'next/link' -import { useEffect, useState } from 'react' +import { ThemeSwitcher } from "@/components/theme-switcher"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import { AnimatePresence, motion } from "framer-motion"; +import { + ArrowRight, + ChartBar, + LayoutDashboard, + LogOut, + Menu, + X, +} from "lucide-react"; +import { signOut, useSession } from "next-auth/react"; +import Image from "next/image"; +import Link from "next/link"; +import { useEffect, useState } from "react"; function SignOut() { return (
{ - e.preventDefault() - signOut() + e.preventDefault(); + signOut(); }} >
- ) + ); } export const Header = () => { - const [ isOpen, setIsOpen ] = useState(false) - const [ isScrolled, setIsScrolled ] = useState(false) - const { data: session } = useSession() + const [isOpen, setIsOpen] = useState(false); + const [isScrolled, setIsScrolled] = useState(false); + const { data: session } = useSession(); - useEffect( - () => { - const handleScroll = () => { - setIsScrolled(window.scrollY > 10) - } - window.addEventListener('scroll', handleScroll) - return () => window.removeEventListener('scroll', handleScroll) - }, - [] - ) + useEffect(() => { + const handleScroll = () => { + setIsScrolled(window.scrollY > 10); + }; + window.addEventListener("scroll", handleScroll); + return () => window.removeEventListener("scroll", handleScroll); + }, []); const navLinks = [ - { name: 'Features', href: '/#features' }, - { name: 'Pricing', href: '/#pricing' }, - { name: 'FAQ', href: '/#faq' } - ] + { name: "Features", href: "/#features" }, + { name: "Pricing", href: "/#pricing" }, + { name: "FAQ", href: "/#faq" }, + ]; const afterAuthenticatedNavLinks = [ - { name: 'Dashboard', href: '/dashboard', icon: } - ] + { + name: "Dashboard", + href: "/dashboard", + icon: , + }, + ]; - const userImage = session?.user?.image - const userName = session?.user?.name + const userImage = session?.user?.image; + const userName = session?.user?.name; - const firstName = userName?.split(' ')[0] + const firstName = userName?.split(" ")[0]; return (
- ) -} + ); +}; diff --git a/src/lib/db.ts b/src/lib/db.ts index c282eeb..ef71e3d 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -1,8 +1,8 @@ -import {drizzle} from "drizzle-orm/postgres-js"; +import { drizzle } from "drizzle-orm/postgres-js"; import postgres from "postgres"; import * as schema from "./schema"; const connectionString = process.env.POSTGRESS_URL!; -const client = postgres(connectionString) -export const db = drizzle(client, {schema}); \ No newline at end of file +const client = postgres(connectionString); +export const db = drizzle(client, { schema }); From 76440d715fb89934d78f87053387fb564cd5901c Mon Sep 17 00:00:00 2001 From: code-env Date: Sat, 17 May 2025 01:20:40 +0100 Subject: [PATCH 2/7] Add Zustand for state management and implement AddNewSite component --- bun.lock | 3 + package.json | 3 +- src/app/dashboard/page.tsx | 103 ++++++++++------------ src/app/globals.css | 80 +++++++++++------ src/components/dashboard/add-new-site.tsx | 36 ++++++++ src/components/dashboard/add-website.tsx | 56 +++++++----- src/components/dashboard/modal.tsx | 0 src/store/index.ts | 13 +++ 8 files changed, 187 insertions(+), 107 deletions(-) create mode 100644 src/components/dashboard/add-new-site.tsx create mode 100644 src/components/dashboard/modal.tsx create mode 100644 src/store/index.ts diff --git a/bun.lock b/bun.lock index 73dfbbe..2c7f7de 100644 --- a/bun.lock +++ b/bun.lock @@ -36,6 +36,7 @@ "tailwind-merge": "^3.1.0", "tw-animate-css": "^1.2.5", "ua-parser-js": "^2.0.3", + "zustand": "^5.0.4", }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -1271,6 +1272,8 @@ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "zustand": ["zustand@5.0.4", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ=="], + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], diff --git a/package.json b/package.json index 6380a6a..58392f5 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,8 @@ "sonner": "^2.0.3", "tailwind-merge": "^3.1.0", "tw-animate-css": "^1.2.5", - "ua-parser-js": "^2.0.3" + "ua-parser-js": "^2.0.3", + "zustand": "^5.0.4" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 8746335..5e98997 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,77 +1,70 @@ import { auth } from "@/auth"; -import AddWebsite from "@/components/dashboard/add-website"; +import AddNewSite from "@/components/dashboard/add-new-site"; import { WebsiteCard } from "@/components/dashboard/wesbite-card"; -import { Button } from "@/components/ui/button"; -import {redirect} from "next/navigation"; -import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; -import { Plus } from "lucide-react"; -import { getAllProjects } from "../actions/actions"; import { Metadata } from "next"; +import { redirect } from "next/navigation"; +import { getAllProjects } from "../actions/actions"; export const metadata: Metadata = { title: { default: "Dashboard", template: "%s | WebTracker", }, - description: "A web analytics tool for tracking user behavior and performance - Dashboard", + description: + "A web analytics tool for tracking user behavior and performance - Dashboard", }; +export default async function Dashboard() { + const session = await auth(); + if (!session) { + redirect("/"); + } -export default async function Dashboard(){ - const session = await auth(); - if(!session){ - redirect("/"); - } + if (!session?.user?.id) { + redirect("/"); + } - if(!session?.user?.id){ - redirect("/"); - } + const projects = await getAllProjects(session.user.id); - const projects = await getAllProjects(session.user.id); - - - return( + return (
-
-

Your Websites

- - - - - - Add a new website - - - -
{projects?.length === 0 ? ( -
-

No websites added yet.

+
+

+ No websites added yet. +

+ +
) : ( -
- {projects?.map((website) => ( - - ))} -
+ <> +
+

+ Your Websites +

+ +
+
+ {projects?.map((website) => ( + + ))} +
+ )}
- ) -} \ No newline at end of file + ); +} diff --git a/src/app/globals.css b/src/app/globals.css index 2ce8cbc..2d934ac 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -44,7 +44,7 @@ } :root { - --radius: 0.75rem; + --radius: 0.25rem; /* Background & Foreground */ --background: oklch(1 0 0); @@ -61,8 +61,8 @@ --primary-foreground: oklch(1 0 0); /* Secondary Colors */ - --secondary: oklch(0.980 0.008 275.087); - --secondary-foreground: oklch(0.235 0.010 275.254); + --secondary: oklch(0.98 0.008 275.087); + --secondary-foreground: oklch(0.235 0.01 275.254); /* Muted */ --muted: oklch(0.969 0.007 276.287); @@ -70,7 +70,7 @@ /* Accent */ --accent: oklch(0.969 0.007 276.287); - --accent-foreground: oklch(0.235 0.010 275.254); + --accent-foreground: oklch(0.235 0.01 275.254); /* Status Colors */ --destructive: oklch(0.656 0.195 27.234); @@ -84,17 +84,17 @@ /* Chart Colors */ --chart-1: oklch(0.569 0.215 251.258); --chart-2: oklch(0.642 0.158 159.271); - --chart-3: oklch(0.550 0.193 292.394); + --chart-3: oklch(0.55 0.193 292.394); --chart-4: oklch(0.756 0.187 81.572); - --chart-5: oklch(0.480 0.022 275.448); + --chart-5: oklch(0.48 0.022 275.448); /* Sidebar */ - --sidebar: oklch(0.980 0.008 275.087); + --sidebar: oklch(0.98 0.008 275.087); --sidebar-foreground: oklch(0.141 0.005 285.823); --sidebar-primary: oklch(0.569 0.215 251.258); --sidebar-primary-foreground: oklch(1 0 0); --sidebar-accent: oklch(0.969 0.007 276.287); - --sidebar-accent-foreground: oklch(0.235 0.010 275.254); + --sidebar-accent-foreground: oklch(0.235 0.01 275.254); --sidebar-border: oklch(0.906 0.012 285.573); --sidebar-ring: oklch(0.569 0.215 251.258); } @@ -102,21 +102,21 @@ .dark { /* Background & Foreground */ --background: oklch(0.141 0.005 285.823); - --foreground: oklch(0.980 0.008 275.087); + --foreground: oklch(0.98 0.008 275.087); /* Card & Popover */ - --card: oklch(0.235 0.010 275.254); - --card-foreground: oklch(0.980 0.008 275.087); - --popover: oklch(0.235 0.010 275.254); - --popover-foreground: oklch(0.980 0.008 275.087); + --card: oklch(0.235 0.01 275.254); + --card-foreground: oklch(0.98 0.008 275.087); + --popover: oklch(0.235 0.01 275.254); + --popover-foreground: oklch(0.98 0.008 275.087); /* Primary Colors */ --primary: oklch(0.553 0.226 269.114); --primary-foreground: oklch(1 0 0); /* Secondary Colors */ - --secondary: oklch(0.235 0.010 275.254); - --secondary-foreground: oklch(0.980 0.008 275.087); + --secondary: oklch(0.235 0.01 275.254); + --secondary-foreground: oklch(0.98 0.008 275.087); /* Muted */ --muted: oklch(0.329 0.013 276.198); @@ -124,10 +124,10 @@ /* Accent */ --accent: oklch(0.329 0.013 276.198); - --accent-foreground: oklch(0.980 0.008 275.087); + --accent-foreground: oklch(0.98 0.008 275.087); /* Status Colors */ - --destructive: oklch(0.711 0.180 21.251); + --destructive: oklch(0.711 0.18 21.251); --destructive-foreground: oklch(1 0 0); /* Borders & Inputs */ @@ -143,12 +143,12 @@ --chart-5: oklch(0.652 0.013 276.198); /* Sidebar */ - --sidebar: oklch(0.235 0.010 275.254); - --sidebar-foreground: oklch(0.980 0.008 275.087); + --sidebar: oklch(0.235 0.01 275.254); + --sidebar-foreground: oklch(0.98 0.008 275.087); --sidebar-primary: oklch(0.553 0.226 269.114); --sidebar-primary-foreground: oklch(1 0 0); --sidebar-accent: oklch(0.329 0.013 276.198); - --sidebar-accent-foreground: oklch(0.980 0.008 275.087); + --sidebar-accent-foreground: oklch(0.98 0.008 275.087); --sidebar-border: oklch(0.329 0.013 276.198); --sidebar-ring: oklch(0.553 0.226 269.114); } @@ -161,7 +161,12 @@ @apply bg-background text-foreground font-sans; } - h1, h2, h3, h4, h5, h6 { + h1, + h2, + h3, + h4, + h5, + h6 { @apply font-medium; } @@ -189,19 +194,38 @@ /* Essential animations */ @keyframes float { - 0%, 100% { transform: translateY(0px); } - 50% { transform: translateY(-10px); } + 0%, + 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-10px); + } } @keyframes pulse { - 0%, 100% { transform: scale(1); opacity: 1; } - 50% { transform: scale(1.05); opacity: 0.8; } + 0%, + 100% { + transform: scale(1); + opacity: 1; + } + 50% { + transform: scale(1.05); + opacity: 0.8; + } } @keyframes blob { - 0%, 100% { transform: translate(0px, 0px) scale(1); } - 33% { transform: translate(30px, -50px) scale(1.1); } - 66% { transform: translate(-20px, 20px) scale(0.9); } + 0%, + 100% { + transform: translate(0px, 0px) scale(1); + } + 33% { + transform: translate(30px, -50px) scale(1.1); + } + 66% { + transform: translate(-20px, 20px) scale(0.9); + } } .animate-float { diff --git a/src/components/dashboard/add-new-site.tsx b/src/components/dashboard/add-new-site.tsx new file mode 100644 index 0000000..df56fb5 --- /dev/null +++ b/src/components/dashboard/add-new-site.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { + Dialog, + DialogContent, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { useCreateNewSite } from "@/store"; +import { Plus } from "lucide-react"; +import { Button } from "../ui/button"; +import AddWebsite from "./add-website"; + +const AddNewSite = () => { + const { isOpen, onOpen } = useCreateNewSite(); + return ( + + + + + + + Add a new website + + + + ); +}; + +export default AddNewSite; diff --git a/src/components/dashboard/add-website.tsx b/src/components/dashboard/add-website.tsx index 54e9345..3261540 100644 --- a/src/components/dashboard/add-website.tsx +++ b/src/components/dashboard/add-website.tsx @@ -13,6 +13,7 @@ import { useRouter } from "next/navigation"; import { toast } from "sonner"; import { Textarea } from "@/components/ui/textarea"; import Snippet from "./snippet"; +import { useCreateNewSite } from "@/store"; export default function AddWebsite() { const [website, setWebsite] = useState(""); @@ -22,20 +23,13 @@ export default function AddWebsite() { const [step, setStep] = useState(1); const router = useRouter(); const [error, setError] = useState(""); - - // Set the name automatically when website changes - // useEffect(() => { - // if (!name || name === "") { - // setName(website); - // } - // }, [website, name]); + const { onClose } = useCreateNewSite(); const addWebsite = async () => { if (website.trim() === "" || loading) return; setLoading(true); - + try { - // Submit to your API const response = await fetch("/api/project", { method: "POST", headers: { @@ -49,15 +43,15 @@ export default function AddWebsite() { }); const data = await response.json(); - + if (!response.ok) { throw new Error(data.message || "Failed to add website"); } if (data.success) { toast.success(data.message || "Website added successfully"); - router.refresh(); - setStep(2); + router.push(`/dashboard/${website.trim()}`); + onClose(); } else { toast.error(data.message || "Failed to add website"); } @@ -84,7 +78,9 @@ export default function AddWebsite() { return ( - Add Website + + Add Website + Add your website to start tracking analytics @@ -93,26 +89,34 @@ export default function AddWebsite() { {step === 1 ? (
- + setWebsite(e.target.value.trim().toLowerCase())} + onChange={(e) => + setWebsite(e.target.value.trim().toLowerCase()) + } placeholder="example.com" className={`bg-white text-black focus:border-blue-500 focus:ring-blue-500 dark:bg-zinc-800 dark:text-blue-100 dark:focus:border-blue-400 dark:focus:ring-blue-400 ${ error ? "border-red-500 dark:border-red-400" : "" }`} /> {error ? ( -

{error}

+

+ {error} +

) : (

Enter the domain or subdomain without "www"

)}
- +
- + setName(e.target.value)} @@ -120,9 +124,11 @@ export default function AddWebsite() { className="bg-white text-black focus:border-blue-500 focus:ring-blue-500 dark:bg-zinc-800 dark:text-blue-100 dark:focus:border-blue-400 dark:focus:ring-blue-400" />
- +
- +