Add UI stuff for editing feed

This commit is contained in:
2025-05-07 23:05:49 +03:30
parent 8e36304b5a
commit d60607a424
5 changed files with 219 additions and 51 deletions

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

@@ -91,11 +91,11 @@ export const addNewFeed = async (name: string, isPublic: boolean) => {
try {
const token = getToken();
if (!token) {
throw new Error('Authentication required');
throw new Error("Authentication required");
}
const response = await userDataApi.post(
'/api/addnewfeed',
"/api/addnewfeed",
{
Name: name,
IsPublic: isPublic,
@@ -108,10 +108,37 @@ export const addNewFeed = async (name: string, isPublic: boolean) => {
);
return response.data;
} 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) => {
try {

View File

@@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom";
import { toast } from "sonner";
import { isAuthenticated } from "@/lib/auth";
import { useFeedStore } from "@/lib/store";
import { addNewFeed } from "@/lib/api";
import { addNewFeed, updateFeed } from "@/lib/api";
import { formatDateTime, getTimeRangeOptions } from "@/lib/timeRanges";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
@@ -15,10 +15,11 @@ import {
SelectValue,
} from "@/components/ui/select";
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 { FeedActions } from "@/components/FeedActions";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { FeedDialog } from "@/components/FeedDialog";
export function DashboardPage() {
const navigate = useNavigate();
@@ -36,7 +37,6 @@ export function DashboardPage() {
deleteFeed,
} = useFeedStore();
// Check if user is authenticated
useEffect(() => {
if (!isAuthenticated()) {
toast.error("Please login to access the dashboard");
@@ -44,11 +44,9 @@ export function DashboardPage() {
return;
}
// Fetch feeds on component mount
fetchFeeds();
}, [navigate, fetchFeeds]);
// Handle time range change
const handleTimeRangeChange = (value: string) => {
setTimeRangeValue(value);
const selectedOption = getTimeRangeOptions().find(option => option.value === value);
@@ -58,36 +56,49 @@ export function DashboardPage() {
}
};
// Handle add new feed
const handleAddNewFeed = async () => {
try {
const defaultName = `Untitled Feed ${new Date().toLocaleString()}`;
const response = await addNewFeed(defaultName);
const [isDialogOpen, setDialogOpen] = useState(false);
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 {
if (dialogMode === "create") {
const response = await addNewFeed(name, isPublic);
if (response.success) {
toast.success("New feed created successfully");
toast.success("Feed created successfully");
fetchFeeds();
} else {
toast.error("Failed to create new feed");
toast.error(response.message || "Failed to create feed");
}
} catch (error) {
toast.error("An error occurred while creating a new feed");
console.error(error);
} else if (dialogMode === "edit" && editFeedData) {
const success = await updateFeed(editFeedData.id, name, isPublic);
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 () => {
// First refresh the feeds list
await fetchFeeds();
// Then refresh current feed data if a feed is selected
if (selectedFeed) {
await refreshCurrentFeedData();
}
toast.success("Data refreshed");
};
// Handle delete feed
const handleDeleteFeed = async (feedId: string) => {
try {
await deleteFeed(feedId);
@@ -108,7 +119,6 @@ export function DashboardPage() {
</p>
</div>
<div className="flex items-center gap-2">
{/* Button to add a new feed */}
<Button size="sm" onClick={handleAddNewFeed} disabled={isLoading}>
Add Feed
<Plus className="ml-2 h-4 w-4" />
@@ -157,26 +167,32 @@ export function DashboardPage() {
) : (
<div className="space-y-2">
{feeds.map((feed) => (
<div
key={feed.id}
className="flex items-center gap-1"
<div key={feed.id} className="flex items-center justify-between border p-2 rounded">
<div className="cursor-pointer text-sm font-medium flex items-center gap-2" onClick={() => selectFeed(feed)}>
{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
variant={selectedFeed?.id === feed.id ? "default" : "outline"}
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.name || `Feed ${feed.id.substring(0, 8)}...`}
{feed.isPublic ? 'Public' : 'Private'}
</span>
</div>
<div className="text-xs text-muted-foreground truncate w-full">
Created: {formatDateTime(feed.createdAt)}
</div>
</div>
</Button>
<div className="flex gap-1">
<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>
@@ -206,9 +222,7 @@ export function DashboardPage() {
toast.success("Feed ID copied to clipboard");
}}
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16h8M8 12h8m-6 8h6a2 2 0 002-2v-5m0-4V6a2 2 0 00-2-2h-6l-2 2H6a2 2 0 00-2 2v12a2 2 0 002 2h2" />
</svg>
<Copy className="h-4 w-4" />
</Button>
</span>
</>
@@ -219,7 +233,6 @@ export function DashboardPage() {
</CardHeader>
</div>
{/* Flex container for the select box and button */}
<div className="flex items-center space-x-2">
<Select
value={timeRangeValue}
@@ -238,7 +251,6 @@ export function DashboardPage() {
</SelectContent>
</Select>
{/* Button to refresh feed data */}
<button
className={`flex items-center p-1 border rounded-md
${isLoading || !selectedFeed
@@ -277,6 +289,14 @@ export function DashboardPage() {
)}
</CardContent>
</Card>
<FeedDialog
open={isDialogOpen}
onClose={() => setDialogOpen(false)}
onSubmit={handleDialogSubmit}
initialName={editFeedData?.name}
initialIsPublic={editFeedData?.isPublic}
mode={dialogMode}
/>
</div>
</div>
);