aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorLibravatarLibravatar Biswa Kalyan Bhuyan <[email protected]> 2025-04-26 15:55:15 +0530
committerLibravatarLibravatar Biswa Kalyan Bhuyan <[email protected]> 2025-04-26 15:55:15 +0530
commit550f111681be38ddfbc8679d01aeac169ef34c1d (patch)
tree1c1c9f8bfef7b89f3a5fe6eee00e1af114154324 /app
parentc861ed705bd61eb98984efd9caaddfdd3128155f (diff)
downloadrealtimeloc-550f111681be38ddfbc8679d01aeac169ef34c1d.tar.gz
realtimeloc-550f111681be38ddfbc8679d01aeac169ef34c1d.tar.bz2
realtimeloc-550f111681be38ddfbc8679d01aeac169ef34c1d.zip
feat: updated the more ui and updated the /track page
Diffstat (limited to 'app')
-rw-r--r--app/src/app/layout.tsx18
-rw-r--r--app/src/app/page.tsx10
-rw-r--r--app/src/app/track/page.tsx148
-rw-r--r--app/src/components/navbar.tsx100
-rw-r--r--app/src/components/theme-provider.tsx9
-rw-r--r--app/src/components/ui/theme-toggle.tsx61
6 files changed, 337 insertions, 9 deletions
diff --git a/app/src/app/layout.tsx b/app/src/app/layout.tsx
index d461f2d..97ebb2a 100644
--- a/app/src/app/layout.tsx
+++ b/app/src/app/layout.tsx
@@ -2,6 +2,8 @@ import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { Toaster } from "@/components/ui/sonner";
+import { ThemeProvider } from "@/components/theme-provider";
+import Navbar from "@/components/navbar";
const inter = Inter({ subsets: ["latin"] });
@@ -17,9 +19,19 @@ export default function RootLayout({
}>) {
return (
<html lang="en" suppressHydrationWarning={true}>
- <body className={inter.className}>
- {children}
- <Toaster />
+ <body className={`${inter.className} bg-background text-foreground`}>
+ <ThemeProvider
+ attribute="class"
+ defaultTheme="system"
+ enableSystem
+ disableTransitionOnChange
+ >
+ <div className="flex flex-col min-h-screen">
+ <Navbar />
+ <main className="flex-1 w-full">{children}</main>
+ </div>
+ <Toaster />
+ </ThemeProvider>
</body>
</html>
);
diff --git a/app/src/app/page.tsx b/app/src/app/page.tsx
index 736b472..beecc8f 100644
--- a/app/src/app/page.tsx
+++ b/app/src/app/page.tsx
@@ -4,8 +4,8 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
export default function Home() {
return (
- <main className="flex min-h-screen flex-col items-center justify-center p-4 bg-slate-50">
- <Card className="w-full max-w-lg shadow-lg">
+ <main className="flex flex-col items-center justify-center p-4 bg-background">
+ <Card className="w-full max-w-lg">
<CardHeader>
<CardTitle className="text-2xl text-center">Real-Time Location Tracking</CardTitle>
<CardDescription className="text-center">
@@ -52,7 +52,7 @@ export default function Home() {
<CardTitle className="text-lg">Real-Time Tracking</CardTitle>
</CardHeader>
<CardContent>
- <p className="text-sm text-slate-600">Connect with others in real-time to see their current location.</p>
+ <p className="text-sm text-muted-foreground">Connect with others in real-time to see their current location.</p>
</CardContent>
</Card>
<Card>
@@ -60,13 +60,13 @@ export default function Home() {
<CardTitle className="text-lg">Location Sharing</CardTitle>
</CardHeader>
<CardContent>
- <p className="text-sm text-slate-600">Share your location via email links that can be viewed by anyone.</p>
+ <p className="text-sm text-muted-foreground">Share your location via email links that can be viewed by anyone.</p>
</CardContent>
</Card>
</div>
</div>
- <footer className="mt-8 text-center text-sm text-slate-500">
+ <footer className="mt-8 text-center text-sm text-muted-foreground">
&copy; {new Date().getFullYear()} Real-Time Location Tracker
</footer>
</main>
diff --git a/app/src/app/track/page.tsx b/app/src/app/track/page.tsx
index 0519ecb..060d391 100644
--- a/app/src/app/track/page.tsx
+++ b/app/src/app/track/page.tsx
@@ -1 +1,147 @@
- \ No newline at end of file
+"use client";
+
+import { useState, useEffect } from "react";
+import { useSocket } from "@/hooks/useSocket";
+import Map from "@/components/Map";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { toast } from "sonner";
+
+export default function TrackPage() {
+ const [userId, setUserId] = useState<string>("");
+ const [location, setLocation] = useState<{
+ latitude: number;
+ longitude: number;
+ accuracy?: number;
+ } | null>(null);
+ const [isTracking, setIsTracking] = useState(false);
+ const { isConnected, socket } = useSocket();
+
+ // Generate a random user ID on component mount if not already set
+ useEffect(() => {
+ if (!userId) {
+ const newUserId = `user_${Math.random().toString(36).substring(2, 9)}`;
+ setUserId(newUserId);
+ }
+ }, [userId]);
+
+ // Handle location updates from the map component
+ const handleLocationUpdate = (location: {
+ latitude: number;
+ longitude: number;
+ accuracy?: number;
+ timestamp: Date;
+ }) => {
+ setLocation({
+ latitude: location.latitude,
+ longitude: location.longitude,
+ accuracy: location.accuracy,
+ });
+
+ // Only send location updates if we're tracking
+ if (isTracking && socket && isConnected) {
+ socket.emit('location:update', {
+ userId,
+ latitude: location.latitude,
+ longitude: location.longitude,
+ accuracy: location.accuracy,
+ timestamp: new Date().toISOString()
+ });
+ }
+ };
+
+ // Toggle location tracking
+ const toggleTracking = () => {
+ if (!isTracking && socket) {
+ // Register user when starting tracking
+ socket.emit('user:register', userId);
+ }
+
+ setIsTracking(!isTracking);
+
+ if (!isTracking) {
+ toast.success("Location tracking started");
+ } else {
+ toast.info("Location tracking stopped");
+ }
+ };
+
+ return (
+ <div className="container mx-auto py-8 space-y-8">
+ <h1 className="text-3xl font-bold">Real-Time Location Tracking</h1>
+
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
+ <div className="md:col-span-2">
+ <div className="h-[500px] rounded-lg overflow-hidden border">
+ <Map
+ onLocationUpdate={handleLocationUpdate}
+ mode="tracking"
+ />
+ </div>
+ </div>
+
+ <div className="space-y-4">
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center">
+ Tracking Controls
+ {isConnected && (
+ <span className="ml-2 text-xs text-green-500 inline-flex items-center">
+ <span className="h-2 w-2 bg-green-500 rounded-full mr-1"></span>
+ Connected
+ </span>
+ )}
+ </CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="space-y-4">
+ <div>
+ <p className="text-sm text-muted-foreground mb-2">Your user ID: {userId}</p>
+ <Button
+ onClick={toggleTracking}
+ className={isTracking ? "bg-red-500 hover:bg-red-600" : ""}
+ >
+ {isTracking ? "Stop Tracking" : "Start Tracking"}
+ </Button>
+ </div>
+ {location && (
+ <div className="pt-4 border-t">
+ <h3 className="font-medium mb-2">Current Location</h3>
+ <p className="text-sm">Latitude: {location.latitude.toFixed(6)}</p>
+ <p className="text-sm">Longitude: {location.longitude.toFixed(6)}</p>
+ {location.accuracy && (
+ <p className="text-sm">Accuracy: {location.accuracy.toFixed(2)} meters</p>
+ )}
+ </div>
+ )}
+ </div>
+ </CardContent>
+ </Card>
+
+ <Card>
+ <CardHeader>
+ <CardTitle>Status</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <p className="text-sm mb-2">
+ Socket Connection: {isConnected ?
+ <span className="text-green-500">Connected</span> :
+ <span className="text-red-500">Disconnected</span>
+ }
+ </p>
+ <p className="text-sm mb-2">
+ Tracking Status: {isTracking ?
+ <span className="text-green-500">Active</span> :
+ <span className="text-yellow-500">Inactive</span>
+ }
+ </p>
+ <p className="text-sm">
+ Location: {location ? 'Available' : 'Waiting...'}
+ </p>
+ </CardContent>
+ </Card>
+ </div>
+ </div>
+ </div>
+ );
+} \ No newline at end of file
diff --git a/app/src/components/navbar.tsx b/app/src/components/navbar.tsx
new file mode 100644
index 0000000..37f1a5b
--- /dev/null
+++ b/app/src/components/navbar.tsx
@@ -0,0 +1,100 @@
+"use client";
+
+import Link from "next/link";
+import { useState } from "react";
+import { MapPin, Menu, X } from "lucide-react";
+import { SimpleThemeToggle } from "@/components/ui/theme-toggle";
+import { Button } from "@/components/ui/button";
+
+export default function Navbar() {
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
+
+ const toggleMenu = () => {
+ setIsMenuOpen(!isMenuOpen);
+ };
+
+ return (
+ <nav className="border-b bg-background">
+ <div className="container mx-auto px-4 py-3">
+ <div className="flex items-center justify-between">
+ {/* Logo and Title */}
+ <Link href="/" className="flex items-center space-x-2">
+ <MapPin className="h-6 w-6 text-primary" />
+ <span className="text-lg font-bold">Location Tracker</span>
+ </Link>
+
+ {/* Desktop Navigation */}
+ <div className="hidden md:flex items-center space-x-6">
+ <Link
+ href="/map"
+ className="text-foreground/70 hover:text-foreground transition-colors"
+ >
+ Map
+ </Link>
+ <Link
+ href="/track"
+ className="text-foreground/70 hover:text-foreground transition-colors"
+ >
+ Track
+ </Link>
+ <Link
+ href="/share"
+ className="text-foreground/70 hover:text-foreground transition-colors"
+ >
+ Share
+ </Link>
+ <SimpleThemeToggle />
+ </div>
+
+ {/* Mobile Navigation Toggle */}
+ <div className="flex items-center md:hidden">
+ <div className="mr-2">
+ <SimpleThemeToggle />
+ </div>
+ <Button
+ variant="ghost"
+ size="icon"
+ onClick={toggleMenu}
+ >
+ {isMenuOpen ? (
+ <X className="h-5 w-5" />
+ ) : (
+ <Menu className="h-5 w-5" />
+ )}
+ <span className="sr-only">Toggle menu</span>
+ </Button>
+ </div>
+ </div>
+
+ {/* Mobile Navigation Menu */}
+ {isMenuOpen && (
+ <div className="mt-2 py-2 border-t md:hidden">
+ <div className="flex flex-col space-y-2">
+ <Link
+ href="/map"
+ className="px-2 py-2 rounded-md hover:bg-accent transition-colors"
+ onClick={() => setIsMenuOpen(false)}
+ >
+ Map
+ </Link>
+ <Link
+ href="/track"
+ className="px-2 py-2 rounded-md hover:bg-accent transition-colors"
+ onClick={() => setIsMenuOpen(false)}
+ >
+ Track
+ </Link>
+ <Link
+ href="/share"
+ className="px-2 py-2 rounded-md hover:bg-accent transition-colors"
+ onClick={() => setIsMenuOpen(false)}
+ >
+ Share
+ </Link>
+ </div>
+ </div>
+ )}
+ </div>
+ </nav>
+ );
+} \ No newline at end of file
diff --git a/app/src/components/theme-provider.tsx b/app/src/components/theme-provider.tsx
new file mode 100644
index 0000000..9fced93
--- /dev/null
+++ b/app/src/components/theme-provider.tsx
@@ -0,0 +1,9 @@
+"use client";
+
+import * as React from "react";
+import { ThemeProvider as NextThemesProvider } from "next-themes";
+import { type ThemeProviderProps } from "next-themes/dist/types";
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
+} \ No newline at end of file
diff --git a/app/src/components/ui/theme-toggle.tsx b/app/src/components/ui/theme-toggle.tsx
new file mode 100644
index 0000000..fbf7448
--- /dev/null
+++ b/app/src/components/ui/theme-toggle.tsx
@@ -0,0 +1,61 @@
+"use client";
+
+import * as React from "react";
+import { useTheme } from "next-themes";
+import { Moon, Sun } from "lucide-react";
+
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+
+export function ThemeToggle() {
+ const { setTheme, theme } = useTheme();
+
+ return (
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button variant="outline" size="icon" className="rounded-full">
+ <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
+ <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
+ <span className="sr-only">Toggle theme</span>
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="end">
+ <DropdownMenuItem onClick={() => setTheme("light")}>
+ Light
+ </DropdownMenuItem>
+ <DropdownMenuItem onClick={() => setTheme("dark")}>
+ Dark
+ </DropdownMenuItem>
+ <DropdownMenuItem onClick={() => setTheme("system")}>
+ System
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ );
+}
+
+export function SimpleThemeToggle() {
+ const { setTheme, theme } = useTheme();
+
+ const toggleTheme = () => {
+ setTheme(theme === "dark" ? "light" : "dark");
+ };
+
+ return (
+ <Button
+ variant="ghost"
+ size="icon"
+ onClick={toggleTheme}
+ className="rounded-full h-9 w-9 relative"
+ aria-label="Toggle theme"
+ >
+ <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
+ <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
+ </Button>
+ );
+} \ No newline at end of file