From e87ff98beb256e0cdf280a378a9adccc6767b486 Mon Sep 17 00:00:00 2001 From: Winston Hsiao <96440583+Winston-Hsiao@users.noreply.github.com> Date: Tue, 10 Sep 2024 22:37:51 -0400 Subject: [PATCH] Downloads page and other clean up (#378) --- frontend/package-lock.json | 2 +- frontend/package.json | 1 + frontend/src/App.tsx | 66 ++++---- frontend/src/components/Container.tsx | 2 +- frontend/src/components/ScrollToTop.tsx | 12 ++ .../src/components/landing/BuySection.tsx | 15 +- .../src/components/landing/HeroASCIIArt.tsx | 2 +- .../src/components/landing/NavSection.tsx | 27 ++-- frontend/src/components/nav/Navbar.tsx | 85 ++++------ frontend/src/components/pages/BuyPage.tsx | 2 +- frontend/src/components/pages/Download.tsx | 145 ++++++++++++++++++ frontend/src/components/pages/Home.tsx | 17 +- frontend/src/components/pages/Studio.tsx | 66 -------- frontend/src/components/ui/BentoGrid.tsx | 83 ---------- frontend/src/components/ui/badge.tsx | 36 +++++ frontend/src/components/ui/button.tsx | 57 +++++++ frontend/src/components/ui/input.tsx | 24 +++ frontend/src/components/ui/tabs.tsx | 53 +++++++ frontend/src/hooks/useScrollToTop.tsx | 10 ++ 19 files changed, 451 insertions(+), 254 deletions(-) create mode 100644 frontend/src/components/ScrollToTop.tsx create mode 100644 frontend/src/components/pages/Download.tsx delete mode 100644 frontend/src/components/pages/Studio.tsx delete mode 100644 frontend/src/components/ui/BentoGrid.tsx create mode 100644 frontend/src/components/ui/badge.tsx create mode 100644 frontend/src/components/ui/button.tsx create mode 100644 frontend/src/components/ui/input.tsx create mode 100644 frontend/src/components/ui/tabs.tsx create mode 100644 frontend/src/hooks/useScrollToTop.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 21eeef17..923afa1f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,6 +12,7 @@ "@hookform/resolvers": "^3.9.0", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-tabs": "^1.1.0", "@react-oauth/google": "^0.12.1", "@react-three/drei": "^9.109.2", "@react-three/fiber": "^8.16.8", @@ -2145,7 +2146,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.0" }, diff --git a/frontend/package.json b/frontend/package.json index e7f9958d..673ca416 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,6 +27,7 @@ "@hookform/resolvers": "^3.9.0", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-tabs": "^1.1.0", "@react-oauth/google": "^0.12.1", "@react-three/drei": "^9.109.2", "@react-three/fiber": "^8.16.8", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index dac798b3..832a1f0c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -10,6 +10,7 @@ import "@/App.css"; import Container from "@/components/Container"; import NotFoundRedirect from "@/components/NotFoundRedirect"; +import { ScrollToTop } from "@/components/ScrollToTop"; import Footer from "@/components/footer/Footer"; import Navbar from "@/components/nav/Navbar"; import APIKeys from "@/components/pages/APIKeys"; @@ -26,11 +27,12 @@ import Logout from "@/components/pages/Logout"; import NotFound from "@/components/pages/NotFound"; import Profile from "@/components/pages/Profile"; import Signup from "@/components/pages/Signup"; -import Studio from "@/components/pages/Studio"; import { AlertQueue, AlertQueueProvider } from "@/hooks/useAlertQueue"; import { AuthenticationProvider } from "@/hooks/useAuth"; import { DarkModeProvider } from "@/hooks/useDarkMode"; +import DownloadsPage from "./components/pages/Download"; + const PendoInitializer = () => { const location = useLocation(); // Hook to get current route @@ -61,42 +63,44 @@ const App = () => { -
- - {" "} - {/* This component is where Pendo is initialized */} -
- - - } /> + +
+ + {" "} + {/* This component is where Pendo is initialized */} +
+ + + } /> - } /> - } /> + } /> + } /> + } /> - } /> - } /> - } - /> + } /> + } + /> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> - } /> - } /> - } /> + } /> + } /> + } /> - } /> - } /> - } /> - - + } /> + } /> + } /> + + +
+
-
-
+ diff --git a/frontend/src/components/Container.tsx b/frontend/src/components/Container.tsx index 73dd3611..e97ef06c 100644 --- a/frontend/src/components/Container.tsx +++ b/frontend/src/components/Container.tsx @@ -11,7 +11,7 @@ const Container = (props: ContainerProps) => { // Landing page / home path. if (pathname === "/") { - return
{children}
; + return
{children}
; } return
{children}
; diff --git a/frontend/src/components/ScrollToTop.tsx b/frontend/src/components/ScrollToTop.tsx new file mode 100644 index 00000000..ebf8b7e6 --- /dev/null +++ b/frontend/src/components/ScrollToTop.tsx @@ -0,0 +1,12 @@ +import { ReactNode } from "react"; + +import { useScrollToTop } from "@/hooks/useScrollToTop"; + +interface ScrollToTopProps { + children: ReactNode; +} + +export function ScrollToTop({ children }: ScrollToTopProps) { + useScrollToTop(); + return <>{children}; +} diff --git a/frontend/src/components/landing/BuySection.tsx b/frontend/src/components/landing/BuySection.tsx index eacde8b8..d28a4774 100644 --- a/frontend/src/components/landing/BuySection.tsx +++ b/frontend/src/components/landing/BuySection.tsx @@ -1,7 +1,11 @@ +import { useNavigate } from "react-router-dom"; + import { Button } from "@/components/ui/Buttons/Button"; import KScale_Garage from "@/images/KScale_Garage.jpeg"; export default function BuySection() { + const navigate = useNavigate(); + return (
@@ -56,10 +60,17 @@ export default function BuySection() {
- -
diff --git a/frontend/src/components/landing/HeroASCIIArt.tsx b/frontend/src/components/landing/HeroASCIIArt.tsx index 51459834..d957c632 100644 --- a/frontend/src/components/landing/HeroASCIIArt.tsx +++ b/frontend/src/components/landing/HeroASCIIArt.tsx @@ -153,7 +153,7 @@ const HeroASCIIArt = () => {
); diff --git a/frontend/src/components/landing/NavSection.tsx b/frontend/src/components/landing/NavSection.tsx index 27a83213..dc3398b9 100644 --- a/frontend/src/components/landing/NavSection.tsx +++ b/frontend/src/components/landing/NavSection.tsx @@ -8,6 +8,7 @@ import { CardTitle, } from "@/components/ui/Card"; import { + ChevronRightIcon, CodeIcon, DownloadIcon, LayersIcon, @@ -20,9 +21,15 @@ export default function NavSection() { return (
-

- Explore More -

+
+

+ The K-Scale Ecosystem +

+

+ We're open source and always iterating. Join us in building the + future of robotics. +

+
{[ { @@ -33,17 +40,17 @@ export default function NavSection() { buttonText: "Explore K-Lang", }, { - title: "Kernel Images", + title: "Downloads", description: - "View and download official K-Scale and community uploaded kernel images", + "View and download official K-Scale and community uploaded kernel images, URDFs, ML models, and more", icon: DownloadIcon, - path: "/kernel-images", - buttonText: "Browse Images", + path: "/downloads", + buttonText: "Browse Downloads", }, { title: "Browse Builds", description: - "Browse completed/published robot builds which include CAD files, part lists, and various related downloads", + "Browse robot builds with linked CAD files, part lists, and various related downloads", icon: MagnifyingGlassIcon, path: "/browse", buttonText: "View Builds", @@ -58,7 +65,7 @@ export default function NavSection() { ].map((item, index) => ( navigate(item.path)} > @@ -69,7 +76,7 @@ export default function NavSection() {
{item.buttonText} - +
diff --git a/frontend/src/components/nav/Navbar.tsx b/frontend/src/components/nav/Navbar.tsx index 2f4ce2d2..0dd558f3 100644 --- a/frontend/src/components/nav/Navbar.tsx +++ b/frontend/src/components/nav/Navbar.tsx @@ -1,25 +1,38 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { isMobile } from "react-device-detect"; -import { - FaMoon, - FaSignInAlt, - FaSun, - FaUserCircle, - FaUserPlus, -} from "react-icons/fa"; -import { useNavigate } from "react-router-dom"; +import { FaMoon, FaSun, FaUserCircle } from "react-icons/fa"; +import { useLocation, useNavigate } from "react-router-dom"; import Sidebar from "@/components/nav/Sidebar"; -import { useAuthentication } from "@/hooks/useAuth"; import { useDarkMode } from "@/hooks/useDarkMode"; const ICON_SIZE = isMobile ? 16 : 20; const Navbar = () => { const [showSidebar, setShowSidebar] = useState(false); + const [showNavbar, setShowNavbar] = useState(true); const { darkMode, setDarkMode } = useDarkMode(); - const { isAuthenticated } = useAuthentication(); const navigate = useNavigate(); + const location = useLocation(); + + useEffect(() => { + const handleScroll = () => { + if (location.pathname === "/") { + setShowNavbar(window.scrollY > 100); + } + }; + + if (location.pathname === "/") { + setShowNavbar(false); + window.addEventListener("scroll", handleScroll); + } else { + setShowNavbar(true); + } + + return () => window.removeEventListener("scroll", handleScroll); + }, [location.pathname]); + + if (!showNavbar) return null; return ( <> @@ -41,52 +54,10 @@ const Navbar = () => { )} - {isAuthenticated ? ( - - ) : ( -
- - -
- )} + +
diff --git a/frontend/src/components/pages/BuyPage.tsx b/frontend/src/components/pages/BuyPage.tsx index 388bbcee..232ec442 100644 --- a/frontend/src/components/pages/BuyPage.tsx +++ b/frontend/src/components/pages/BuyPage.tsx @@ -3,7 +3,7 @@ const BuyPage = () => {

- Buy Stompy (In Progress) + Buy Stompy Pro (In Progress)

This page is currently under construction. Stompy, our robot, is also diff --git a/frontend/src/components/pages/Download.tsx b/frontend/src/components/pages/Download.tsx new file mode 100644 index 00000000..e1be6362 --- /dev/null +++ b/frontend/src/components/pages/Download.tsx @@ -0,0 +1,145 @@ +import { useState } from "react"; + +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/Card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Download, Search, Upload } from "lucide-react"; + +// Mock data for demonstration +const resources = [ + { + id: 1, + name: "K-Scale Core Kernel", + type: "kernel", + official: true, + downloads: 1200, + }, + { + id: 2, + name: "Robotic Arm URDF", + type: "urdf", + official: true, + downloads: 850, + }, + { + id: 3, + name: "Object Detection Model", + type: "ml", + official: true, + downloads: 2000, + }, + { + id: 4, + name: "Custom Kernel by user123", + type: "kernel", + official: false, + downloads: 300, + }, + { id: 5, name: "Drone URDF", type: "urdf", official: false, downloads: 450 }, + { + id: 6, + name: "Sentiment Analysis Model", + type: "ml", + official: false, + downloads: 600, + }, + { + id: 7, + name: "K-Scale Documentation", + type: "other", + official: true, + downloads: 3000, + }, + { + id: 8, + name: "Community Guidelines", + type: "other", + official: true, + downloads: 500, + }, +]; + +export default function DownloadsPage() { + const [searchTerm, setSearchTerm] = useState(""); + const [activeTab, setActiveTab] = useState("all"); + + const filteredResources = resources.filter( + (resource) => + resource.name.toLowerCase().includes(searchTerm.toLowerCase()) && + (activeTab === "all" || resource.type === activeTab), + ); + + return ( +

+

K-Scale Downloads

+

+ View and download official K-Scale and community uploaded kernel images, + URDFs, ML models, and more +

+ +
+
+ + setSearchTerm(e.target.value)} + className="pl-8" + /> +
+ +
+ + + + All + Kernel Images + URDFs + ML Models + Other + + + +
+ {filteredResources.map((resource) => ( + + + + {resource.name} + {resource.official && ( + Official + )} + + + +

+ Downloads: {resource.downloads} +

+
+ + + +
+ ))} +
+ + {filteredResources.length === 0 && ( +

+ No resources found matching your search criteria. +

+ )} +
+ ); +} diff --git a/frontend/src/components/pages/Home.tsx b/frontend/src/components/pages/Home.tsx index 00dc965c..95673ae7 100644 --- a/frontend/src/components/pages/Home.tsx +++ b/frontend/src/components/pages/Home.tsx @@ -1,11 +1,26 @@ +import { useEffect, useState } from "react"; + import BuySection from "@/components/landing/BuySection"; import HeroASCIIArt from "@/components/landing/HeroASCIIArt"; import KLangDemo from "@/components/landing/KLangDemo"; import NavSection from "@/components/landing/NavSection"; const Home = () => { + const [scrolled, setScrolled] = useState(false); + + useEffect(() => { + const handleScroll = () => { + setScrolled(window.scrollY > 100); + }; + + window.addEventListener("scroll", handleScroll); + return () => window.removeEventListener("scroll", handleScroll); + }, []); + return ( -
+
diff --git a/frontend/src/components/pages/Studio.tsx b/frontend/src/components/pages/Studio.tsx deleted file mode 100644 index f69569ac..00000000 --- a/frontend/src/components/pages/Studio.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { - CodeIcon, - DownloadIcon, - MagnifyingGlassIcon, - RocketIcon, -} from "@radix-ui/react-icons"; - -import { BentoCard, BentoGrid } from "../ui/BentoGrid"; - -const Studio = () => { - return ( -
-

- Everything for your project -

- - - } - Icon={RocketIcon} - description="Begin your K-Scale Dev journey" - href="/get-started" - cta="Start now" - /> - - } - Icon={DownloadIcon} - description="Access official and community resources" - href="/downloads" - cta="Get files" - /> - - } - Icon={MagnifyingGlassIcon} - description="Explore published robot builds" - href="/browse" - cta="Discover" - /> - - } - Icon={CodeIcon} - description="Write and run K-Lang programs (Coming Soon)" - href="/k-lang" - cta="Learn more" - /> - -
- ); -}; - -export default Studio; diff --git a/frontend/src/components/ui/BentoGrid.tsx b/frontend/src/components/ui/BentoGrid.tsx deleted file mode 100644 index ea455739..00000000 --- a/frontend/src/components/ui/BentoGrid.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { ElementType, ReactNode } from "react"; - -import { cn } from "@/lib/utils"; -import { ArrowRightIcon } from "@radix-ui/react-icons"; - -import { Button } from "./Buttons/Button"; - -const BentoGrid = ({ - children, - className, -}: { - children: ReactNode; - className?: string; -}) => { - return ( -
- {children} -
- ); -}; - -const BentoCard = ({ - name, - className, - background, - Icon, - description, - href, - cta, -}: { - name: string; - className: string; - background: ReactNode; - Icon: ElementType; - description: string; - href: string; - cta: string; -}) => ( -
-
{background}
-
- -

{name}

-

{description}

-
- - -
-
-); - -export { BentoCard, BentoGrid }; diff --git a/frontend/src/components/ui/badge.tsx b/frontend/src/components/ui/badge.tsx new file mode 100644 index 00000000..5d0eb77a --- /dev/null +++ b/frontend/src/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; +import { type VariantProps, cva } from "class-variance-authority"; + +const badgeVariants = cva( + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ); +} + +export { Badge, badgeVariants }; diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx new file mode 100644 index 00000000..148350de --- /dev/null +++ b/frontend/src/components/ui/button.tsx @@ -0,0 +1,57 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; +import { Slot } from "@radix-ui/react-slot"; +import { type VariantProps, cva } from "class-variance-authority"; + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: + "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx new file mode 100644 index 00000000..84607ef9 --- /dev/null +++ b/frontend/src/components/ui/input.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +export type InputProps = React.InputHTMLAttributes; + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ); + }, +); +Input.displayName = "Input"; + +export { Input }; diff --git a/frontend/src/components/ui/tabs.tsx b/frontend/src/components/ui/tabs.tsx new file mode 100644 index 00000000..9ff20cfd --- /dev/null +++ b/frontend/src/components/ui/tabs.tsx @@ -0,0 +1,53 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; +import * as TabsPrimitive from "@radix-ui/react-tabs"; + +const Tabs = TabsPrimitive.Root; + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsList.displayName = TabsPrimitive.List.displayName; + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsContent.displayName = TabsPrimitive.Content.displayName; + +export { Tabs, TabsList, TabsTrigger, TabsContent }; diff --git a/frontend/src/hooks/useScrollToTop.tsx b/frontend/src/hooks/useScrollToTop.tsx new file mode 100644 index 00000000..d0acd700 --- /dev/null +++ b/frontend/src/hooks/useScrollToTop.tsx @@ -0,0 +1,10 @@ +import { useEffect } from "react"; +import { useLocation } from "react-router-dom"; + +export function useScrollToTop() { + const { pathname } = useLocation(); + + useEffect(() => { + window.scrollTo(0, 0); + }, [pathname]); +}