Add public feeds page
This commit is contained in:
1029
pnpm-lock.yaml
generated
1029
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
|
@@ -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 {
|
||||
|
@@ -35,6 +35,8 @@ export interface ApiKey {
|
||||
export interface Feed {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
name: string;
|
||||
isPublic: boolean;
|
||||
}
|
||||
|
||||
export interface AddFeedRequest {
|
||||
|
@@ -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>
|
||||
);
|
||||
|
116
src/pages/PublicFeedPage.tsx
Normal file
116
src/pages/PublicFeedPage.tsx
Normal 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>
|
||||
);
|
||||
}
|
@@ -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"}
|
Reference in New Issue
Block a user