diff options
| -rw-r--r-- | components.json | 16 | ||||
| -rw-r--r-- | src/components/ChannelCard/ChannelCard.tsx | 94 | ||||
| -rw-r--r-- | src/components/Divider/Divider.tsx | 2 | ||||
| -rw-r--r-- | src/components/channel-card.tsx | 84 | ||||
| -rw-r--r-- | src/components/ui/avatar.tsx | 50 | ||||
| -rw-r--r-- | src/components/ui/badge.tsx | 36 | ||||
| -rw-r--r-- | src/components/ui/card.tsx | 86 | ||||
| -rw-r--r-- | src/lib/utils.ts | 6 | ||||
| -rw-r--r-- | src/pages/stats/[slug].tsx | 84 |
9 files changed, 137 insertions, 321 deletions
diff --git a/components.json b/components.json deleted file mode 100644 index 2fceff5..0000000 --- a/components.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", - "rsc": true, - "tsx": true, - "tailwind": { - "config": "tailwind.config.ts", - "css": "src/app/globals.css", - "baseColor": "gray", - "cssVariables": false - }, - "aliases": { - "utils": "@/lib/utils", - "components": "@/components" - } -}
\ No newline at end of file diff --git a/src/components/ChannelCard/ChannelCard.tsx b/src/components/ChannelCard/ChannelCard.tsx new file mode 100644 index 0000000..83471f2 --- /dev/null +++ b/src/components/ChannelCard/ChannelCard.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import Image from 'next/image'; + +type ChannelCardProps = { + channel_id: string; + name: string; + avatarUrl: string; + subscriberCount: number; + videoCount: number; + viewCount: number; + suborg?: string; + nextMilestone: string; + nextMilestoneDays: string; + nextMilestoneDate: string; +}; + +const ChannelCard: React.FC<ChannelCardProps> = ({ + channel_id, + name, + avatarUrl, + subscriberCount, + videoCount, + viewCount, + suborg, + nextMilestone, + nextMilestoneDays, + nextMilestoneDate, +}) => { + return ( + <div className="max-w-4xl w-full mb-4 mt-4 rounded-xl overflow-hidden shadow-lg bg-gradient-to-r from-gray-800 via-gray-900 to-gray-800 p-4 sm:p-8 hover:shadow-2xl transition-all duration-300"> + <div className="flex flex-col sm:flex-row items-center mb-6"> + <Image + src={avatarUrl} + alt={name} + width={80} + height={80} + className="rounded-full border-4 border-white" + /> + <div className="mt-4 sm:mt-0 sm:ml-6 text-center sm:text-left"> + <h3 className="text-xl sm:text-2xl font-bold text-white"> + {name} + </h3> + {suborg && ( + <p className="text-sm sm:text-md text-gray-400"> + {suborg} + </p> + )} + </div> + </div> + <div className="grid grid-cols-1 sm:grid-cols-3 gap-4 sm:gap-8 text-center mb-6"> + <div> + <p className="text-lg sm:text-xl font-bold text-white"> + {subscriberCount.toLocaleString()} + </p> + <p className="text-xs sm:text-sm text-gray-400"> + Subscribers + </p> + </div> + <div> + <p className="text-lg sm:text-xl font-bold text-white"> + {videoCount.toLocaleString()} + </p> + <p className="text-xs sm:text-sm text-gray-400"> + Videos + </p> + </div> + <div> + <p className="text-lg sm:text-xl font-bold text-white"> + {viewCount.toLocaleString()} + </p> + <p className="text-xs sm:text-sm text-gray-400"> + Views + </p> + </div> + </div> + <div className="bg-gray-700 rounded-lg p-4 mb-6"> + <p className="text-md sm:text-lg font-semibold text-white"> + Next Milestone: {Number(nextMilestone).toLocaleString()} + </p> + <p className="text-xs sm:text-sm text-gray-300"> + Estimated in {nextMilestoneDays} days ({nextMilestoneDate}) + </p> + </div> + <button + onClick={() => console.log(`Navigate to channel ${channel_id}`)} + className="w-full bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-6 rounded-lg transition-all duration-200" + > + View Channel on YouTube + </button> + </div> + ); +}; + +export default ChannelCard;
\ No newline at end of file diff --git a/src/components/Divider/Divider.tsx b/src/components/Divider/Divider.tsx index e2ef41b..ef6b30b 100644 --- a/src/components/Divider/Divider.tsx +++ b/src/components/Divider/Divider.tsx @@ -4,7 +4,7 @@ interface DividerProps { const Divider = (props: DividerProps) => { return ( - <div className="flex flex-row items-center justify-center bg-black h-24 mt-8"> + <div className="flex flex-row items-center justify-center bg-black h-24 max-w-full px-72"> <div className="px-2 text-white text-4xl font-extrabold"> {props.text} </div> diff --git a/src/components/channel-card.tsx b/src/components/channel-card.tsx deleted file mode 100644 index c19f492..0000000 --- a/src/components/channel-card.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { Badge } from "@/components/ui/badge"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; - -interface ChannelCardProps { - channel_id: string; - name: string; - avatarUrl: string; - subscriberCount: number; - videoCount: number; - viewCount: number; - suborg: string; - nextMilestone: string; - nextMilestoneDays: string; - nextMilestoneDate: string; -} - -export function ChannelCard(props: ChannelCardProps) { - const { - channel_id, - name, - avatarUrl, - subscriberCount, - videoCount, - viewCount, - suborg, - nextMilestone, - nextMilestoneDays, - nextMilestoneDate, - } = props; - return ( - <Card className="w-[500px] shadow-lg rounded-lg overflow-hidden mt-4 py-4"> - <CardHeader> - <div className="flex items-center space-x-4 p-4"> - <Avatar> - <AvatarImage src={avatarUrl} /> - <AvatarFallback>PR</AvatarFallback> - </Avatar> - <div> - <a - className="hover:underline" - href={`https://youtube.com/channel/${channel_id}`} - > - <CardTitle>{name}</CardTitle> - </a> - <Badge variant="secondary">{suborg}</Badge> - </div> - </div> - </CardHeader> - <CardContent className="px-4 py-2 space-y-4"> - <div className="flex flex-col items-center"> - <span className="text-l text-gray-600">Subscribers</span> - <span className="font-semibold"> - {Number(subscriberCount).toLocaleString()} - </span> - </div> - <div className="flex flex-col items-center"> - <span className="text-l text-gray-600">Videos</span> - <span className="font-semibold">{videoCount}</span> - </div> - <div className="flex flex-col items-center"> - <span className="text-l text-gray-600">View Count</span> - <span className="font-semibold"> - {Number(viewCount).toLocaleString()} - </span> - </div> - <div className="flex flex-col items-center"> - <span className="text-l text-gray-600">Next Milestone</span> - <span className="font-semibold"> - {Number(nextMilestone).toLocaleString()} - </span> - <div className="flex justify-center items-center"> - <span className="text-sm text-gray-600 px-2"> - {nextMilestoneDays} days - </span> - <span className="text-sm text-gray-600 px-2"> - {nextMilestoneDate} - </span> - </div> - </div> - </CardContent> - </Card> - ); -} diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx deleted file mode 100644 index 3c884e8..0000000 --- a/src/components/ui/avatar.tsx +++ /dev/null @@ -1,50 +0,0 @@ -"use client"; - -import * as AvatarPrimitive from "@radix-ui/react-avatar"; -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -const Avatar = React.forwardRef< - React.ElementRef<typeof AvatarPrimitive.Root>, - React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> ->(({ className, ...props }, ref) => ( - <AvatarPrimitive.Root - ref={ref} - className={cn( - "relative flex h-12 w-12 overflow-hidden rounded-full", - className, - )} - {...props} - /> -)); -Avatar.displayName = AvatarPrimitive.Root.displayName; - -const AvatarImage = React.forwardRef< - React.ElementRef<typeof AvatarPrimitive.Image>, - React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> ->(({ className, ...props }, ref) => ( - <AvatarPrimitive.Image - ref={ref} - className={cn("aspect-square h-full w-full", className)} - {...props} - /> -)); -AvatarImage.displayName = AvatarPrimitive.Image.displayName; - -const AvatarFallback = React.forwardRef< - React.ElementRef<typeof AvatarPrimitive.Fallback>, - React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> ->(({ className, ...props }, ref) => ( - <AvatarPrimitive.Fallback - ref={ref} - className={cn( - "flex h-full w-full items-center justify-center rounded-full bg-muted", - className, - )} - {...props} - /> -)); -AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; - -export { Avatar, AvatarImage, AvatarFallback }; diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx deleted file mode 100644 index f38976c..0000000 --- a/src/components/ui/badge.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { type VariantProps, cva } from "class-variance-authority"; -import type * as React from "react"; - -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<HTMLDivElement>, - VariantProps<typeof badgeVariants> {} - -function Badge({ className, variant, ...props }: BadgeProps) { - return ( - <div className={cn(badgeVariants({ variant }), className)} {...props} /> - ); -} - -export { Badge, badgeVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx deleted file mode 100644 index 2681236..0000000 --- a/src/components/ui/card.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -const Card = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> ->(({ className, ...props }, ref) => ( - <div - ref={ref} - className={cn( - "rounded-lg border bg-card text-card-foreground shadow-sm", - className, - )} - {...props} - /> -)); -Card.displayName = "Card"; - -const CardHeader = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> ->(({ className, ...props }, ref) => ( - <div - ref={ref} - className={cn("flex flex-col space-y-1 px-4", className)} - {...props} - /> -)); -CardHeader.displayName = "CardHeader"; - -const CardTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes<HTMLHeadingElement> ->(({ className, ...props }, ref) => ( - <h3 - ref={ref} - className={cn( - "text-2xl font-semibold leading-none tracking-tight", - className, - )} - {...props} - /> -)); -CardTitle.displayName = "CardTitle"; - -const CardDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes<HTMLParagraphElement> ->(({ className, ...props }, ref) => ( - <p - ref={ref} - className={cn("text-sm text-muted-foreground", className)} - {...props} - /> -)); -CardDescription.displayName = "CardDescription"; - -const CardContent = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> ->(({ className, ...props }, ref) => ( - <div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> -)); -CardContent.displayName = "CardContent"; - -const CardFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> ->(({ className, ...props }, ref) => ( - <div - ref={ref} - className={cn("flex items-center p-6 pt-0", className)} - {...props} - /> -)); -CardFooter.displayName = "CardFooter"; - -export { - Card, - CardHeader, - CardFooter, - CardTitle, - CardDescription, - CardContent, -}; diff --git a/src/lib/utils.ts b/src/lib/utils.ts deleted file mode 100644 index ac680b3..0000000 --- a/src/lib/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { type ClassValue, clsx } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} diff --git a/src/pages/stats/[slug].tsx b/src/pages/stats/[slug].tsx index dfd63e4..685d01b 100644 --- a/src/pages/stats/[slug].tsx +++ b/src/pages/stats/[slug].tsx @@ -4,7 +4,7 @@ import CompactTable from "@/components/CompactTable/CompactTable"; import DataChart from "@/components/DataChart/DataChart"; import Divider from "@/components/Divider/Divider"; import Footer from "@/components/Footer/Footer"; -import { ChannelCard } from "@/components/channel-card"; +import ChannelCard from "@/components/ChannelCard/ChannelCard" import Head from "next/head"; import TitleBar from "../../components/TitleBar/TitleBar"; @@ -93,48 +93,48 @@ function Page({ showHomeButton backgroundColor="black" /> - <div className="flex justify-center"> - <div className="flex flex-col items-center"> - <ChannelCard - channel_id={channelData.channel_id} - name={channelData.channel_name} - avatarUrl={channelData.profile_pic} - subscriberCount={channelData.subscribers} - videoCount={channelData.video_count} - viewCount={channelData.view_count} - suborg={channelData.sub_org} - nextMilestone={channelData.next_milestone} - nextMilestoneDays={channelData.days_until_next_milestone} - nextMilestoneDate={channelData.next_milestone_date} - /> - </div> + <div className="flex justify-center px-12 "> + <ChannelCard + channel_id={channelData.channel_id} + name={channelData.channel_name} + avatarUrl={channelData.profile_pic} + subscriberCount={channelData.subscribers} + videoCount={channelData.video_count} + viewCount={channelData.view_count} + suborg={channelData.sub_org} + nextMilestone={channelData.next_milestone} + nextMilestoneDays={channelData.days_until_next_milestone} + nextMilestoneDate={channelData.next_milestone_date} + /> </div> - <Divider text="Individual Data" /> - <div className="px-48 mb-10 mt-10"> - <div className="mb-12"> - <DataChart - overrideBGColor="black" - overrideBorderColor="black" - chartData={chartData} - /> - </div> - <div className="mb-12"> - <DataChart - chartData={sevenDayGraphData} - overrideBGColor="black" - overrideBorderColor="black" - graphTitle="7 Day Historical" - /> - </div> - <Divider text="Milestones" /> - <div className="mb-12"> - <CompactTable - tableData={{ - dates: milestoneData.dates, - milestones: milestoneData.milestones, - }} - /> - <p className="mt-2 font-semibold">For intervals which we did not record any data, the closest recorded datapoint is chosen</p> + <div className="hidden sm:block"> + <Divider text="Individual Data" /> + <div className="px-48 mb-10 mt-10"> + <div className="mb-12"> + <DataChart + overrideBGColor="black" + overrideBorderColor="black" + chartData={chartData} + /> + </div> + <div className="mb-12"> + <DataChart + chartData={sevenDayGraphData} + overrideBGColor="black" + overrideBorderColor="black" + graphTitle="7 Day Historical" + /> + </div> + <Divider text="Milestones" /> + <div className="mb-12"> + <CompactTable + tableData={{ + dates: milestoneData.dates, + milestones: milestoneData.milestones, + }} + /> + <p className="mt-2 font-semibold">For intervals which we did not record any data, the closest recorded datapoint is chosen</p> + </div> </div> </div> <Footer /> |
