From 779ce67c4c09f29607a4c3b96587b8aa7848a2ab Mon Sep 17 00:00:00 2001 From: Abhishek kushwaha Date: Wed, 23 Oct 2024 12:43:54 +0530 Subject: [PATCH] feat: event page for keploy with mock data Signed-off-by: Abhishek kushwaha --- .../event/_components/eventheader.tsx | 49 ++++++++ app/(default)/event/_components/header.tsx | 111 ++++++++++++++++++ app/(default)/event/_data/events.ts | 94 +++++++++++++++ app/(default)/event/layout.tsx | 36 ++++++ app/(default)/event/page.tsx | 83 +++++++++++++ components/ui/badge.tsx | 39 ++++++ components/ui/card.tsx | 79 +++++++++++++ components/ui/hover-border-gradient.tsx | 101 ++++++++++++++++ components/utils/countingNumbers.tsx | 6 +- package-lock.json | 20 ++++ package.json | 1 + 11 files changed, 616 insertions(+), 3 deletions(-) create mode 100644 app/(default)/event/_components/eventheader.tsx create mode 100644 app/(default)/event/_components/header.tsx create mode 100644 app/(default)/event/_data/events.ts create mode 100644 app/(default)/event/layout.tsx create mode 100644 app/(default)/event/page.tsx create mode 100644 components/ui/badge.tsx create mode 100644 components/ui/card.tsx create mode 100644 components/ui/hover-border-gradient.tsx diff --git a/app/(default)/event/_components/eventheader.tsx b/app/(default)/event/_components/eventheader.tsx new file mode 100644 index 00000000..1dd22dfb --- /dev/null +++ b/app/(default)/event/_components/eventheader.tsx @@ -0,0 +1,49 @@ + +import { cn } from '@/lib/utils'; +import { UsersIcon } from '@heroicons/react/24/solid' +import { HoverBorderGradient } from '@/components/ui/hover-border-gradient'; +import React from 'react' + +function EventHeader() { + return ( +
+
+ +

+ + Events +

+
+

+ Events +

+

+ Explore Keploy events and webinars. Learn from each other, move forward together, and celebrate what’s next. Whether you’re a developer, architect, tester, community manager, or something in-between, you’ll find something here. +

+
+
+ ) +} + + +function HoverBorderGradientChip({ + children, + className, +}: { + children: React.ReactNode; + className?: string; +}) { + return ( +
+ + {children} + +
+ ); +} + +export default EventHeader \ No newline at end of file diff --git a/app/(default)/event/_components/header.tsx b/app/(default)/event/_components/header.tsx new file mode 100644 index 00000000..299097c0 --- /dev/null +++ b/app/(default)/event/_components/header.tsx @@ -0,0 +1,111 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import Link from "next/link"; +import MobileMenu from "@/components/ui/mobile-menu"; +import { FaSlack } from "react-icons/fa"; +import { StarIcon } from "@heroicons/react/24/solid"; +import CountingNumbers from "@/components/utils/countingNumbers"; + +export default function Header() { + const [top, setTop] = useState(true); + const [starsCount, setStarsCount] = useState(0); + + // detect whether user has scrolled the page down by 10px + const scrollHandler = () => { + window.pageYOffset > 10 ? setTop(false) : setTop(true); + }; + + useEffect(() => { + scrollHandler(); + window.addEventListener("scroll", scrollHandler); + return () => { + window.removeEventListener("scroll", scrollHandler); + }; + }, []); + + useEffect(() => { + const fetchStarsCount = async () => { + try { + const response = await fetch( + "https://api.github.com/repos/keploy/keploy" + ); + if (response.ok) { + const data = await response.json() as { stargazers_count: number }; + const stars = data.stargazers_count; + return stars; + } else { + console.error("Failed to fetch stars count", response.statusText); + return 0; // Return a default value in case of error + } + } catch (error) { + console.error("Error fetching stars count:", error); + return 0; // Return a default value in case of error + } + }; + + void fetchStarsCount().then(setStarsCount); + }, []); + + return ( +
+
+
+ {/* Site branding */} +
+ + {/* Keploy Logo */} + Keploy + +
+ + {/* Desktop navigation */} + + + +
+
+
+ ); +} diff --git a/app/(default)/event/_data/events.ts b/app/(default)/event/_data/events.ts new file mode 100644 index 00000000..09f2343e --- /dev/null +++ b/app/(default)/event/_data/events.ts @@ -0,0 +1,94 @@ +export const events: EventType[] = [ + { + id: 1, + title: "Keploy Meetup", + subtitle: "Keploy Meetup", + description: "Keploy Meetup", + startsAt: "2024-01-01", + duration: 60, + tags: ["Keploy", "Meetup"], + registerURL: "https://keploy.io", + createdAt: "2024-01-01", + updatedAt: "2024-01-01", + publishedAt: "2024-01-01", + banner: { + url: "https://keploy.io", + width: 100, + height: 100, + } + }, + { + id: 1, + title: "Keploy Meetup", + subtitle: "Keploy Meetup", + description: "Keploy Meetup", + startsAt: "2024-01-01", + duration: 60, + tags: ["Keploy", "Meetup"], + registerURL: "https://keploy.io", + createdAt: "2024-01-01", + updatedAt: "2024-01-01", + publishedAt: "2024-01-01", + banner: { + url: "https://keploy.io", + width: 100, + height: 100, + } + }, + { + id: 1, + title: "Keploy Meetup", + subtitle: "Keploy Meetup", + description: "Keploy Meetup", + startsAt: "2024-01-01", + duration: 60, + tags: ["Keploy", "Meetup"], + registerURL: "https://keploy.io", + createdAt: "2024-01-01", + updatedAt: "2024-01-01", + publishedAt: "2024-01-01", + banner: { + url: "https://keploy.io", + width: 100, + height: 100, + } + }, + { + id: 1, + title: "Keploy Meetup", + subtitle: "Keploy Meetup", + description: "Keploy Meetup", + startsAt: "2024-01-01", + duration: 60, + tags: ["Keploy", "Meetup"], + registerURL: "https://keploy.io", + createdAt: "2024-01-01", + updatedAt: "2024-01-01", + publishedAt: "2024-01-01", + banner: { + url: "https://keploy.io", + width: 100, + height: 100, + } + } +] + + +export interface EventType { + id: number; + title: string; + subtitle: string; + description: string; + startsAt: string; + duration: number; + tags: string[]; + registerURL: string | null; + createdAt: string; + updatedAt: string; + publishedAt: string; + banner: { + url: string; + width: number; + height: number; + }; +} diff --git a/app/(default)/event/layout.tsx b/app/(default)/event/layout.tsx new file mode 100644 index 00000000..cfd010ac --- /dev/null +++ b/app/(default)/event/layout.tsx @@ -0,0 +1,36 @@ +import Header from './_components/header' + +import { Inter, Architects_Daughter } from 'next/font/google' + + +const inter = Inter({ + subsets: ['latin'], + variable: '--font-inter', + display: 'swap' +}) + + +const architects_daughter = Architects_Daughter({ + subsets: ['latin'], + variable: '--font-architects-daughter', + weight: '400', + display: 'swap' +}) + +export const metadata = { + title: "Events | Keploy", + description: "Keploy is your open-source, developer-centric backend testing tool. It makes backend testing easy and productive for engineering teams. Plus, it's easy-to-use, powerful and extensible. 🛠️Keploy creates test cases and data mocks/stubs from user-traffic by recording API calls and DB queries, significantly speeding up releases and enhancing reliability. 📈" +} + +export default function EventLayout({ children, }: { children: React.ReactNode }) { + return ( + + +
+
+ {children} +
+ + + ) +} \ No newline at end of file diff --git a/app/(default)/event/page.tsx b/app/(default)/event/page.tsx new file mode 100644 index 00000000..beda3432 --- /dev/null +++ b/app/(default)/event/page.tsx @@ -0,0 +1,83 @@ +"use client" + +import React from 'react' +import EventHeader from './_components/eventheader' +import Button from '@mui/material/Button' + +import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { CalendarIcon, ClockIcon, } from '@heroicons/react/24/solid' +import Image from 'next/image' +import Link from 'next/link' + +import { events } from './_data/events' + +function EventPage() { + return ( +
+
+ +
+
+
+ {events.map((event, index) => ( + +
+ {event.title} +
+ + + {event.title} + + + {event.subtitle} + + + +
+ + + {new Date(event.startsAt).toLocaleString()} + +
+
+ + {event.duration} minutes +
+
+ {event.tags.slice(0, 3).map((tag, index) => ( + + {tag} + + ))} +
+
+ + + +
+ ))} +
+
+
+
+
+ ) +} + +export default EventPage \ No newline at end of file diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx new file mode 100644 index 00000000..9cb0b878 --- /dev/null +++ b/components/ui/badge.tsx @@ -0,0 +1,39 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-full 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 hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground 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/components/ui/card.tsx b/components/ui/card.tsx new file mode 100644 index 00000000..5b8e64f7 --- /dev/null +++ b/components/ui/card.tsx @@ -0,0 +1,79 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/components/ui/hover-border-gradient.tsx b/components/ui/hover-border-gradient.tsx new file mode 100644 index 00000000..5e710eec --- /dev/null +++ b/components/ui/hover-border-gradient.tsx @@ -0,0 +1,101 @@ +'use client'; +import React, { useState, useEffect } from 'react'; + +import { motion } from 'framer-motion'; +import { cn } from '@/lib/utils'; + +type Direction = 'TOP' | 'LEFT' | 'BOTTOM' | 'RIGHT'; + +export function HoverBorderGradient({ + children, + containerClassName, + className, + as: Tag = 'button', + duration = 1, + clockwise = true, + ...props +}: React.PropsWithChildren< + { + as?: React.ElementType; + containerClassName?: string; + className?: string; + duration?: number; + clockwise?: boolean; + } & React.HTMLAttributes +>) { + const [hovered, setHovered] = useState(false); + const [direction, setDirection] = useState('TOP'); + + const rotateDirection = (currentDirection: Direction): Direction => { + const directions: Direction[] = ['TOP', 'LEFT', 'BOTTOM', 'RIGHT']; + const currentIndex = directions.indexOf(currentDirection); + const nextIndex = clockwise + ? (currentIndex - 1 + directions.length) % directions.length + : (currentIndex + 1) % directions.length; + return directions[nextIndex]; + }; + + const movingMap: Record = { + TOP: 'radial-gradient(20.7% 50% at 50% 0%, #c8ff00 0%, rgba(200, 255, 0, 0) 100%)', + LEFT: 'radial-gradient(16.6% 43.1% at 0% 50%, #c8ff00 0%, rgba(200, 255, 0, 0) 100%)', + BOTTOM: + 'radial-gradient(20.7% 50% at 50% 100%, #c8ff00 0%, rgba(200, 255, 0, 0) 100%)', + RIGHT: + 'radial-gradient(16.2% 41.199999999999996% at 100% 50%, #c8ff00 0%, rgba(200, 255, 0, 0) 100%)', + }; + + const highlight = + 'radial-gradient(75% 181.15942028985506% at 50% 50%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)'; + + useEffect(() => { + if (!hovered) { + const interval = setInterval(() => { + setDirection((prevState) => rotateDirection(prevState)); + }, duration * 1000); + return () => { clearInterval(interval); }; + } + }, [hovered]); + return ( + ) => { + setHovered(true); + }} + onMouseLeave={() => { + setHovered(false); + }} + className={cn( + 'relative flex rounded-full border content-center bg-black/20 hover:bg-black/10 transition duration-500 dark:bg-white/20 items-center flex-col flex-nowrap gap-10 h-min justify-center overflow-visible p-px decoration-clone w-fit', + containerClassName + )} + {...props} + > +
+ {children} +
+ +
+ + ); +} diff --git a/components/utils/countingNumbers.tsx b/components/utils/countingNumbers.tsx index 0d753fd3..1f231b9c 100644 --- a/components/utils/countingNumbers.tsx +++ b/components/utils/countingNumbers.tsx @@ -3,7 +3,7 @@ import { useInView } from "framer-motion"; import { useEffect, useRef, useState } from "react"; -const formatStars = (num:number)=>Intl.NumberFormat('en-US', { +const formatStars = (num: number) => Intl.NumberFormat('en-US', { notation: "compact", maximumFractionDigits: 1 }).format(num); @@ -47,7 +47,7 @@ export default function CountingNumbers({ "https://api.github.com/repos/keploy/keploy" ); if (response.ok) { - const data = await response.json(); + const data = await response.json() as { stargazers_count: number }; // Use setStarsCount to update the state setStarsCount(data.stargazers_count); } else { @@ -58,7 +58,7 @@ export default function CountingNumbers({ } }; - fetchStarsCount(); + void fetchStarsCount(); }, [starsCount]); // Include starsCount as a dependency diff --git a/package-lock.json b/package-lock.json index 31ff6423..0b51e32d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "@types/react-dom": "^18.2.4", "aos": "^3.0.0-beta.6", "blitz": "^2.0.0-beta.31", + "class-variance-authority": "^0.7.0", "clipboard": "^2.0.11", "clsx": "^2.1.1", "framer-motion": "^10.16.14", @@ -4559,6 +4560,25 @@ "node": ">= 0.4" } }, + "node_modules/class-variance-authority": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", + "integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==", + "dependencies": { + "clsx": "2.0.0" + }, + "funding": { + "url": "https://joebell.co.uk" + } + }, + "node_modules/class-variance-authority/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/classlist-polyfill": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz", diff --git a/package.json b/package.json index 2d6adfef..47792e29 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@types/react-dom": "^18.2.4", "aos": "^3.0.0-beta.6", "blitz": "^2.0.0-beta.31", + "class-variance-authority": "^0.7.0", "clipboard": "^2.0.11", "clsx": "^2.1.1", "framer-motion": "^10.16.14",