diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 99bc0b2..d06487a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7142,15 +7142,14 @@ } } }, - "node_modules/next-intl/node_modules/@swc/helpers": { - "version": "0.5.20", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.20.tgz", - "integrity": "sha512-2egEBHUMasdypIzrprsu8g+OEVd7Vp2MM3a2eVlM/cyFYto0nGz5BX5BTgh/ShZZI9ed+ozEq+Ngt+rgmUs8tw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.8.0" + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "node_modules/next-themes": { diff --git a/frontend/src/components/ui/Badge.tsx b/frontend/src/components/ui/Badge.tsx new file mode 100644 index 0000000..78d26e0 --- /dev/null +++ b/frontend/src/components/ui/Badge.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { cn } from "@/lib/utils"; +import type { BadgeProps, BadgeVariant, BadgeSize } from "@/types/ui"; + +export type { BadgeProps, BadgeVariant, BadgeSize }; + +const variantColorMap: Record = { + open: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400", + "in-progress": + "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400", + completed: + "bg-stellar-slate text-stellar-white dark:bg-stellar-navy dark:text-stellar-white", + expired: "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-400", + draft: "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-400", + active: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400", + passed: + "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400", + rejected: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400", + executed: + "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400", + default: + "bg-stellar-lightNavy text-stellar-white dark:bg-stellar-navy dark:text-stellar-white", +}; + +const sizeClasses: Record = { + sm: "px-2 py-0.5 text-xs", + md: "px-2.5 py-0.5 text-sm", + lg: "px-3 py-1 text-base", +}; + +const Badge = React.forwardRef( + ( + { className, variant = "default", size = "md", children, ...props }, + ref, + ) => { + const baseClasses = + "inline-flex items-center justify-center font-medium rounded-full transition-colors"; + + return ( + + {children} + + ); + }, +); + +Badge.displayName = "Badge"; + +export { Badge }; diff --git a/frontend/src/components/ui/index.ts b/frontend/src/components/ui/index.ts index 20e4928..531166f 100644 --- a/frontend/src/components/ui/index.ts +++ b/frontend/src/components/ui/index.ts @@ -6,6 +6,7 @@ export { Modal } from "./Modal"; export { Dropzone } from "./Dropzone"; export { EmptyState } from "./EmptyState"; export { SearchInput } from "./SearchInput"; +export { Badge } from "./Badge"; export { DropdownMenu, DropdownMenuTrigger, diff --git a/frontend/src/types/ui.ts b/frontend/src/types/ui.ts index 84cb04d..c6109f3 100644 --- a/frontend/src/types/ui.ts +++ b/frontend/src/types/ui.ts @@ -1,156 +1,170 @@ -import React from 'react' +import React from "react"; // UI Component Types -export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger' -export type ButtonSize = 'sm' | 'md' | 'lg' +export type ButtonVariant = + | "primary" + | "secondary" + | "outline" + | "ghost" + | "danger"; +export type ButtonSize = "sm" | "md" | "lg"; export interface ButtonProps extends React.ButtonHTMLAttributes { - variant?: ButtonVariant - size?: ButtonSize - isLoading?: boolean - leftIcon?: React.ReactNode - rightIcon?: React.ReactNode + variant?: ButtonVariant; + size?: ButtonSize; + isLoading?: boolean; + leftIcon?: React.ReactNode; + rightIcon?: React.ReactNode; } export interface InputProps extends React.InputHTMLAttributes { - label?: string - error?: string - helperText?: string + label?: string; + error?: string; + helperText?: string; } export interface CardProps extends React.HTMLAttributes { - title?: string - description?: string - footer?: React.ReactNode + title?: string; + description?: string; + footer?: React.ReactNode; } export interface ModalProps { - isOpen: boolean - onClose: () => void - title?: string - children: React.ReactNode - size?: 'sm' | 'md' | 'lg' | 'xl' + isOpen: boolean; + onClose: () => void; + title?: string; + children: React.ReactNode; + size?: "sm" | "md" | "lg" | "xl"; } // Layout Types export interface SidebarItem { - id: string - label: string - href: string - icon: React.ReactNode - isActive?: boolean + id: string; + label: string; + href: string; + icon: React.ReactNode; + isActive?: boolean; } export interface NavigationItem { - id: string - label: string - href: string - isActive?: boolean + id: string; + label: string; + href: string; + isActive?: boolean; } // Mock Data Types export interface Guild { - id: string - name: string - description: string - tier: 'bronze' | 'silver' | 'gold' | 'platinum' - memberCount: number - reputation: number - createdAt: string - logo?: string + id: string; + name: string; + description: string; + tier: "bronze" | "silver" | "gold" | "platinum"; + memberCount: number; + reputation: number; + createdAt: string; + logo?: string; } export interface Bounty { - id: string - title: string - description: string + id: string; + title: string; + description: string; reward: { - amount: number - currency: string - } - deadline: string - status: 'open' | 'in-progress' | 'completed' | 'expired' - guildId: string - createdAt: string + amount: number; + currency: string; + }; + deadline: string; + status: "open" | "in-progress" | "completed" | "expired"; + guildId: string; + createdAt: string; } export interface UserProfile { - id: string - username: string - email: string - avatar?: string - reputation: number - joinedAt: string - guilds: string[] + id: string; + username: string; + email: string; + avatar?: string; + reputation: number; + joinedAt: string; + guilds: string[]; } // Governance Types -export type ProposalType = 'treasury' | 'rule-change' | 'membership' | 'general' -export type ProposalStatus = 'draft' | 'active' | 'passed' | 'rejected' | 'executed' -export type VoteChoice = 'for' | 'against' | 'abstain' +export type ProposalType = + | "treasury" + | "rule-change" + | "membership" + | "general"; +export type ProposalStatus = + | "draft" + | "active" + | "passed" + | "rejected" + | "executed"; +export type VoteChoice = "for" | "against" | "abstain"; export interface Vote { - id: string - proposalId: string - voterId: string - voterAddress: string - choice: VoteChoice - votingPower: number - timestamp: string + id: string; + proposalId: string; + voterId: string; + voterAddress: string; + choice: VoteChoice; + votingPower: number; + timestamp: string; } export interface VotingStats { - for: number - against: number - abstain: number - total: number - forPower: number - againstPower: number - abstainPower: number - totalPower: number + for: number; + against: number; + abstain: number; + total: number; + forPower: number; + againstPower: number; + abstainPower: number; + totalPower: number; } export interface Proposal { - id: string - guildId: string - title: string - description: string - type: ProposalType - status: ProposalStatus - proposerId: string - proposerAddress: string - proposerName: string - createdAt: string - startDate: string - endDate: string - quorum: number - quorumThreshold: number - executionData?: string - votes: Vote[] - votingStats: VotingStats + id: string; + guildId: string; + title: string; + description: string; + type: ProposalType; + status: ProposalStatus; + proposerId: string; + proposerAddress: string; + proposerName: string; + createdAt: string; + startDate: string; + endDate: string; + quorum: number; + quorumThreshold: number; + executionData?: string; + votes: Vote[]; + votingStats: VotingStats; } // Dropzone Types -export type DropzoneVariant = 'avatar' | 'banner' +export type DropzoneVariant = "avatar" | "banner"; export interface DropzoneProps { - variant?: DropzoneVariant - value?: string | null - onChange?: (url: string | null) => void - onError?: (error: string) => void - disabled?: boolean - className?: string + variant?: DropzoneVariant; + value?: string | null; + onChange?: (url: string | null) => void; + onError?: (error: string) => void; + disabled?: boolean; + className?: string; } export interface FileValidationResult { - isValid: boolean - error?: string + isValid: boolean; + error?: string; } export interface UploadResult { - success: boolean - url?: string - error?: string + success: boolean; + url?: string; + error?: string; } export interface Tab { @@ -164,4 +178,24 @@ export interface TabsProps { tabs: Tab[]; defaultTab?: string; className?: string; -} \ No newline at end of file +} + +// Badge Types +export type BadgeVariant = + | "open" + | "in-progress" + | "completed" + | "expired" + | "draft" + | "active" + | "passed" + | "rejected" + | "executed" + | "default"; +export type BadgeSize = "sm" | "md" | "lg"; + +export interface BadgeProps extends React.HTMLAttributes { + variant?: BadgeVariant; + size?: BadgeSize; + children: React.ReactNode; +}