Add public feeds page

This commit is contained in:
2025-05-07 23:44:49 +03:30
parent d60607a424
commit db32cbfdf2
7 changed files with 655 additions and 518 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 { ProfilePage } from "@/pages/ProfilePage";
import { VerifyEmail } from "@/pages/VerifyEmail";
import { PublicFeedPage } from "@/pages/PublicFeedPage";
function App() {
return (
@@ -52,7 +55,9 @@ function App() {
}
/>
<Route path="*" element={<Navigate to="/" replace />} />
<Route path="/publicfeed/:feedId" element={<PublicFeedPage />} />
</Route>
</Routes>
</Router>
</ThemeProvider>

View File

@@ -164,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
export const createApiKey = async () => {
try {

View File

@@ -35,6 +35,8 @@ export interface ApiKey {
export interface Feed {
id: string;
createdAt: string;
name: string;
isPublic: boolean;
}
export interface AddFeedRequest {

View File

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

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"}