Compare commits

...

5 Commits

Author SHA1 Message Date
8bd6a5ba84 Update api endpoints to production 2025-05-07 23:49:45 +03:30
2b8f664e59 Add share button for public feeds 2025-05-07 23:48:27 +03:30
db32cbfdf2 Add public feeds page 2025-05-07 23:44:49 +03:30
d60607a424 Add UI stuff for editing feed 2025-05-07 23:05:49 +03:30
8e36304b5a Add new gettallfeed api 2025-05-07 22:28:02 +03:30
11 changed files with 1000 additions and 657 deletions

1029
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,9 @@ import { DashboardPage } from "@/pages/DashboardPage";
import { ApiKeysPage } from "@/pages/ApiKeysPage"; import { ApiKeysPage } from "@/pages/ApiKeysPage";
import { ProfilePage } from "@/pages/ProfilePage"; import { ProfilePage } from "@/pages/ProfilePage";
import { VerifyEmail } from "@/pages/VerifyEmail"; import { VerifyEmail } from "@/pages/VerifyEmail";
import { PublicFeedPage } from "@/pages/PublicFeedPage";
function App() { function App() {
return ( return (
@@ -52,7 +55,9 @@ function App() {
} }
/> />
<Route path="*" element={<Navigate to="/" replace />} /> <Route path="*" element={<Navigate to="/" replace />} />
<Route path="/publicfeed/:feedId" element={<PublicFeedPage />} />
</Route> </Route>
</Routes> </Routes>
</Router> </Router>
</ThemeProvider> </ThemeProvider>

View File

@@ -0,0 +1,83 @@
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "./ui/dialog";
import { Input } from "./ui/input";
import { Label } from "./ui/label";
import { Checkbox } from "./ui/checkbox";
import { Button } from "./ui/button";
import { useState, useEffect } from "react";
export interface FeedDialogProps {
open: boolean;
onClose: () => void;
onSubmit: (name: string, isPublic: boolean) => void;
initialName?: string;
initialIsPublic?: boolean;
mode?: "create" | "edit";
}
export function FeedDialog({
open,
onClose,
onSubmit,
initialName = "",
initialIsPublic = false,
mode = "create",
}: FeedDialogProps) {
const [name, setName] = useState(initialName);
const [isPublic, setIsPublic] = useState(initialIsPublic);
useEffect(() => {
setName(initialName);
setIsPublic(initialIsPublic);
}, [initialName, initialIsPublic]);
const handleSubmit = () => {
if (name.trim()) {
onSubmit(name.trim(), isPublic);
onClose();
}
};
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent>
<DialogHeader>
<DialogTitle>{mode === "edit" ? "Edit Feed" : "Create New Feed"}</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label htmlFor="feed-name">Feed Name</Label>
<Input
id="feed-name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter feed name"
/>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="is-public"
checked={isPublic}
onCheckedChange={(val) => setIsPublic(Boolean(val))}
/>
<Label htmlFor="is-public">Make feed public</Label>
</div>
</div>
<DialogFooter>
<Button onClick={handleSubmit}>
{mode === "edit" ? "Save Changes" : "Create Feed"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,15 @@
// components/ui/badge.tsx
import { cn } from "@/lib/utils";
export function Badge({ children, className }: { children: React.ReactNode; className?: string }) {
return (
<span
className={cn(
"text-xs px-2 py-0.5 rounded font-semibold inline-block",
className
)}
>
{children}
</span>
);
}

View File

@@ -0,0 +1,23 @@
import * as React from "react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { Check } from "lucide-react";
import { cn } from "@/lib/utils";
export const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator className="flex items-center justify-center text-current">
<Check className="h-3.5 w-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
Checkbox.displayName = CheckboxPrimitive.Root.displayName;

View File

@@ -87,43 +87,59 @@ export const getAllFeeds = async () => {
} }
}; };
export const updateFeed = async (feedId: string, name: string, isPublic: boolean) => { export const addNewFeed = async (name: string, isPublic: boolean) => {
try {
const token = getToken(); const token = getToken();
if (!token) throw new Error('Authentication required'); if (!token) {
throw new Error("Authentication required");
}
const response = await userDataApi.patch('/api/updatefeed', { const response = await userDataApi.post(
feedId, "/api/addnewfeed",
{
Name: name, Name: name,
IsPublic: isPublic, IsPublic: isPublic,
}, { },
headers: { {
Authorization: `Bearer ${token}`
}
});
return response.data;
};
export const addNewFeed = async (name: string, isPublic: boolean) => {
const token = getToken();
if (!token) throw new Error('Authentication required');
try {
const response = await userDataApi.post('/api/addnewfeed', {
Name: name,
IsPublic: isPublic
}, {
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
'Content-Type': 'application/json' },
} }
}); );
return response.data; return response.data;
} catch (error: any) { } catch (error: any) {
throw error.response?.data || { success: false, message: 'Network error' }; throw error.response?.data || { success: false, message: "Network error" };
} }
}; };
export const updateFeed = async (feedId: string, name: string, isPublic: boolean): Promise<boolean> => {
try {
const token = getToken();
if (!token) {
throw new Error("Authentication required");
}
const response = await userDataApi.patch(
`/api/updatefeed`,
{
feedId: feedId,
Name: name,
IsPublic: isPublic,
},
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
return response.status === 200;
} catch (error: any) {
return false;
}
};
export const deleteFeed = async (feedId: string) => { export const deleteFeed = async (feedId: string) => {
try { try {
const response = await userDataApi.delete('/api/deletefeed', { const response = await userDataApi.delete('/api/deletefeed', {
@@ -148,6 +164,23 @@ export const getFeedDataTimeRange = async (
} }
}; };
export const getPublicFeedDataTimeRange = async (
feedId: string,
startTime: string,
endTime: string
): Promise<{ feedName: string; data: { timestamp: string; data: string }[] }> => {
try {
const response = await dataApi.get(`/dash/getpublicfeeddatatimerange/${feedId}/${startTime}/${endTime}`);
return response.data;
} catch (error: any) {
if (error.response?.status === 404) {
throw new Error("This feed is private or does not exist.");
}
throw new Error("Failed to fetch public feed data.");
}
};
// API Key functions // API Key functions
export const createApiKey = async () => { export const createApiKey = async () => {
try { try {

View File

@@ -25,7 +25,7 @@ export interface ApiKeyResponse {
apiKey: string; apiKey: string;
} }
// New ApiKey Typez // New ApiKey Type
export interface ApiKey { export interface ApiKey {
createdAt: string; createdAt: string;
key: string; key: string;
@@ -35,6 +35,13 @@ export interface ApiKey {
export interface Feed { export interface Feed {
id: string; id: string;
createdAt: string; createdAt: string;
name: string;
isPublic: boolean;
}
export interface AddFeedRequest {
Name: string;
IsPublic: boolean;
} }
export interface AddFeedResponse { export interface AddFeedResponse {
@@ -42,6 +49,7 @@ export interface AddFeedResponse {
feedId: string; feedId: string;
} }
export interface FeedDataPoint { export interface FeedDataPoint {
timestamp: string; timestamp: string;
data: string; data: string;

View File

@@ -5,6 +5,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import App from "./App"; import App from "./App";
import "./index.css"; import "./index.css";
// Create a client for React Query // Create a client for React Query
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
@@ -25,6 +26,7 @@ ReactDOM.createRoot(rootElement).render(
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<App /> <App />
<Toaster position="top-right" /> <Toaster position="top-right" />
</QueryClientProvider> </QueryClientProvider>
</React.StrictMode> </React.StrictMode>
); );

View File

@@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom";
import { toast } from "sonner"; import { toast } from "sonner";
import { isAuthenticated } from "@/lib/auth"; import { isAuthenticated } from "@/lib/auth";
import { useFeedStore } from "@/lib/store"; import { useFeedStore } from "@/lib/store";
import { addNewFeed } from "@/lib/api"; import { addNewFeed, updateFeed } from "@/lib/api";
import { formatDateTime, getTimeRangeOptions } from "@/lib/timeRanges"; import { formatDateTime, getTimeRangeOptions } from "@/lib/timeRanges";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
@@ -15,10 +15,11 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { Plus, RefreshCw, ScanBarcode } from "lucide-react"; import { Plus, RefreshCw, ScanBarcode, Edit, Copy } from "lucide-react";
import { FeedChart } from "@/components/FeedChart"; import { FeedChart } from "@/components/FeedChart";
import { FeedActions } from "@/components/FeedActions"; import { FeedActions } from "@/components/FeedActions";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { FeedDialog } from "@/components/FeedDialog";
export function DashboardPage() { export function DashboardPage() {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -36,7 +37,6 @@ export function DashboardPage() {
deleteFeed, deleteFeed,
} = useFeedStore(); } = useFeedStore();
// Check if user is authenticated
useEffect(() => { useEffect(() => {
if (!isAuthenticated()) { if (!isAuthenticated()) {
toast.error("Please login to access the dashboard"); toast.error("Please login to access the dashboard");
@@ -44,11 +44,9 @@ export function DashboardPage() {
return; return;
} }
// Fetch feeds on component mount
fetchFeeds(); fetchFeeds();
}, [navigate, fetchFeeds]); }, [navigate, fetchFeeds]);
// Handle time range change
const handleTimeRangeChange = (value: string) => { const handleTimeRangeChange = (value: string) => {
setTimeRangeValue(value); setTimeRangeValue(value);
const selectedOption = getTimeRangeOptions().find(option => option.value === value); const selectedOption = getTimeRangeOptions().find(option => option.value === value);
@@ -58,34 +56,49 @@ export function DashboardPage() {
} }
}; };
// Handle add new feed const [isDialogOpen, setDialogOpen] = useState(false);
const handleAddNewFeed = async () => { const [dialogMode, setDialogMode] = useState<"create" | "edit">("create");
const [editFeedData, setEditFeedData] = useState<{ id: string; name: string; isPublic: boolean } | null>(null);
const handleAddNewFeed = () => {
setDialogMode("create");
setEditFeedData(null);
setDialogOpen(true);
};
const handleDialogSubmit = async (name: string, isPublic: boolean) => {
try { try {
const response = await addNewFeed(); if (dialogMode === "create") {
const response = await addNewFeed(name, isPublic);
if (response.success) { if (response.success) {
toast.success("New feed created successfully"); toast.success("Feed created successfully");
fetchFeeds(); fetchFeeds();
} else { } else {
toast.error("Failed to create new feed"); toast.error(response.message || "Failed to create feed");
} }
} catch (error) { } else if (dialogMode === "edit" && editFeedData) {
toast.error("An error occurred while creating a new feed"); const success = await updateFeed(editFeedData.id, name, isPublic);
console.error(error); if (success) {
toast.success("Feed updated successfully");
fetchFeeds();
} else {
toast.error("Failed to update feed");
fetchFeeds();
}
}
} catch (error: any) {
toast.error(error.message || "An error occurred");
} }
}; };
// Handle refresh
const handleRefresh = async () => { const handleRefresh = async () => {
// First refresh the feeds list
await fetchFeeds(); await fetchFeeds();
// Then refresh current feed data if a feed is selected
if (selectedFeed) { if (selectedFeed) {
await refreshCurrentFeedData(); await refreshCurrentFeedData();
} }
toast.success("Data refreshed"); toast.success("Data refreshed");
}; };
// Handle delete feed
const handleDeleteFeed = async (feedId: string) => { const handleDeleteFeed = async (feedId: string) => {
try { try {
await deleteFeed(feedId); await deleteFeed(feedId);
@@ -106,7 +119,6 @@ export function DashboardPage() {
</p> </p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{/* Button to add a new feed */}
<Button size="sm" onClick={handleAddNewFeed} disabled={isLoading}> <Button size="sm" onClick={handleAddNewFeed} disabled={isLoading}>
Add Feed Add Feed
<Plus className="ml-2 h-4 w-4" /> <Plus className="ml-2 h-4 w-4" />
@@ -155,25 +167,32 @@ export function DashboardPage() {
) : ( ) : (
<div className="space-y-2"> <div className="space-y-2">
{feeds.map((feed) => ( {feeds.map((feed) => (
<div <div key={feed.id} className="flex items-center justify-between border p-2 rounded">
key={feed.id} <div className="cursor-pointer text-sm font-medium flex items-center gap-2" onClick={() => selectFeed(feed)}>
className="flex items-center gap-1" {feed.name}
<span
className={`text-xs px-2 py-0.5 rounded-full font-medium ${feed.isPublic ? 'bg-green-100 text-green-700' : 'bg-yellow-100 text-yellow-700'
}`}
> >
<Button {feed.isPublic ? 'Public' : 'Private'}
variant={selectedFeed?.id === feed.id ? "default" : "outline"} </span>
className="w-full justify-start text-left flex-grow overflow-hidden"
onClick={() => selectFeed(feed)}
>
<div className="flex flex-col items-start w-full overflow-hidden">
<div className="font-medium truncate w-full">
Feed {feed.id.substring(0, 8)}...
</div> </div>
<div className="text-xs text-muted-foreground truncate w-full">
Created: {formatDateTime(feed.createdAt)} <div className="flex gap-1">
</div>
</div>
</Button>
<FeedActions feed={feed} onDelete={handleDeleteFeed} /> <FeedActions feed={feed} onDelete={handleDeleteFeed} />
<Button
size="icon"
variant="ghost"
className="h-8 w-8"
onClick={() => {
setDialogMode("edit");
setEditFeedData({ id: feed.id, name: feed.name, isPublic: feed.isPublic });
setDialogOpen(true);
}}
>
<Edit className="ml-2 h-4 w-4" />
</Button>
</div>
</div> </div>
))} ))}
</div> </div>
@@ -184,16 +203,53 @@ export function DashboardPage() {
<Card className="md:col-span-3 relative"> <Card className="md:col-span-3 relative">
<CardHeader className="flex flex-col md:flex-row md:items-center md:justify-between space-y-2 md:space-y-0"> <CardHeader className="flex flex-col md:flex-row md:items-center md:justify-between space-y-2 md:space-y-0">
<div> <div>
<CardTitle>Feed Data</CardTitle> <CardHeader>
<CardDescription> <CardTitle>
{selectedFeed {selectedFeed ? `Feed Data: ${selectedFeed.name}` : "Feed Data"}
? `Showing data for Feed ${selectedFeed.id.substring(0, 8)}...` </CardTitle>
: "Select a feed to view data" <CardDescription className="flex flex-col">
} {selectedFeed ? (
<>
<span className="text-sm text-muted-foreground flex items-center gap-2">
Feed ID:
<code className="bg-muted px-1 py-0.5 rounded text-xs">{selectedFeed.id}</code>
<Button
size="icon"
variant="ghost"
className="h-5 w-5 p-1"
onClick={() => {
navigator.clipboard.writeText(selectedFeed.id);
toast.success("Feed ID copied to clipboard");
}}
title="Copy Feed ID"
>
<Copy className="h-4 w-4" />
</Button>
{selectedFeed.isPublic && (
<Button
size="sm"
variant="outline"
className="text-xs ml-2"
onClick={() => {
const shareUrl = `${window.location.origin}/publicfeed/${selectedFeed.id}`;
navigator.clipboard.writeText(shareUrl);
toast.success("Public share link copied");
}}
>
Share
</Button>
)}
</span>
</>
) : (
"Select a feed to view data"
)}
</CardDescription> </CardDescription>
</CardHeader>
</div> </div>
{/* Flex container for the select box and button */}
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Select <Select
value={timeRangeValue} value={timeRangeValue}
@@ -212,7 +268,6 @@ export function DashboardPage() {
</SelectContent> </SelectContent>
</Select> </Select>
{/* Button to refresh feed data */}
<button <button
className={`flex items-center p-1 border rounded-md className={`flex items-center p-1 border rounded-md
${isLoading || !selectedFeed ${isLoading || !selectedFeed
@@ -251,6 +306,14 @@ export function DashboardPage() {
)} )}
</CardContent> </CardContent>
</Card> </Card>
<FeedDialog
open={isDialogOpen}
onClose={() => setDialogOpen(false)}
onSubmit={handleDialogSubmit}
initialName={editFeedData?.name}
initialIsPublic={editFeedData?.isPublic}
mode={dialogMode}
/>
</div> </div>
</div> </div>
); );

View File

@@ -0,0 +1,116 @@
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { getPublicFeedDataTimeRange } from "@/lib/api";
import { formatDateTime, getTimeRangeOptions } from "@/lib/timeRanges";
import { FeedChart } from "@/components/FeedChart";
import { Card, CardHeader, CardTitle, CardContent, CardDescription } from "@/components/ui/card";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Skeleton } from "@/components/ui/skeleton";
import { ScanBarcode } from "lucide-react";
import { toast } from "sonner";
export function PublicFeedPage() {
const { feedId } = useParams<{ feedId: string }>();
const [timeRangeValue, setTimeRangeValue] = useState("30min");
const [feedName, setFeedName] = useState<string>("");
const [data, setData] = useState<{ timestamp: string; data: string }[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [notFound, setNotFound] = useState(false);
const fetchData = async (rangeValue: string) => {
try {
setIsLoading(true);
const option = getTimeRangeOptions().find((o) => o.value === rangeValue);
if (!option || !feedId) return;
const { startTime, endTime } = option.getTimeRange();
const response = await getPublicFeedDataTimeRange(feedId, startTime, endTime);
setFeedName(response.feedName);
setData(response.data);
setNotFound(false);
} catch (error: any) {
if (error.message.includes("private")) {
setNotFound(true);
} else {
toast.error(error.message || "Error loading public feed");
}
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchData(timeRangeValue);
}, [feedId]);
const handleTimeRangeChange = (value: string) => {
setTimeRangeValue(value);
fetchData(value);
};
return (
<div className="container mx-auto py-10 max-w-4xl">
<Card>
<CardHeader className="flex flex-col md:flex-row md:items-center md:justify-between">
<div>
<CardTitle>
{notFound
? "Feed Not Available"
: feedName
? `Public Feed: ${feedName}`
: "Loading Feed..."}
</CardTitle>
{!notFound && (
<CardDescription>Visualized public data (read-only)</CardDescription>
)}
</div>
{!notFound && (
<Select value={timeRangeValue} onValueChange={handleTimeRangeChange}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select time range" />
</SelectTrigger>
<SelectContent>
{getTimeRangeOptions().map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
)}
</CardHeader>
<CardContent className="pt-6">
{isLoading ? (
<Skeleton className="h-[350px] w-full" />
) : notFound ? (
<div className="flex flex-col items-center justify-center h-[350px] text-center">
<ScanBarcode className="mb-2 h-10 w-10 text-muted-foreground" />
<h3 className="text-lg font-medium">Private or Invalid Feed</h3>
<p className="text-sm text-muted-foreground">
This feed cannot be accessed publicly.
</p>
</div>
) : data.length === 0 ? (
<div className="flex flex-col items-center justify-center h-[350px] text-center">
<ScanBarcode className="mb-2 h-10 w-10 text-muted-foreground" />
<h3 className="text-lg font-medium">No data available</h3>
<p className="text-sm text-muted-foreground">
No public data found for the selected time range.
</p>
</div>
) : (
<FeedChart data={data} />
)}
</CardContent>
</Card>
</div>
);
}

View File

@@ -1 +1 @@
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/ApiKeyItem.tsx","./src/components/FeedActions.tsx","./src/components/FeedChart.tsx","./src/components/Header.tsx","./src/components/Layout.tsx","./src/components/ProtectedRoute.tsx","./src/components/ThemeToggle.tsx","./src/components/ui/alert.tsx","./src/components/ui/avatar.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/date-time-picker.tsx","./src/components/ui/dialog.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/form.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/popover.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/tabs.tsx","./src/components/ui/toast.tsx","./src/components/ui/toaster.tsx","./src/hooks/use-toast.ts","./src/lib/api.ts","./src/lib/auth.ts","./src/lib/store.ts","./src/lib/theme-provider.tsx","./src/lib/timeRanges.ts","./src/lib/types.ts","./src/lib/utils.ts","./src/pages/ApiKeysPage.tsx","./src/pages/DashboardPage.tsx","./src/pages/HomePage.tsx","./src/pages/LoginPage.tsx","./src/pages/ProfilePage.tsx","./src/pages/RegisterPage.tsx","./src/pages/VerifyEmail.tsx","./vite.config.ts"],"version":"5.6.3"} {"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/ApiKeyItem.tsx","./src/components/FeedActions.tsx","./src/components/FeedChart.tsx","./src/components/FeedDialog.tsx","./src/components/Header.tsx","./src/components/Layout.tsx","./src/components/ProtectedRoute.tsx","./src/components/ThemeToggle.tsx","./src/components/ui/alert.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/date-time-picker.tsx","./src/components/ui/dialog.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/form.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/popover.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/tabs.tsx","./src/components/ui/toast.tsx","./src/components/ui/toaster.tsx","./src/hooks/use-toast.ts","./src/lib/api.ts","./src/lib/auth.ts","./src/lib/store.ts","./src/lib/theme-provider.tsx","./src/lib/timeRanges.ts","./src/lib/types.ts","./src/lib/utils.ts","./src/pages/ApiKeysPage.tsx","./src/pages/DashboardPage.tsx","./src/pages/HomePage.tsx","./src/pages/LoginPage.tsx","./src/pages/ProfilePage.tsx","./src/pages/PublicFeedPage.tsx","./src/pages/RegisterPage.tsx","./src/pages/VerifyEmail.tsx","./vite.config.ts"],"version":"5.6.3"}