diff --git a/components/ui/Button.tsx b/components/ui/Button.tsx
new file mode 100644
index 0000000..8f00c91
--- /dev/null
+++ b/components/ui/Button.tsx
@@ -0,0 +1,54 @@
+import React from 'react';
+import { cn } from '@/lib/utils';
+
+export type ButtonVariant = 'primary' | 'outline' | 'error';
+export type ButtonSize = 'sm' | 'md' | 'lg';
+
+export interface ButtonClassesProps {
+ variant?: ButtonVariant;
+ size?: ButtonSize;
+ className?: string;
+}
+
+export function buttonClasses({ variant = 'primary', size = 'md', className }: ButtonClassesProps = {}) {
+ const baseClasses = 'inline-flex items-center justify-center gap-2 whitespace-nowrap transition-colors';
+
+ const variantClasses = {
+ primary: 'btn btn-primary text-white font-bold',
+ outline: 'btn bg-white/10 text-white border border-white/20 hover:bg-white/20 font-bold',
+ error: 'btn btn-error text-white font-bold',
+ };
+
+ const sizeClasses = {
+ sm: 'btn-sm text-sm',
+ md: '', // default DaisyUI btn size
+ lg: 'btn-lg text-lg',
+ };
+
+ return cn(
+ baseClasses,
+ variantClasses[variant],
+ sizeClasses[size],
+ className
+ );
+}
+
+export interface ButtonProps extends React.ButtonHTMLAttributes, ButtonClassesProps {
+ children: React.ReactNode;
+}
+
+export const Button = React.forwardRef(
+ ({ className, variant, size, children, ...props }, ref) => {
+ return (
+
+ );
+ }
+);
+
+Button.displayName = 'Button';
diff --git a/find_buttons.py b/find_buttons.py
new file mode 100644
index 0000000..317ee5a
--- /dev/null
+++ b/find_buttons.py
@@ -0,0 +1,43 @@
+import os
+import re
+from collections import Counter
+
+dirs_to_search = ['app', 'components']
+button_classes = []
+
+class_regex = re.compile(r'className=["\']([^"\']+)["\']')
+dynamic_class_regex = re.compile(r'className=\{`([^`]+)`\}')
+
+for d in dirs_to_search:
+ for root, _, files in os.walk(d):
+ for file in files:
+ if file.endswith(('.tsx', '.jsx', '.ts', '.js')):
+ filepath = os.path.join(root, file)
+ with open(filepath, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ # Find all classNames
+ matches = class_regex.findall(content) + dynamic_class_regex.findall(content)
+
+ for m in matches:
+ # Check if it looks like a button
+ if 'btn' in m or ('px-' in m and 'py-' in m and 'rounded' in m):
+ # Normalize spacing
+ normalized = ' '.join(m.split())
+ button_classes.append((normalized, filepath))
+
+# Count frequencies
+counts = Counter([c[0] for c in button_classes])
+most_common = counts.most_common(10)
+
+print("Top variants:")
+for variant, count in most_common:
+ print(f"\nVariant ({count} times): {variant}")
+ # Find up to 3 files where it appears
+ files_seen = set()
+ for c, fpath in button_classes:
+ if c == variant and fpath not in files_seen:
+ files_seen.add(fpath)
+ print(f" - {fpath}")
+ if len(files_seen) >= 3:
+ break
diff --git a/refactor_buttons.js b/refactor_buttons.js
new file mode 100644
index 0000000..9f89c1a
--- /dev/null
+++ b/refactor_buttons.js
@@ -0,0 +1,133 @@
+const fs = require('fs');
+const path = require('path');
+
+const directories = ['app', 'components'];
+const patterns = [
+ {
+ regex: /className=["']flex items-center gap-2 px-6 py-3 bg-accent text-white rounded-xl font-bold hover:bg-accent\/90 transition-colors["']/g,
+ replace: "className={buttonClasses({ variant: 'primary', size: 'md' })}"
+ },
+ {
+ regex: /className=["']flex items-center gap-2 px-6 py-3 bg-white\/10 text-white rounded-xl font-bold hover:bg-white\/20 transition-colors border border-white\/20["']/g,
+ replace: "className={buttonClasses({ variant: 'outline', size: 'md' })}"
+ },
+ {
+ regex: /className=["']flex items-center gap-2 px-8 py-4 bg-accent text-white rounded-xl font-bold text-lg hover:bg-accent\/90 transition-colors["']/g,
+ replace: "className={buttonClasses({ variant: 'primary', size: 'lg' })}"
+ },
+ {
+ regex: /className=["']flex items-center gap-2 px-8 py-4 bg-white\/10 text-white rounded-xl font-bold text-lg hover:bg-white\/20 transition-colors border border-white\/20["']/g,
+ replace: "className={buttonClasses({ variant: 'outline', size: 'lg' })}"
+ },
+ {
+ regex: /className=["']btn btn-primary text-white font-bold["']/g,
+ replace: "className={buttonClasses({ variant: 'primary', size: 'md' })}"
+ },
+ {
+ regex: /className=["']btn btn-primary font-bold["']/g,
+ replace: "className={buttonClasses({ variant: 'primary', size: 'md' })}"
+ },
+ {
+ regex: /className=["']flex items-center gap-1\.5 px-4 py-2 bg-red-500 text-white rounded-\[var\(--radius-field\)\] font-bold text-sm hover:bg-red-600 transition-colors whitespace-nowrap["']/g,
+ replace: "className={buttonClasses({ variant: 'error', size: 'sm' })}"
+ }
+];
+
+// In case there are some dynamic classNames (template literals):
+const dynamicPatterns = [
+ {
+ regex: /className=\{`flex items-center gap-2 px-6 py-3 bg-accent text-white rounded-xl font-bold hover:bg-accent\/90 transition-colors (.*?)`\}/g,
+ replace: "className={buttonClasses({ variant: 'primary', size: 'md', className: `$1` })}"
+ },
+ {
+ regex: /className=\{`btn btn-primary font-bold (.*?)`\}/g,
+ replace: "className={buttonClasses({ variant: 'primary', size: 'md', className: `$1` })}"
+ }
+];
+
+function processDirectory(dir) {
+ const files = fs.readdirSync(dir);
+
+ for (const file of files) {
+ const fullPath = path.join(dir, file);
+ const stat = fs.statSync(fullPath);
+
+ if (stat.isDirectory()) {
+ processDirectory(fullPath);
+ } else if (file.endsWith('.tsx') || file.endsWith('.jsx')) {
+ // Don't process the Button.tsx itself
+ if (fullPath.includes('Button.tsx')) continue;
+
+ let content = fs.readFileSync(fullPath, 'utf8');
+ let originalContent = content;
+
+ let changed = false;
+ let usedButtonClasses = false;
+ let usedButtonComponent = false;
+
+ // 1. Tag conversion: