mirror of
https://github.com/freedbygrace/DynamoDNS.git
synced 2026-08-12 03:06:53 +00:00
Implement initial application structure and UI.
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9111ef36-26c8-4085-84ca-a35dc1fec1b5 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7083d608-d6d3-4a6a-9a27-6286c5109627/54918520-d42c-44cd-b4f4-9e5a42eb5be1.jpg
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useOrganization } from "@/context/organization-context";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
export function OrganizationSelector() {
|
||||
const { organizations, currentOrganization, setCurrentOrganization, isLoading } = useOrganization();
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="w-full flex items-center justify-between px-3 py-2 text-sm font-medium rounded-md bg-secondary text-secondary-foreground opacity-70 animate-pulse">
|
||||
<div className="flex items-center">
|
||||
<span className="w-2 h-2 bg-muted rounded-full mr-2"></span>
|
||||
<span>Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentOrganization || organizations.length === 0) {
|
||||
return (
|
||||
<div className="w-full flex items-center justify-between px-3 py-2 text-sm font-medium rounded-md bg-secondary text-secondary-foreground">
|
||||
<div className="flex items-center">
|
||||
<span className="w-2 h-2 bg-warning rounded-full mr-2"></span>
|
||||
<span>No Organization</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="w-full flex items-center justify-between px-3 py-2 text-sm font-medium rounded-md hover:bg-accent">
|
||||
<div className="flex items-center">
|
||||
<span className="w-2 h-2 bg-success rounded-full mr-2"></span>
|
||||
<span>{currentOrganization.name}</span>
|
||||
</div>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-64">
|
||||
{organizations.map((org) => (
|
||||
<DropdownMenuItem
|
||||
key={org.id}
|
||||
onClick={() => setCurrentOrganization(org)}
|
||||
className={org.id === currentOrganization.id ? "bg-muted" : ""}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className={`w-2 h-2 rounded-full mr-2 ${org.isActive ? "bg-success" : "bg-warning"}`}></span>
|
||||
<span>{org.name}</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
|
||||
interface PaginationProps {
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
onPageChange: (page: number) => void;
|
||||
itemLabel?: string;
|
||||
totalItems?: number;
|
||||
itemsPerPage?: number;
|
||||
}
|
||||
|
||||
export function Pagination({
|
||||
currentPage,
|
||||
totalPages,
|
||||
onPageChange,
|
||||
itemLabel = "items",
|
||||
totalItems,
|
||||
itemsPerPage = 10,
|
||||
}: PaginationProps) {
|
||||
// Generate page numbers to display
|
||||
const getPageNumbers = () => {
|
||||
const pages = [];
|
||||
|
||||
// Always include first page
|
||||
pages.push(1);
|
||||
|
||||
// Add ellipsis if needed
|
||||
if (currentPage > 3) {
|
||||
pages.push("ellipsis1");
|
||||
}
|
||||
|
||||
// Add pages around current page
|
||||
for (let i = Math.max(2, currentPage - 1); i <= Math.min(totalPages - 1, currentPage + 1); i++) {
|
||||
if (i > 1 && i < totalPages) {
|
||||
pages.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Add ellipsis if needed
|
||||
if (currentPage < totalPages - 2) {
|
||||
pages.push("ellipsis2");
|
||||
}
|
||||
|
||||
// Always include last page if more than 1 page
|
||||
if (totalPages > 1) {
|
||||
pages.push(totalPages);
|
||||
}
|
||||
|
||||
return pages;
|
||||
};
|
||||
|
||||
const pageNumbers = getPageNumbers();
|
||||
|
||||
const startItem = (currentPage - 1) * itemsPerPage + 1;
|
||||
const endItem = Math.min(startItem + itemsPerPage - 1, totalItems || 0);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
{totalItems !== undefined && (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Showing {startItem}-{endItem} of {totalItems} {itemLabel}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onPageChange(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4 mr-1" />
|
||||
Previous
|
||||
</Button>
|
||||
|
||||
{pageNumbers.map((page, index) => {
|
||||
if (page === "ellipsis1" || page === "ellipsis2") {
|
||||
return (
|
||||
<Button key={page} variant="outline" size="sm" disabled>
|
||||
...
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={page}
|
||||
variant={currentPage === page ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => onPageChange(page as number)}
|
||||
>
|
||||
{page}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onPageChange(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
Next
|
||||
<ChevronRight className="h-4 w-4 ml-1" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTheme } from "@/hooks/use-theme";
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
const toggleTheme = () => {
|
||||
setTheme(theme === "dark" ? "light" : "dark");
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={toggleTheme}
|
||||
title={theme === "dark" ? "Switch to light mode" : "Switch to dark mode"}
|
||||
>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user