more analytics
Some checks failed
Deploy to Server / deploy (push) Failing after 1m7s

This commit is contained in:
rocord01 2026-06-16 03:45:30 -04:00
parent dac9fab4a8
commit 40d44bcfda
11 changed files with 84 additions and 17 deletions

View file

@ -1,5 +1,6 @@
"use client";
import { usePlausible } from 'next-plausible';
import { useState, useEffect, useCallback } from 'react';
import { useAuth } from '@/contexts/AuthContext';
import { useApi } from '@/hooks/use-api';
@ -17,6 +18,7 @@ export default function ConferenceDetails({ conferenceId }) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const { token, isLoggedIn } = useAuth();
const plausible = usePlausible();
const apiFetch = useApi();
const [isConnecting, setIsConnecting] = useState(false);
const [currentUserExtension, setCurrentUserExtension] = useState(null);
@ -85,6 +87,7 @@ export default function ConferenceDetails({ conferenceId }) {
const handleConnectMe = async () => {
if (!token) return;
plausible('Conference Connect', {props: {room: conferenceId}});
setIsConnecting(true);
try {
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL}/conferences/${conferenceId}/connectme`, {
@ -149,7 +152,7 @@ export default function ConferenceDetails({ conferenceId }) {
<div className="container py-12 px-4">
<div className="max-w-4xl mx-auto">
<div className="mb-8">
<Button asChild variant="outline" size="sm">
<Button asChild variant="outline" size="sm" onClick={() => plausible('Conference Back to List')}>
<Link href="/conferences">
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Conferences
@ -165,7 +168,7 @@ export default function ConferenceDetails({ conferenceId }) {
Participants currently in the room.
</p>
<div className="flex items-center justify-center gap-4 mt-6">
<Button onClick={fetchDetails} disabled={loading}>
<Button onClick={() => { plausible('Conference Refresh'); fetchDetails(); }} disabled={loading}>
<RefreshCw className={`mr-2 h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
{loading ? 'Refreshing...' : 'Refresh'}
</Button>

View file

@ -1,5 +1,6 @@
"use client";
import { usePlausible } from 'next-plausible';
import { useState, useEffect, useCallback } from 'react';
import { useApi } from '@/hooks/use-api';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
@ -13,6 +14,7 @@ export default function ConferenceList() {
const [conferences, setConferences] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const plausible = usePlausible();
const apiFetch = useApi();
const fetchConferences = useCallback(async () => {
@ -58,7 +60,7 @@ export default function ConferenceList() {
Join a conversation or see who's currently in a conference.
</p>
<div className="flex items-center justify-center gap-4 mt-6">
<Button onClick={fetchConferences} disabled={loading}>
<Button onClick={() => { plausible('Conference Refresh'); fetchConferences(); }} disabled={loading}>
<RefreshCw className={`mr-2 h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
{loading ? 'Refreshing...' : 'Refresh'}
</Button>
@ -114,7 +116,7 @@ export default function ConferenceList() {
<span>{room.locked ? 'Locked' : 'Open'}</span>
</div>
</div>
<Button asChild variant="outline" size="sm">
<Button asChild variant="outline" size="sm" onClick={() => plausible('Conference View', {props: {room: room.conferenceId}})}>
<Link href={`/conferences?id=${room.conferenceId}`}>
View <ArrowRight className="ml-2 h-4 w-4" />
</Link>

View file

@ -1,5 +1,6 @@
"use client";
import { usePlausible } from 'next-plausible';
import { useState } from 'react';
import { useAuth } from '@/contexts/AuthContext';
import { useApi } from '@/hooks/use-api';
@ -39,6 +40,7 @@ function CredentialRow({ icon, label, value, onCopy, isCopied }) {
export function SipCredentialsCard({ details, loading, refetchDetails }) {
const { token } = useAuth();
const plausible = usePlausible();
const apiFetch = useApi();
const [newSecret, setNewSecret] = useState(null);
const [isResetting, setIsResetting] = useState(false);
@ -68,6 +70,7 @@ export function SipCredentialsCard({ details, loading, refetchDetails }) {
const copyToClipboard = (text, id) => {
if (!text) return;
plausible('Dashboard Credentials Copy', {props: {field: id}});
navigator.clipboard.writeText(text).then(() => {
setCopiedState(prev => ({ ...prev, [id]: true }));
setTimeout(() => {

View file

@ -1,5 +1,6 @@
"use client";
import { usePlausible } from 'next-plausible';
import { useState, useEffect, useCallback } from 'react';
import { useAuth } from '@/contexts/AuthContext';
import { useApi } from '@/hooks/use-api';
@ -26,6 +27,7 @@ import { Badge } from "@/components/ui/badge"
export default function ApiKeys() {
const { token } = useAuth();
const plausible = usePlausible();
const apiFetch = useApi();
const [keys, setKeys] = useState([]);
const [loading, setLoading] = useState(true);
@ -76,6 +78,7 @@ export default function ApiKeys() {
}
// Use newKeyData.apiKey from the response
setNewKey(newKeyData.apiKey);
plausible('Dashboard Create API Key');
// Add the new key to the local state, transforming the object to match the table's expected structure
const keyForState = {
@ -108,6 +111,7 @@ export default function ApiKeys() {
throw new Error(errorData.error);
}
setKeys(prevKeys => prevKeys.filter(key => key.key !== keyToDelete));
plausible('Dashboard Delete API Key');
} catch (error) {
if (error.message !== 'Unauthorized') {
console.error("Failed to delete key", error);

View file

@ -1,5 +1,6 @@
"use client";
import { usePlausible } from 'next-plausible';
import { useState, useEffect } from 'react';
import { useAuth } from '@/contexts/AuthContext';
import { useApi } from '@/hooks/use-api';
@ -41,6 +42,7 @@ function OverviewCard({ details, deviceStatus, loading }) {
export default function DashboardClient() {
const { isLoggedIn, loading, logout, token, noExtension } = useAuth();
const plausible = usePlausible();
const [details, setDetails] = useState(null);
const [detailsLoading, setDetailsLoading] = useState(true);
const [deviceStatus, setDeviceStatus] = useState(null);
@ -110,6 +112,7 @@ export default function DashboardClient() {
}, [token, deviceStatusLoading, apiFetch]);
const handleLogin = () => {
plausible('Dashboard Login');
window.location.href = `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/auth/discord`;
};
@ -217,7 +220,7 @@ export default function DashboardClient() {
)}
</div>
</div>
<Button variant="outline" onClick={logout} className="w-full sm:w-auto">
<Button variant="outline" onClick={() => { plausible('Dashboard Logout'); logout(); }} className="w-full sm:w-auto">
<LogOut className="h-4 w-4 mr-2" />
Logout
</Button>

View file

@ -1,5 +1,6 @@
"use client";
import { usePlausible } from 'next-plausible';
import { useState, useEffect, useCallback } from 'react';
import { useAuth } from '@/contexts/AuthContext';
import { useApi } from '@/hooks/use-api';
@ -66,6 +67,7 @@ function DeviceCard({ device }) {
export function RegisteredDevices() {
const { token } = useAuth();
const plausible = usePlausible();
const [devices, setDevices] = useState([]);
const [loading, setLoading] = useState(true);
const [isInitialLoad, setIsInitialLoad] = useState(true);
@ -81,6 +83,9 @@ export function RegisteredDevices() {
if (res.ok) {
const data = await res.json();
setDevices(Array.isArray(data) ? data : []);
if (isInitialLoad && Array.isArray(data) && data.length > 0) {
plausible('Dashboard View Devices', {props: {count: data.length}});
}
} else {
setDevices([]);
}

View file

@ -1,5 +1,6 @@
"use client";
import { usePlausible } from 'next-plausible';
import { useEffect, useState, useCallback, useMemo } from "react";
import { useAuth } from "@/contexts/AuthContext";
import { useApi } from "@/hooks/use-api";
@ -49,6 +50,7 @@ function formatVoicemailDate(dateStr) {
export function VoicemailPanel({ extensionId: extProp }) {
const { token } = useAuth();
const plausible = usePlausible();
const apiFetch = useApi();
const [extensionId, setExtensionId] = useState(extProp || null);
const [loadingExt, setLoadingExt] = useState(!extProp);
@ -127,6 +129,9 @@ export function VoicemailPanel({ extensionId: extProp }) {
const toggleExpand = async (vm) => {
const isOpening = expandedId !== vm.messageId;
setExpandedId(isOpening ? vm.messageId : null);
if (isOpening) {
plausible('Dashboard Play Voicemail');
}
if (isOpening && vm.hasAudio && !audioMap[vm.messageId]?.url) {
setAudioMap(m => ({ ...m, [vm.messageId]: { url: null, loading: true } }));
try {
@ -169,6 +174,7 @@ export function VoicemailPanel({ extensionId: extProp }) {
const downloadVoicemail = async (vm) => {
if (!vm.hasAudio) return;
plausible('Dashboard Download Voicemail');
try {
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL}/extensions/${extensionId}/voicemails/${vm.messageId}/download`);
if (!res.ok) return;

View file

@ -1,5 +1,7 @@
"use client"
import { useRef, useEffect } from 'react'
import { usePlausible } from 'next-plausible'
import { Phone, Users, VoicemailIcon, Radio, MoreHorizontal, Network, Bot, LayoutDashboard } from 'lucide-react'
import Link from 'next/link'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
@ -67,8 +69,26 @@ const colorClasses = {
}
export default function Features() {
const plausible = usePlausible();
const featuresRef = useRef(null);
const hasFiredRef = useRef(false);
useEffect(() => {
const el = featuresRef.current;
if (!el) return;
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting && !hasFiredRef.current) {
hasFiredRef.current = true;
plausible('Scroll to Features');
observer.unobserve(el);
}
}, { threshold: 0.3 });
observer.observe(el);
return () => observer.disconnect();
}, [plausible]);
return (
<section id="features" className="relative py-32 overflow-hidden">
<section ref={featuresRef} id="features" className="relative py-32 overflow-hidden">
<div className="container relative">
<div className="text-center max-w-3xl mx-auto mb-16 px-4">
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold text-white mb-6 bg-gradient-to-r from-white via-blue-100 to-white bg-clip-text text-transparent pb-2">

View file

@ -23,19 +23,19 @@ export default function Header() {
<span className="hidden font-bold text-white sm:inline-block">LiteNet</span>
</Link>
<nav className="flex items-center space-x-6 text-sm font-medium">
<Link href="/#features" className="text-gray-300 hover:text-white">
<Link href="/#features" className="text-gray-300 hover:text-white" onClick={() => plausible('Nav Click', {props: {section: 'Features'}})}>
Features
</Link>
<Link href="/#updates" className="text-gray-300 hover:text-white">
<Link href="/#updates" className="text-gray-300 hover:text-white" onClick={() => plausible('Nav Click', {props: {section: 'Updates'}})}>
Updates
</Link>
<Link href="/#team" className="text-gray-300 hover:text-white">
<Link href="/#team" className="text-gray-300 hover:text-white" onClick={() => plausible('Nav Click', {props: {section: 'Team'}})}>
Team
</Link>
<Link href="/hardware-survey" className="text-gray-300 hover:text-white">
<Link href="/hardware-survey" className="text-gray-300 hover:text-white" onClick={() => plausible('Nav Click', {props: {section: 'Hardware Survey'}})}>
Hardware Survey
</Link>
<Link href="/conferences" className="text-gray-300 hover:text-white">
<Link href="/conferences" className="text-gray-300 hover:text-white" onClick={() => plausible('Nav Click', {props: {section: 'Conferences'}})}>
Conferences
</Link>
</nav>
@ -119,35 +119,35 @@ export default function Header() {
<Link
href="/#features"
className="text-gray-300 hover:text-white transition-colors px-2 py-2 rounded-md hover:bg-gray-800/50"
onClick={() => setMobileMenuOpen(false)}
onClick={() => { plausible('Nav Click', {props: {section: 'Features'}}); setMobileMenuOpen(false); }}
>
Features
</Link>
<Link
href="/#updates"
className="text-gray-300 hover:text-white transition-colors px-2 py-2 rounded-md hover:bg-gray-800/50"
onClick={() => setMobileMenuOpen(false)}
onClick={() => { plausible('Nav Click', {props: {section: 'Updates'}}); setMobileMenuOpen(false); }}
>
Updates
</Link>
<Link
href="/#team"
className="text-gray-300 hover:text-white transition-colors px-2 py-2 rounded-md hover:bg-gray-800/50"
onClick={() => setMobileMenuOpen(false)}
onClick={() => { plausible('Nav Click', {props: {section: 'Team'}}); setMobileMenuOpen(false); }}
>
Team
</Link>
<Link
href="/hardware-survey"
className="text-gray-300 hover:text-white transition-colors px-2 py-2 rounded-md hover:bg-gray-800/50"
onClick={() => setMobileMenuOpen(false)}
onClick={() => { plausible('Nav Click', {props: {section: 'Hardware Survey'}}); setMobileMenuOpen(false); }}
>
Hardware Survey
</Link>
<Link
href="/conferences"
className="text-gray-300 hover:text-white transition-colors px-2 py-2 rounded-md hover:bg-gray-800/50"
onClick={() => setMobileMenuOpen(false)}
onClick={() => { plausible('Nav Click', {props: {section: 'Conferences'}}); setMobileMenuOpen(false); }}
>
Conferences
</Link>

View file

@ -59,6 +59,7 @@ export default function Hero() {
<Button
size="lg"
className="bg-blue-600 hover:bg-blue-700 text-white shadow-lg hover:shadow-xl transition-all duration-300 group"
onClick={() => plausible('CTA Get Started')}
asChild
>
<Link href="/dashboard">

View file

@ -1,5 +1,7 @@
"use client"
import { useRef, useEffect } from 'react'
import { usePlausible } from 'next-plausible'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
@ -69,8 +71,26 @@ function TeamMember({ name, ext, image, discord, role }) {
}
export default function Team() {
const plausible = usePlausible();
const teamRef = useRef(null);
const hasFiredRef = useRef(false);
useEffect(() => {
const el = teamRef.current;
if (!el) return;
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting && !hasFiredRef.current) {
hasFiredRef.current = true;
plausible('Scroll to Team');
observer.unobserve(el);
}
}, { threshold: 0.3 });
observer.observe(el);
return () => observer.disconnect();
}, [plausible]);
return (
<section id="team" className="relative py-32 overflow-hidden">
<section ref={teamRef} id="team" className="relative py-32 overflow-hidden">
<div className="container relative">
<div className="text-center max-w-3xl mx-auto mb-16 px-4">
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold text-white mb-6 bg-gradient-to-r from-white via-purple-100 to-white bg-clip-text text-transparent pb-2">