diff --git a/bun.lockb b/bun.lockb index 569a8da..cdb349e 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 3a72679..49d2522 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-avatar": "^1.1.0", "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-slot": "^1.1.0", diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx new file mode 100644 index 0000000..41fa7e0 --- /dev/null +++ b/src/components/ui/alert.tsx @@ -0,0 +1,59 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = "Alert" + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertTitle.displayName = "AlertTitle" + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = "AlertDescription" + +export { Alert, AlertTitle, AlertDescription } diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..769ff7a --- /dev/null +++ b/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,198 @@ +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { Check, ChevronRight, Circle } from "lucide-react" + +import { cn } from "@/lib/utils" + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} diff --git a/src/components/widgets/Client.tsx b/src/components/widgets/Client.tsx index 2d1a877..d5f3b7d 100644 --- a/src/components/widgets/Client.tsx +++ b/src/components/widgets/Client.tsx @@ -11,7 +11,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover import { Input } from "@/components/ui/input.tsx"; import { SearchIcon } from "lucide-react"; import timeRangesData from '@/lib/time.json'; -import {cn} from "@/lib/utils.ts"; +import { cn } from "@/lib/utils.ts"; interface Schedule { dosen: string; @@ -149,7 +149,12 @@ const Client = () => {

{result.hari}

- Jam{result.jam} + + Jam + + {result.jam} + +

{timeRange}

@@ -193,41 +198,43 @@ const Client = () => { ); return ( -
- - - - Smrv2 - - Sebuah website untuk mencari jadwal kuliah, info ruangan, dll berdasarkan simeru. - - - - -
-
- - -
-
- - +
+
+ + + + Smrv2 + + Sebuah website untuk mencari jadwal kuliah, info ruangan, dll berdasarkan simeru. + + + + +
+
+ + +
+
+ + +
+
- -
- - + + +
{(state.isLoading || state.results || state.errorMessage) && ( -
+
-

Search Results

+

Search Results

{state.errorMessage ? ( <> ) : ( @@ -266,7 +273,7 @@ const Client = () => { )}
)} -
+ ); }; diff --git a/src/components/widgets/HealthCheck.tsx b/src/components/widgets/HealthCheck.tsx new file mode 100644 index 0000000..ba75a0c --- /dev/null +++ b/src/components/widgets/HealthCheck.tsx @@ -0,0 +1,69 @@ +import React, { useState, useEffect } from 'react'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { Activity, CheckCircle2, XCircle } from 'lucide-react'; +import { fetchData } from "@/lib/fetch.ts"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover.tsx"; +import {Button} from "@/components/ui/button.tsx"; + +interface HealthStatus { + livez: boolean; + readyz: boolean; +} + +const HealthCheck: React.FC = () => { + const [health, setHealth] = useState({ livez: false, readyz: false }); + + const checkEndpoint = async (endpoint: string): Promise => { + try { + const response = await fetch(`${import.meta.env.PUBLIC_API}${endpoint}`); + return response.status === 200; + } catch (error) { + console.error(`Health check failed for ${endpoint}:`, error); + return false; + } + }; + + const checkHealth = async () => { + const livezStatus = await checkEndpoint('/livez'); + const readyzStatus = await checkEndpoint('/readyz'); + + setHealth({ + livez: livezStatus, + readyz: readyzStatus + }); + }; + + useEffect(() => { + checkHealth(); + const interval = setInterval(checkHealth, 60*1000); + return () => clearInterval(interval); + }, []); + + return ( + + + + + +
+
+ {health.livez ? : } + Server Status +
+
+ {health.readyz ? : } + Apps Status +
+
+
+
+ ); +}; + +export default HealthCheck; diff --git a/src/components/widgets/ModeToggle.tsx b/src/components/widgets/ModeToggle.tsx new file mode 100644 index 0000000..d760a04 --- /dev/null +++ b/src/components/widgets/ModeToggle.tsx @@ -0,0 +1,52 @@ +import * as React from "react" +import { Moon, Sun } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" + +export function ModeToggle() { + const [theme, setThemeState] = React.useState< + "theme-light" | "dark" | "system" + >("theme-light") + + React.useEffect(() => { + const isDarkMode = document.documentElement.classList.contains("dark") + setThemeState(isDarkMode ? "dark" : "theme-light") + }, []) + + React.useEffect(() => { + const isDark = + theme === "dark" || + (theme === "system" && + window.matchMedia("(prefers-color-scheme: dark)").matches) + document.documentElement.classList[isDark ? "add" : "remove"]("dark") + }, [theme]) + + return ( + + + + + + setThemeState("theme-light")}> + Light + + setThemeState("dark")}> + Dark + + setThemeState("system")}> + System + + + + ) +} diff --git a/src/pages/index.astro b/src/pages/index.astro index 7cbd11d..8072d23 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -5,6 +5,9 @@ import Footer from '@/components/widgets/Footer.astro'; import ScrollUp from '@/components/widgets/ScrollUp'; import { Toaster } from "@/components/ui/toaster" import Client from "@/components/widgets/Client"; +import HealthCheck from "../components/widgets/HealthCheck"; +import {ModeToggle} from "../components/widgets/ModeToggle"; +import React from "react"; --- +
+ + +