@create-lft-app/cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/bin/cli.d.ts +2 -0
  2. package/dist/bin/cli.js +938 -0
  3. package/dist/bin/cli.js.map +1 -0
  4. package/dist/src/index.d.ts +10 -0
  5. package/dist/src/index.js +669 -0
  6. package/dist/src/index.js.map +1 -0
  7. package/package.json +60 -0
  8. package/templates/app/auth/login/page.tsx +153 -0
  9. package/templates/app/dashboard/page.tsx +102 -0
  10. package/templates/app/globals.css +68 -0
  11. package/templates/app/layout.tsx +40 -0
  12. package/templates/app/page.tsx +5 -0
  13. package/templates/components/dashboard/widget.tsx +113 -0
  14. package/templates/components/layout/admin-midday-sidebar.tsx +247 -0
  15. package/templates/components/layout/admin-sidebar.tsx +146 -0
  16. package/templates/components/layout/header.tsx +71 -0
  17. package/templates/components/layout/impersonation-banner.tsx +36 -0
  18. package/templates/components/layout/main-content.tsx +28 -0
  19. package/templates/components/layout/midday-sidebar.tsx +381 -0
  20. package/templates/components/layout/nav-user.tsx +108 -0
  21. package/templates/components/layout/page-header.tsx +95 -0
  22. package/templates/components/layout/sidebar-context.tsx +33 -0
  23. package/templates/components/layout/sidebar.tsx +194 -0
  24. package/templates/components/layout/suspension-banner.tsx +21 -0
  25. package/templates/components/ui/accordion.tsx +58 -0
  26. package/templates/components/ui/alert-dialog.tsx +165 -0
  27. package/templates/components/ui/alert.tsx +66 -0
  28. package/templates/components/ui/avatar.tsx +55 -0
  29. package/templates/components/ui/badge.tsx +50 -0
  30. package/templates/components/ui/button.tsx +89 -0
  31. package/templates/components/ui/calendar.tsx +220 -0
  32. package/templates/components/ui/card.tsx +89 -0
  33. package/templates/components/ui/checkbox.tsx +38 -0
  34. package/templates/components/ui/collapsible.tsx +33 -0
  35. package/templates/components/ui/command.tsx +196 -0
  36. package/templates/components/ui/dialog.tsx +153 -0
  37. package/templates/components/ui/dropdown-menu.tsx +280 -0
  38. package/templates/components/ui/form.tsx +171 -0
  39. package/templates/components/ui/icons.tsx +167 -0
  40. package/templates/components/ui/input.tsx +28 -0
  41. package/templates/components/ui/label.tsx +25 -0
  42. package/templates/components/ui/popover.tsx +59 -0
  43. package/templates/components/ui/progress.tsx +32 -0
  44. package/templates/components/ui/radio-group.tsx +45 -0
  45. package/templates/components/ui/scroll-area.tsx +63 -0
  46. package/templates/components/ui/select.tsx +208 -0
  47. package/templates/components/ui/separator.tsx +28 -0
  48. package/templates/components/ui/sheet.tsx +146 -0
  49. package/templates/components/ui/sidebar.tsx +726 -0
  50. package/templates/components/ui/skeleton.tsx +15 -0
  51. package/templates/components/ui/slider.tsx +58 -0
  52. package/templates/components/ui/sonner.tsx +47 -0
  53. package/templates/components/ui/spinner.tsx +27 -0
  54. package/templates/components/ui/submit-button.tsx +47 -0
  55. package/templates/components/ui/switch.tsx +31 -0
  56. package/templates/components/ui/table.tsx +120 -0
  57. package/templates/components/ui/tabs.tsx +75 -0
  58. package/templates/components/ui/textarea.tsx +26 -0
  59. package/templates/components/ui/tooltip.tsx +70 -0
  60. package/templates/hooks/use-mobile.ts +21 -0
  61. package/templates/lib/utils.ts +6 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/index.ts","../../src/config/index.ts","../../src/ui/logger.ts","../../src/ui/spinner.ts","../../src/services/github.ts","../../src/services/supabase.ts","../../src/utils/validation.ts","../../src/services/jira.ts","../../src/steps/scaffold-nextjs.ts","../../src/steps/copy-template.ts","../../src/steps/install-deps.ts","../../src/steps/create-env.ts","../../src/steps/setup-git.ts","../../src/ui/banner.ts"],"sourcesContent":["import path from 'path';\nimport { confirm } from '@inquirer/prompts';\nimport { loadConfig, hasConfig } from './config/index.js';\nimport { createGitHubRepo } from './services/github.js';\nimport { createSupabaseProject } from './services/supabase.js';\nimport { createJiraProject } from './services/jira.js';\nimport { scaffoldNextJs } from './steps/scaffold-nextjs.js';\nimport { copyTemplate } from './steps/copy-template.js';\nimport { installDependencies } from './steps/install-deps.js';\nimport { createEnvFile } from './steps/create-env.js';\nimport { setupGit } from './steps/setup-git.js';\nimport { logger } from './ui/logger.js';\nimport { showSuccessBanner } from './ui/banner.js';\nimport { validateProjectName } from './utils/validation.js';\n\nexport interface CreateProjectOptions {\n skipGithub?: boolean;\n skipSupabase?: boolean;\n skipJira?: boolean;\n skipGit?: boolean;\n autoConfirm?: boolean;\n}\n\nexport async function createProject(\n projectName: string,\n options: CreateProjectOptions = {}\n): Promise<void> {\n // Validar nombre del proyecto\n const validation = validateProjectName(projectName);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n\n // Verificar que el directorio no exista\n const projectPath = path.resolve(process.cwd(), projectName);\n\n // Cargar configuración\n if (!await hasConfig()) {\n logger.warning('No se encontró configuración. Ejecuta \"create-lft-app config\" primero.');\n throw new Error('Configuración no encontrada');\n }\n\n const config = await loadConfig();\n\n // Mostrar resumen\n logger.newLine();\n logger.title('Resumen de recursos a crear:');\n logger.newLine();\n\n const resources = [];\n if (!options.skipGithub) {\n resources.push({ label: 'GitHub', value: `${config.defaults.githubOrg || config.credentials.github.username}/${projectName} (privado)` });\n }\n if (!options.skipSupabase) {\n resources.push({ label: 'Supabase', value: `${projectName} en ${config.defaults.supabaseRegion}` });\n }\n if (!options.skipJira) {\n resources.push({ label: 'Jira', value: `Proyecto \"${projectName}\" en ${config.credentials.jira.domain}` });\n }\n resources.push({ label: 'Next.js', value: 'App Router + TypeScript + Tailwind + Dashboard' });\n\n logger.table(resources);\n logger.newLine();\n\n // Confirmar\n if (!options.autoConfirm) {\n const shouldContinue = await confirm({\n message: '¿Continuar con la creación?',\n default: true,\n });\n\n if (!shouldContinue) {\n logger.info('Operación cancelada');\n return;\n }\n }\n\n logger.newLine();\n logger.divider();\n logger.newLine();\n\n const urls: { github?: string; supabase?: string; jira?: string } = {};\n let supabaseKeys: { url: string; anonKey: string; serviceKey: string } | undefined;\n\n // Crear recursos externos en paralelo\n const externalTasks: Promise<void>[] = [];\n\n if (!options.skipGithub) {\n externalTasks.push(\n createGitHubRepo(projectName, config).then((url) => {\n urls.github = url;\n })\n );\n }\n\n if (!options.skipSupabase) {\n externalTasks.push(\n createSupabaseProject(projectName, config).then((result) => {\n urls.supabase = result.url;\n supabaseKeys = result;\n })\n );\n }\n\n if (!options.skipJira) {\n externalTasks.push(\n createJiraProject(projectName, config).then((url) => {\n urls.jira = url;\n })\n );\n }\n\n // Esperar a que terminen los recursos externos\n await Promise.all(externalTasks);\n\n // Scaffold Next.js\n await scaffoldNextJs(projectName, projectPath);\n\n // Copiar template\n await copyTemplate(projectPath);\n\n // Instalar dependencias\n await installDependencies(projectPath);\n\n // Crear .env.local\n if (supabaseKeys) {\n await createEnvFile(projectPath, supabaseKeys);\n }\n\n // Setup git\n if (!options.skipGit && urls.github) {\n await setupGit(projectPath, urls.github);\n }\n\n // Mostrar resumen final\n logger.newLine();\n showSuccessBanner(projectName, urls);\n}\n","import fs from 'fs/promises';\nimport path from 'path';\nimport os from 'os';\nimport { createCipheriv, createDecipheriv, randomBytes, createHash } from 'crypto';\nimport { machineIdSync } from 'node-machine-id';\nimport { input, password, select, confirm } from '@inquirer/prompts';\nimport { logger } from '../ui/logger.js';\nimport { withSpinner } from '../ui/spinner.js';\n\nconst CONFIG_FILE = path.join(os.homedir(), '.lftrc');\nconst ALGORITHM = 'aes-256-gcm';\n\nexport interface LFTConfig {\n version: string;\n credentials: {\n github: {\n token: string;\n username: string;\n };\n supabase: {\n accessToken: string;\n organizationId: string;\n };\n jira: {\n email: string;\n apiToken: string;\n domain: string;\n };\n };\n defaults: {\n githubOrg?: string;\n supabaseRegion: string;\n jiraProjectType: string;\n };\n}\n\nfunction getEncryptionKey(): Buffer {\n const machineId = machineIdSync();\n return createHash('sha256').update(machineId + 'lft-secret').digest();\n}\n\nfunction encryptConfig(config: LFTConfig): string {\n const key = getEncryptionKey();\n const iv = randomBytes(16);\n const cipher = createCipheriv(ALGORITHM, key, iv);\n\n let encrypted = cipher.update(JSON.stringify(config), 'utf8', 'hex');\n encrypted += cipher.final('hex');\n\n const authTag = cipher.getAuthTag();\n return JSON.stringify({\n iv: iv.toString('hex'),\n tag: authTag.toString('hex'),\n data: encrypted,\n });\n}\n\nfunction decryptConfig(encrypted: string): LFTConfig {\n const { iv, tag, data } = JSON.parse(encrypted);\n const key = getEncryptionKey();\n\n const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(iv, 'hex'));\n decipher.setAuthTag(Buffer.from(tag, 'hex'));\n\n let decrypted = decipher.update(data, 'hex', 'utf8');\n decrypted += decipher.final('utf8');\n\n return JSON.parse(decrypted);\n}\n\nexport async function hasConfig(): Promise<boolean> {\n try {\n await fs.access(CONFIG_FILE);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function loadConfig(): Promise<LFTConfig> {\n const encrypted = await fs.readFile(CONFIG_FILE, 'utf8');\n return decryptConfig(encrypted);\n}\n\nexport async function saveConfig(config: LFTConfig): Promise<void> {\n const encrypted = encryptConfig(config);\n await fs.writeFile(CONFIG_FILE, encrypted, 'utf8');\n}\n\nexport async function showConfig(): Promise<void> {\n if (!await hasConfig()) {\n logger.warning('No hay configuración guardada');\n logger.info('Ejecuta \"create-lft-app config\" para configurar');\n return;\n }\n\n const config = await loadConfig();\n\n logger.newLine();\n logger.title('Configuración actual:');\n logger.newLine();\n\n logger.subtitle('GitHub:');\n logger.table([\n { label: 'Usuario', value: config.credentials.github.username },\n { label: 'Token', value: '***' + config.credentials.github.token.slice(-4) },\n { label: 'Org por defecto', value: config.defaults.githubOrg || '(ninguna)' },\n ]);\n\n logger.newLine();\n logger.subtitle('Supabase:');\n logger.table([\n { label: 'Org ID', value: config.credentials.supabase.organizationId },\n { label: 'Token', value: '***' + config.credentials.supabase.accessToken.slice(-4) },\n { label: 'Región', value: config.defaults.supabaseRegion },\n ]);\n\n logger.newLine();\n logger.subtitle('Jira:');\n logger.table([\n { label: 'Email', value: config.credentials.jira.email },\n { label: 'Dominio', value: config.credentials.jira.domain },\n { label: 'Token', value: '***' + config.credentials.jira.apiToken.slice(-4) },\n ]);\n}\n\nexport async function resetConfig(): Promise<void> {\n if (!await hasConfig()) {\n logger.info('No hay configuración para resetear');\n return;\n }\n\n const shouldReset = await confirm({\n message: '¿Estás seguro de que quieres eliminar la configuración?',\n default: false,\n });\n\n if (shouldReset) {\n await fs.unlink(CONFIG_FILE);\n logger.success('Configuración eliminada');\n }\n}\n\ninterface ConfigureOptions {\n onlyGithub?: boolean;\n onlySupabase?: boolean;\n onlyJira?: boolean;\n}\n\nexport async function configureCredentials(options: ConfigureOptions = {}): Promise<void> {\n let config: LFTConfig;\n\n if (await hasConfig()) {\n config = await loadConfig();\n logger.info('Configuración existente encontrada. Actualizando...');\n } else {\n config = {\n version: '1.0.0',\n credentials: {\n github: { token: '', username: '' },\n supabase: { accessToken: '', organizationId: '' },\n jira: { email: '', apiToken: '', domain: '' },\n },\n defaults: {\n supabaseRegion: 'us-east-1',\n jiraProjectType: 'software',\n },\n };\n }\n\n const configureAll = !options.onlyGithub && !options.onlySupabase && !options.onlyJira;\n\n // GitHub\n if (configureAll || options.onlyGithub) {\n logger.newLine();\n logger.title('Configuración de GitHub');\n logger.subtitle('Necesitas un Personal Access Token con permisos: repo, read:org');\n logger.link('Crear token', 'https://github.com/settings/tokens/new');\n logger.newLine();\n\n const githubToken = await password({\n message: 'GitHub Personal Access Token:',\n mask: '*',\n });\n\n // Validar token y obtener username\n const isValid = await withSpinner(\n 'Validando token de GitHub...',\n async () => {\n const response = await fetch('https://api.github.com/user', {\n headers: { Authorization: `Bearer ${githubToken}` },\n });\n if (!response.ok) return null;\n return response.json() as Promise<{ login: string }>;\n }\n );\n\n if (!isValid) {\n throw new Error('Token de GitHub inválido');\n }\n\n config.credentials.github.token = githubToken;\n config.credentials.github.username = isValid.login;\n logger.success(`Conectado como: ${isValid.login}`);\n\n const useOrg = await confirm({\n message: '¿Quieres usar una organización por defecto?',\n default: false,\n });\n\n if (useOrg) {\n config.defaults.githubOrg = await input({\n message: 'Nombre de la organización:',\n });\n }\n }\n\n // Supabase\n if (configureAll || options.onlySupabase) {\n logger.newLine();\n logger.title('Configuración de Supabase');\n logger.subtitle('Necesitas un Access Token de la Management API');\n logger.link('Crear token', 'https://supabase.com/dashboard/account/tokens');\n logger.newLine();\n\n const supabaseToken = await password({\n message: 'Supabase Access Token:',\n mask: '*',\n });\n\n // Obtener organizaciones\n const orgs = await withSpinner(\n 'Obteniendo organizaciones de Supabase...',\n async () => {\n const response = await fetch('https://api.supabase.com/v1/organizations', {\n headers: { Authorization: `Bearer ${supabaseToken}` },\n });\n if (!response.ok) return null;\n return response.json() as Promise<Array<{ id: string; name: string }>>;\n }\n );\n\n if (!orgs || orgs.length === 0) {\n throw new Error('Token de Supabase inválido o no tienes organizaciones');\n }\n\n config.credentials.supabase.accessToken = supabaseToken;\n\n if (orgs.length === 1) {\n config.credentials.supabase.organizationId = orgs[0].id;\n logger.success(`Usando organización: ${orgs[0].name}`);\n } else {\n const selectedOrg = await select({\n message: 'Selecciona la organización:',\n choices: orgs.map((org) => ({ name: org.name, value: org.id })),\n });\n config.credentials.supabase.organizationId = selectedOrg;\n }\n\n config.defaults.supabaseRegion = await select({\n message: 'Región por defecto:',\n choices: [\n { name: 'US East (Virginia)', value: 'us-east-1' },\n { name: 'US West (Oregon)', value: 'us-west-1' },\n { name: 'EU West (Ireland)', value: 'eu-west-1' },\n { name: 'AP Southeast (Singapore)', value: 'ap-southeast-1' },\n { name: 'AP Northeast (Tokyo)', value: 'ap-northeast-1' },\n { name: 'SA East (São Paulo)', value: 'sa-east-1' },\n ],\n default: 'us-east-1',\n });\n }\n\n // Jira\n if (configureAll || options.onlyJira) {\n logger.newLine();\n logger.title('Configuración de Jira');\n logger.subtitle('Necesitas un API Token de Atlassian');\n logger.link('Crear token', 'https://id.atlassian.com/manage-profile/security/api-tokens');\n logger.newLine();\n\n const jiraEmail = await input({\n message: 'Email de Atlassian:',\n });\n\n const jiraToken = await password({\n message: 'Jira API Token:',\n mask: '*',\n });\n\n const jiraDomain = await input({\n message: 'Dominio de Jira (ej: empresa.atlassian.net):',\n validate: (value) => {\n if (!value.includes('.atlassian.net')) {\n return 'El dominio debe terminar en .atlassian.net';\n }\n return true;\n },\n });\n\n // Validar credenciales\n const isValid = await withSpinner(\n 'Validando credenciales de Jira...',\n async () => {\n const auth = Buffer.from(`${jiraEmail}:${jiraToken}`).toString('base64');\n const response = await fetch(`https://${jiraDomain}/rest/api/3/myself`, {\n headers: { Authorization: `Basic ${auth}` },\n });\n return response.ok;\n }\n );\n\n if (!isValid) {\n throw new Error('Credenciales de Jira inválidas');\n }\n\n config.credentials.jira.email = jiraEmail;\n config.credentials.jira.apiToken = jiraToken;\n config.credentials.jira.domain = jiraDomain;\n logger.success('Credenciales de Jira válidas');\n }\n\n // Guardar configuración\n await saveConfig(config);\n logger.newLine();\n logger.success('Configuración guardada en ~/.lftrc');\n}\n","import chalk from 'chalk';\n\nexport const logger = {\n info: (message: string) => {\n console.log(chalk.blue('ℹ'), message);\n },\n\n success: (message: string) => {\n console.log(chalk.green('✔'), message);\n },\n\n warning: (message: string) => {\n console.log(chalk.yellow('⚠'), message);\n },\n\n error: (message: string) => {\n console.log(chalk.red('✖'), message);\n },\n\n step: (step: number, total: number, message: string) => {\n console.log(chalk.cyan(`[${step}/${total}]`), message);\n },\n\n newLine: () => {\n console.log();\n },\n\n divider: () => {\n console.log(chalk.gray('─'.repeat(50)));\n },\n\n title: (message: string) => {\n console.log(chalk.bold.white(message));\n },\n\n subtitle: (message: string) => {\n console.log(chalk.gray(message));\n },\n\n link: (label: string, url: string) => {\n console.log(` ${chalk.gray(label + ':')} ${chalk.cyan.underline(url)}`);\n },\n\n list: (items: string[]) => {\n items.forEach((item) => {\n console.log(chalk.gray(' •'), item);\n });\n },\n\n table: (rows: Array<{ label: string; value: string }>) => {\n const maxLabelLength = Math.max(...rows.map((r) => r.label.length));\n rows.forEach(({ label, value }) => {\n const paddedLabel = label.padEnd(maxLabelLength);\n console.log(` ${chalk.gray(paddedLabel)} ${value}`);\n });\n },\n};\n","import ora, { type Ora } from 'ora';\n\nexport function createSpinner(text: string): Ora {\n return ora({\n text,\n spinner: 'dots',\n });\n}\n\nexport async function withSpinner<T>(\n text: string,\n fn: () => Promise<T>,\n successText?: string\n): Promise<T> {\n const spinner = createSpinner(text).start();\n\n try {\n const result = await fn();\n spinner.succeed(successText || text);\n return result;\n } catch (error) {\n spinner.fail();\n throw error;\n }\n}\n","import { Octokit } from 'octokit';\nimport { withSpinner } from '../ui/spinner.js';\nimport type { LFTConfig } from '../config/index.js';\n\nexport async function createGitHubRepo(\n projectName: string,\n config: LFTConfig\n): Promise<string> {\n const octokit = new Octokit({ auth: config.credentials.github.token });\n const org = config.defaults.githubOrg;\n\n return withSpinner(\n 'Creando repositorio en GitHub...',\n async () => {\n let repo;\n\n if (org) {\n // Crear en organización\n repo = await octokit.rest.repos.createInOrg({\n org,\n name: projectName,\n private: true,\n auto_init: false,\n description: `Proyecto ${projectName} creado con create-lft-app`,\n });\n } else {\n // Crear en cuenta personal\n repo = await octokit.rest.repos.createForAuthenticatedUser({\n name: projectName,\n private: true,\n auto_init: false,\n description: `Proyecto ${projectName} creado con create-lft-app`,\n });\n }\n\n return repo.data.html_url;\n },\n `Repositorio creado: ${org || config.credentials.github.username}/${projectName}`\n );\n}\n\nexport async function validateGitHubToken(token: string): Promise<{ valid: boolean; username?: string }> {\n try {\n const octokit = new Octokit({ auth: token });\n const { data } = await octokit.rest.users.getAuthenticated();\n return { valid: true, username: data.login };\n } catch {\n return { valid: false };\n }\n}\n","import { withSpinner, createSpinner } from '../ui/spinner.js';\nimport type { LFTConfig } from '../config/index.js';\n\ninterface SupabaseProjectResponse {\n id: string;\n name: string;\n organization_id: string;\n region: string;\n status: string;\n}\n\ninterface SupabaseApiKey {\n name: string;\n api_key: string;\n}\n\nexport interface SupabaseProjectResult {\n url: string;\n anonKey: string;\n serviceKey: string;\n}\n\nconst SUPABASE_API_URL = 'https://api.supabase.com/v1';\n\nasync function waitForProjectReady(\n projectId: string,\n token: string,\n maxAttempts = 60\n): Promise<void> {\n const spinner = createSpinner('Provisionando base de datos (esto puede tomar ~2 minutos)...').start();\n\n for (let i = 0; i < maxAttempts; i++) {\n const response = await fetch(`${SUPABASE_API_URL}/projects/${projectId}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (response.ok) {\n const project = (await response.json()) as SupabaseProjectResponse;\n if (project.status === 'ACTIVE_HEALTHY') {\n spinner.succeed('Base de datos provisionada');\n return;\n }\n }\n\n // Esperar 5 segundos antes del siguiente intento\n await new Promise((resolve) => setTimeout(resolve, 5000));\n spinner.text = `Provisionando base de datos... (${Math.floor((i + 1) * 5 / 60)}min ${((i + 1) * 5) % 60}s)`;\n }\n\n spinner.fail('Timeout esperando a que el proyecto esté listo');\n throw new Error('Timeout: el proyecto de Supabase no se activó a tiempo');\n}\n\nasync function getProjectApiKeys(\n projectId: string,\n token: string\n): Promise<{ anonKey: string; serviceKey: string }> {\n const response = await fetch(`${SUPABASE_API_URL}/projects/${projectId}/api-keys`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (!response.ok) {\n throw new Error('No se pudieron obtener las API keys de Supabase');\n }\n\n const keys = (await response.json()) as SupabaseApiKey[];\n\n const anonKey = keys.find((k) => k.name === 'anon')?.api_key;\n const serviceKey = keys.find((k) => k.name === 'service_role')?.api_key;\n\n if (!anonKey || !serviceKey) {\n throw new Error('No se encontraron las API keys necesarias');\n }\n\n return { anonKey, serviceKey };\n}\n\nexport async function createSupabaseProject(\n projectName: string,\n config: LFTConfig\n): Promise<SupabaseProjectResult> {\n const token = config.credentials.supabase.accessToken;\n const orgId = config.credentials.supabase.organizationId;\n const region = config.defaults.supabaseRegion;\n\n // Generar una contraseña segura para la base de datos\n const dbPassword = generateSecurePassword();\n\n // Crear proyecto\n const project = await withSpinner(\n 'Creando proyecto en Supabase...',\n async () => {\n const response = await fetch(`${SUPABASE_API_URL}/projects`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n name: projectName,\n organization_id: orgId,\n region,\n plan: 'free',\n db_pass: dbPassword,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Error creando proyecto Supabase: ${error}`);\n }\n\n return response.json() as Promise<SupabaseProjectResponse>;\n }\n );\n\n // Esperar a que el proyecto esté listo\n await waitForProjectReady(project.id, token);\n\n // Obtener API keys\n const { anonKey, serviceKey } = await getProjectApiKeys(project.id, token);\n\n const projectUrl = `https://${project.id}.supabase.co`;\n\n return {\n url: projectUrl,\n anonKey,\n serviceKey,\n };\n}\n\nfunction generateSecurePassword(): string {\n const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';\n let password = '';\n for (let i = 0; i < 32; i++) {\n password += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return password;\n}\n","import fs from 'fs';\nimport path from 'path';\n\nexport interface ValidationResult {\n valid: boolean;\n error?: string;\n}\n\nexport function validateProjectName(name: string): ValidationResult {\n // Verificar que no esté vacío\n if (!name || name.trim() === '') {\n return { valid: false, error: 'El nombre del proyecto no puede estar vacío' };\n }\n\n // Verificar caracteres válidos (solo letras, números, guiones y guiones bajos)\n const validPattern = /^[a-zA-Z0-9_-]+$/;\n if (!validPattern.test(name)) {\n return {\n valid: false,\n error: 'El nombre solo puede contener letras, números, guiones (-) y guiones bajos (_)',\n };\n }\n\n // Verificar que no empiece con guión o número\n if (/^[-_0-9]/.test(name)) {\n return {\n valid: false,\n error: 'El nombre debe empezar con una letra',\n };\n }\n\n // Verificar longitud\n if (name.length < 2) {\n return { valid: false, error: 'El nombre debe tener al menos 2 caracteres' };\n }\n\n if (name.length > 50) {\n return { valid: false, error: 'El nombre no puede tener más de 50 caracteres' };\n }\n\n // Verificar que el directorio no exista\n const projectPath = path.resolve(process.cwd(), name);\n if (fs.existsSync(projectPath)) {\n return { valid: false, error: `El directorio \"${name}\" ya existe` };\n }\n\n return { valid: true };\n}\n\nexport function generateJiraKey(projectName: string): string {\n // Generar key de Jira: máximo 10 caracteres, solo mayúsculas\n const cleaned = projectName\n .replace(/[^a-zA-Z0-9]/g, '')\n .toUpperCase()\n .slice(0, 10);\n\n return cleaned || 'PROJ';\n}\n","import { withSpinner } from '../ui/spinner.js';\nimport { generateJiraKey } from '../utils/validation.js';\nimport type { LFTConfig } from '../config/index.js';\n\ninterface JiraProjectResponse {\n id: string;\n key: string;\n name: string;\n self: string;\n}\n\nexport async function createJiraProject(\n projectName: string,\n config: LFTConfig\n): Promise<string> {\n const { email, apiToken, domain } = config.credentials.jira;\n const auth = Buffer.from(`${email}:${apiToken}`).toString('base64');\n const projectKey = generateJiraKey(projectName);\n\n return withSpinner(\n 'Creando proyecto en Jira...',\n async () => {\n // Primero obtener el leadAccountId del usuario actual\n const meResponse = await fetch(`https://${domain}/rest/api/3/myself`, {\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!meResponse.ok) {\n throw new Error('No se pudo obtener información del usuario de Jira');\n }\n\n const me = (await meResponse.json()) as { accountId: string };\n\n // Crear el proyecto\n const response = await fetch(`https://${domain}/rest/api/3/project`, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n key: projectKey,\n name: projectName,\n projectTypeKey: 'software',\n projectTemplateKey: 'com.pyxis.greenhopper.jira:gh-simplified-agility-scrum',\n leadAccountId: me.accountId,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n\n // Si el key ya existe, intentar con un sufijo numérico\n if (error.includes('project key')) {\n const newKey = `${projectKey}${Date.now().toString().slice(-4)}`;\n const retryResponse = await fetch(`https://${domain}/rest/api/3/project`, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n key: newKey,\n name: projectName,\n projectTypeKey: 'software',\n projectTemplateKey: 'com.pyxis.greenhopper.jira:gh-simplified-agility-scrum',\n leadAccountId: me.accountId,\n }),\n });\n\n if (!retryResponse.ok) {\n throw new Error(`Error creando proyecto Jira: ${await retryResponse.text()}`);\n }\n\n const project = (await retryResponse.json()) as JiraProjectResponse;\n return `https://${domain}/browse/${project.key}`;\n }\n\n throw new Error(`Error creando proyecto Jira: ${error}`);\n }\n\n const project = (await response.json()) as JiraProjectResponse;\n return `https://${domain}/browse/${project.key}`;\n },\n `Proyecto Jira creado: ${projectKey}`\n );\n}\n\nexport async function validateJiraCredentials(\n email: string,\n apiToken: string,\n domain: string\n): Promise<boolean> {\n try {\n const auth = Buffer.from(`${email}:${apiToken}`).toString('base64');\n const response = await fetch(`https://${domain}/rest/api/3/myself`, {\n headers: { Authorization: `Basic ${auth}` },\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n","import { execa } from 'execa';\nimport { withSpinner } from '../ui/spinner.js';\n\nexport async function scaffoldNextJs(\n projectName: string,\n projectPath: string\n): Promise<void> {\n await withSpinner(\n 'Inicializando proyecto Next.js...',\n async () => {\n await execa('npx', [\n 'create-next-app@latest',\n projectName,\n '--typescript',\n '--tailwind',\n '--eslint',\n '--app',\n '--turbopack',\n '--src-dir',\n '--import-alias', '@/*',\n '--use-npm',\n ], {\n cwd: process.cwd(),\n stdio: 'pipe',\n });\n },\n 'Proyecto Next.js inicializado'\n );\n}\n","import { cp, mkdir, readFile, writeFile } from 'fs/promises';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { withSpinner } from '../ui/spinner.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nexport async function copyTemplate(projectPath: string): Promise<void> {\n await withSpinner(\n 'Copiando template LFT...',\n async () => {\n // Ruta a los templates (relativa al dist)\n const templatesDir = path.join(__dirname, '..', '..', 'templates');\n const srcDir = path.join(projectPath, 'src');\n\n // Copiar componentes UI\n await cp(\n path.join(templatesDir, 'components', 'ui'),\n path.join(srcDir, 'components', 'ui'),\n { recursive: true }\n );\n\n // Copiar componentes de layout\n await cp(\n path.join(templatesDir, 'components', 'layout'),\n path.join(srcDir, 'components', 'layout'),\n { recursive: true }\n );\n\n // Copiar componentes de dashboard\n await cp(\n path.join(templatesDir, 'components', 'dashboard'),\n path.join(srcDir, 'components', 'dashboard'),\n { recursive: true }\n );\n\n // Copiar lib/utils.ts\n await mkdir(path.join(srcDir, 'lib'), { recursive: true });\n await cp(\n path.join(templatesDir, 'lib', 'utils.ts'),\n path.join(srcDir, 'lib', 'utils.ts')\n );\n\n // Copiar hooks\n await mkdir(path.join(srcDir, 'hooks'), { recursive: true });\n await cp(\n path.join(templatesDir, 'hooks'),\n path.join(srcDir, 'hooks'),\n { recursive: true }\n );\n\n // Copiar páginas de app\n await cp(\n path.join(templatesDir, 'app', 'layout.tsx'),\n path.join(srcDir, 'app', 'layout.tsx')\n );\n\n await cp(\n path.join(templatesDir, 'app', 'page.tsx'),\n path.join(srcDir, 'app', 'page.tsx')\n );\n\n // Copiar dashboard page\n await mkdir(path.join(srcDir, 'app', 'dashboard'), { recursive: true });\n await cp(\n path.join(templatesDir, 'app', 'dashboard', 'page.tsx'),\n path.join(srcDir, 'app', 'dashboard', 'page.tsx')\n );\n\n // Copiar auth/login page\n await mkdir(path.join(srcDir, 'app', 'auth', 'login'), { recursive: true });\n await cp(\n path.join(templatesDir, 'app', 'auth', 'login', 'page.tsx'),\n path.join(srcDir, 'app', 'auth', 'login', 'page.tsx')\n );\n\n // Merge globals.css\n await mergeGlobalStyles(projectPath, templatesDir);\n },\n 'Template LFT copiado (47 componentes + páginas)'\n );\n}\n\nasync function mergeGlobalStyles(\n projectPath: string,\n templatesDir: string\n): Promise<void> {\n const templateCssPath = path.join(templatesDir, 'app', 'globals.css');\n const projectCssPath = path.join(projectPath, 'src', 'app', 'globals.css');\n\n try {\n const templateCss = await readFile(templateCssPath, 'utf-8');\n const existingCss = await readFile(projectCssPath, 'utf-8');\n\n // Mantener las directivas de Tailwind del proyecto y agregar custom CSS\n const merged = existingCss + '\\n\\n/* LFT Custom Styles */\\n' + templateCss;\n await writeFile(projectCssPath, merged);\n } catch {\n // Si no existe el template CSS, no hacer nada\n }\n}\n","import { execa } from 'execa';\nimport { withSpinner } from '../ui/spinner.js';\n\nconst TEMPLATE_DEPENDENCIES = [\n // Radix UI primitives\n '@radix-ui/react-accordion',\n '@radix-ui/react-alert-dialog',\n '@radix-ui/react-avatar',\n '@radix-ui/react-checkbox',\n '@radix-ui/react-collapsible',\n '@radix-ui/react-dialog',\n '@radix-ui/react-dropdown-menu',\n '@radix-ui/react-label',\n '@radix-ui/react-popover',\n '@radix-ui/react-progress',\n '@radix-ui/react-radio-group',\n '@radix-ui/react-scroll-area',\n '@radix-ui/react-select',\n '@radix-ui/react-separator',\n '@radix-ui/react-slider',\n '@radix-ui/react-slot',\n '@radix-ui/react-switch',\n '@radix-ui/react-tabs',\n '@radix-ui/react-tooltip',\n\n // UI Utilities\n 'class-variance-authority',\n 'clsx',\n 'tailwind-merge',\n\n // Icons\n 'lucide-react',\n\n // Form handling\n 'react-hook-form',\n '@hookform/resolvers',\n\n // Command menu\n 'cmdk',\n\n // Date picker\n 'react-day-picker',\n 'date-fns',\n\n // Toast notifications\n 'sonner',\n\n // Validation\n 'zod',\n\n // Supabase client\n '@supabase/supabase-js',\n '@supabase/ssr',\n];\n\nconst TEMPLATE_DEV_DEPENDENCIES = [\n 'tailwindcss-animate',\n];\n\nexport async function installDependencies(projectPath: string): Promise<void> {\n await withSpinner(\n `Instalando dependencias (${TEMPLATE_DEPENDENCIES.length} paquetes)...`,\n async () => {\n // Instalar dependencias de producción\n await execa('npm', ['install', ...TEMPLATE_DEPENDENCIES], {\n cwd: projectPath,\n stdio: 'pipe',\n });\n\n // Instalar dependencias de desarrollo\n await execa('npm', ['install', '-D', ...TEMPLATE_DEV_DEPENDENCIES], {\n cwd: projectPath,\n stdio: 'pipe',\n });\n },\n 'Dependencias instaladas'\n );\n}\n","import { writeFile, readFile, appendFile } from 'fs/promises';\nimport path from 'path';\nimport { withSpinner } from '../ui/spinner.js';\n\ninterface SupabaseKeys {\n url: string;\n anonKey: string;\n serviceKey: string;\n}\n\nexport async function createEnvFile(\n projectPath: string,\n supabaseKeys: SupabaseKeys\n): Promise<void> {\n await withSpinner(\n 'Creando archivo .env.local...',\n async () => {\n const envContent = `# Supabase\nNEXT_PUBLIC_SUPABASE_URL=${supabaseKeys.url}\nNEXT_PUBLIC_SUPABASE_ANON_KEY=${supabaseKeys.anonKey}\nSUPABASE_SERVICE_ROLE_KEY=${supabaseKeys.serviceKey}\n`;\n\n await writeFile(\n path.join(projectPath, '.env.local'),\n envContent\n );\n\n // Asegurar que .env.local esté en .gitignore\n const gitignorePath = path.join(projectPath, '.gitignore');\n try {\n const gitignore = await readFile(gitignorePath, 'utf-8');\n if (!gitignore.includes('.env.local')) {\n await appendFile(gitignorePath, '\\n# Environment variables\\n.env.local\\n.env*.local\\n');\n }\n } catch {\n // Si no existe .gitignore, crearlo\n await writeFile(gitignorePath, '# Environment variables\\n.env.local\\n.env*.local\\n');\n }\n },\n 'Archivo .env.local creado con credenciales de Supabase'\n );\n}\n","import { simpleGit } from 'simple-git';\nimport { withSpinner } from '../ui/spinner.js';\n\nexport async function setupGit(\n projectPath: string,\n remoteUrl: string\n): Promise<void> {\n const git = simpleGit(projectPath);\n\n await withSpinner(\n 'Configurando Git...',\n async () => {\n // Verificar si ya es un repo git\n const isRepo = await git.checkIsRepo();\n\n if (!isRepo) {\n await git.init();\n }\n\n // Agregar remote\n try {\n await git.addRemote('origin', remoteUrl);\n } catch {\n // Remote ya existe, actualizarlo\n await git.remote(['set-url', 'origin', remoteUrl]);\n }\n\n // Stage todos los archivos\n await git.add('.');\n\n // Commit inicial\n await git.commit('Initial commit - created with create-lft-app', {\n '--author': 'create-lft-app <noreply@lft.dev>',\n });\n\n // Renombrar branch a main si es necesario\n try {\n await git.branch(['-M', 'main']);\n } catch {\n // Ya está en main\n }\n\n // Push\n await git.push(['--set-upstream', 'origin', 'main']);\n },\n 'Git configurado y código pusheado'\n );\n}\n","import boxen from 'boxen';\nimport chalk from 'chalk';\n\nexport function showBanner(): void {\n const title = chalk.bold.cyan('create-lft-app');\n const version = chalk.gray('v1.0.0');\n const description = chalk.white('Scaffolding para proyectos LFT');\n\n const banner = boxen(`${title} ${version}\\n${description}`, {\n padding: 1,\n margin: 1,\n borderStyle: 'round',\n borderColor: 'cyan',\n });\n\n console.log(banner);\n}\n\nexport function showSuccessBanner(projectName: string, urls: {\n github?: string;\n supabase?: string;\n jira?: string;\n}): void {\n const lines = [\n chalk.green.bold(`Proyecto \"${projectName}\" creado exitosamente!`),\n '',\n chalk.white('Directorio:') + ` ./${projectName}`,\n '',\n ];\n\n if (urls.github || urls.supabase || urls.jira) {\n lines.push(chalk.white('Enlaces:'));\n if (urls.github) {\n lines.push(` ${chalk.gray('GitHub:')} ${chalk.cyan(urls.github)}`);\n }\n if (urls.supabase) {\n lines.push(` ${chalk.gray('Supabase:')} ${chalk.cyan(urls.supabase)}`);\n }\n if (urls.jira) {\n lines.push(` ${chalk.gray('Jira:')} ${chalk.cyan(urls.jira)}`);\n }\n lines.push('');\n }\n\n lines.push(chalk.white('Siguiente pasos:'));\n lines.push(` ${chalk.cyan('cd')} ${projectName}`);\n lines.push(` ${chalk.cyan('npm run dev')}`);\n\n const banner = boxen(lines.join('\\n'), {\n padding: 1,\n margin: 1,\n borderStyle: 'round',\n borderColor: 'green',\n });\n\n console.log(banner);\n}\n"],"mappings":"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","names":["path","confirm","password","fs","path","project","path","__filename","__dirname","path","execa","execa","writeFile","readFile","path","writeFile","path","readFile","chalk","chalk","path","confirm"]}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@create-lft-app/cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI para crear proyectos LFT con Next.js, GitHub, Supabase y Jira",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-lft-app": "./dist/bin/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "templates"
12
+ ],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/Test-Boiler/create-lft-app.git"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "dev": "tsup --watch",
23
+ "start": "node dist/bin/cli.js",
24
+ "typecheck": "tsc --noEmit",
25
+ "prepublishOnly": "npm run build"
26
+ },
27
+ "dependencies": {
28
+ "commander": "^12.1.0",
29
+ "@inquirer/prompts": "^7.0.0",
30
+ "ora": "^8.1.0",
31
+ "chalk": "^5.3.0",
32
+ "octokit": "^4.0.2",
33
+ "jira.js": "^4.0.0",
34
+ "fs-extra": "^11.2.0",
35
+ "simple-git": "^3.27.0",
36
+ "execa": "^9.4.0",
37
+ "zod": "^3.23.8",
38
+ "node-machine-id": "^1.1.12",
39
+ "boxen": "^8.0.1"
40
+ },
41
+ "devDependencies": {
42
+ "@types/fs-extra": "^11.0.4",
43
+ "@types/node": "^22.7.0",
44
+ "tsup": "^8.3.0",
45
+ "typescript": "^5.6.2"
46
+ },
47
+ "engines": {
48
+ "node": ">=18.0.0"
49
+ },
50
+ "keywords": [
51
+ "cli",
52
+ "nextjs",
53
+ "supabase",
54
+ "github",
55
+ "jira",
56
+ "boilerplate"
57
+ ],
58
+ "author": "LFT",
59
+ "license": "MIT"
60
+ }
@@ -0,0 +1,153 @@
1
+ "use client"
2
+
3
+ import { useState } from "react"
4
+ import { useRouter } from "next/navigation"
5
+ import Link from "next/link"
6
+ import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/components/ui/card"
7
+ import { Input } from "@/components/ui/input"
8
+ import { Button } from "@/components/ui/button"
9
+ import { Label } from "@/components/ui/label"
10
+ import { Separator } from "@/components/ui/separator"
11
+ import { Spinner } from "@/components/ui/spinner"
12
+
13
+ export default function LoginPage() {
14
+ const router = useRouter()
15
+ const [isLoading, setIsLoading] = useState(false)
16
+ const [error, setError] = useState<string | null>(null)
17
+
18
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
19
+ e.preventDefault()
20
+ setIsLoading(true)
21
+ setError(null)
22
+
23
+ const formData = new FormData(e.currentTarget)
24
+ const email = formData.get("email") as string
25
+ const password = formData.get("password") as string
26
+
27
+ try {
28
+ // TODO: Implementar autenticación con Supabase
29
+ // const { error } = await supabase.auth.signInWithPassword({ email, password })
30
+
31
+ // Simulación de login exitoso
32
+ await new Promise((resolve) => setTimeout(resolve, 1000))
33
+
34
+ router.push("/dashboard")
35
+ } catch (err) {
36
+ setError("Error al iniciar sesión. Verifica tus credenciales.")
37
+ } finally {
38
+ setIsLoading(false)
39
+ }
40
+ }
41
+
42
+ return (
43
+ <div className="min-h-screen flex items-center justify-center bg-background p-4">
44
+ <Card className="w-full max-w-md">
45
+ <CardHeader className="space-y-1 text-center">
46
+ <CardTitle className="text-2xl font-bold">Iniciar Sesión</CardTitle>
47
+ <CardDescription>
48
+ Ingresa tus credenciales para acceder al panel
49
+ </CardDescription>
50
+ </CardHeader>
51
+ <CardContent>
52
+ <form onSubmit={handleSubmit} className="space-y-4">
53
+ {error && (
54
+ <div className="p-3 text-sm text-destructive bg-destructive/10 border border-destructive/20 rounded-md">
55
+ {error}
56
+ </div>
57
+ )}
58
+
59
+ <div className="space-y-2">
60
+ <Label htmlFor="email">Email</Label>
61
+ <Input
62
+ id="email"
63
+ name="email"
64
+ type="email"
65
+ placeholder="tu@email.com"
66
+ required
67
+ disabled={isLoading}
68
+ autoComplete="email"
69
+ />
70
+ </div>
71
+
72
+ <div className="space-y-2">
73
+ <div className="flex items-center justify-between">
74
+ <Label htmlFor="password">Contraseña</Label>
75
+ <Link
76
+ href="/auth/forgot-password"
77
+ className="text-sm text-primary hover:underline"
78
+ >
79
+ ¿Olvidaste tu contraseña?
80
+ </Link>
81
+ </div>
82
+ <Input
83
+ id="password"
84
+ name="password"
85
+ type="password"
86
+ placeholder="••••••••"
87
+ required
88
+ disabled={isLoading}
89
+ autoComplete="current-password"
90
+ />
91
+ </div>
92
+
93
+ <Button type="submit" className="w-full" disabled={isLoading}>
94
+ {isLoading ? (
95
+ <>
96
+ <Spinner className="mr-2 h-4 w-4" />
97
+ Iniciando sesión...
98
+ </>
99
+ ) : (
100
+ "Iniciar Sesión"
101
+ )}
102
+ </Button>
103
+ </form>
104
+
105
+ <div className="relative my-6">
106
+ <Separator />
107
+ <span className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-background px-2 text-xs text-muted-foreground">
108
+ O continuar con
109
+ </span>
110
+ </div>
111
+
112
+ <div className="grid grid-cols-2 gap-4">
113
+ <Button variant="outline" disabled={isLoading}>
114
+ <svg className="mr-2 h-4 w-4" viewBox="0 0 24 24">
115
+ <path
116
+ fill="currentColor"
117
+ d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
118
+ />
119
+ <path
120
+ fill="currentColor"
121
+ d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
122
+ />
123
+ <path
124
+ fill="currentColor"
125
+ d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
126
+ />
127
+ <path
128
+ fill="currentColor"
129
+ d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
130
+ />
131
+ </svg>
132
+ Google
133
+ </Button>
134
+ <Button variant="outline" disabled={isLoading}>
135
+ <svg className="mr-2 h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
136
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
137
+ </svg>
138
+ GitHub
139
+ </Button>
140
+ </div>
141
+ </CardContent>
142
+ <CardFooter className="flex justify-center">
143
+ <p className="text-sm text-muted-foreground">
144
+ ¿No tienes una cuenta?{" "}
145
+ <Link href="/auth/register" className="text-primary hover:underline">
146
+ Regístrate
147
+ </Link>
148
+ </p>
149
+ </CardFooter>
150
+ </Card>
151
+ </div>
152
+ )
153
+ }
@@ -0,0 +1,102 @@
1
+ import { Header } from "@/components/layout/header"
2
+ import { Widget, WidgetGrid } from "@/components/dashboard/widget"
3
+ import {
4
+ Users,
5
+ DollarSign,
6
+ Activity,
7
+ TrendingUp,
8
+ Building2,
9
+ FileText,
10
+ AlertCircle,
11
+ CheckCircle,
12
+ } from "lucide-react"
13
+
14
+ export default function DashboardPage() {
15
+ return (
16
+ <>
17
+ <Header title="Dashboard" />
18
+ <main className="flex-1 p-6">
19
+ <div className="space-y-6">
20
+ <div>
21
+ <h2 className="text-2xl font-bold tracking-tight">
22
+ Bienvenido de vuelta
23
+ </h2>
24
+ <p className="text-muted-foreground">
25
+ Aquí está el resumen de tu actividad
26
+ </p>
27
+ </div>
28
+
29
+ <WidgetGrid>
30
+ <Widget
31
+ title="Usuarios Activos"
32
+ icon={<Users className="h-4 w-4" />}
33
+ value="1,234"
34
+ subValue="usuarios registrados"
35
+ trend={{ value: 12.5, positive: true }}
36
+ href="/admin/users"
37
+ />
38
+ <Widget
39
+ title="Ingresos"
40
+ icon={<DollarSign className="h-4 w-4" />}
41
+ value="$45,231"
42
+ subValue="este mes"
43
+ trend={{ value: 8.2, positive: true }}
44
+ variant="success"
45
+ />
46
+ <Widget
47
+ title="Organizaciones"
48
+ icon={<Building2 className="h-4 w-4" />}
49
+ value="89"
50
+ subValue="organizaciones activas"
51
+ trend={{ value: 3.1, positive: true }}
52
+ href="/admin/organizations"
53
+ />
54
+ <Widget
55
+ title="Actividad"
56
+ icon={<Activity className="h-4 w-4" />}
57
+ value="2,345"
58
+ subValue="eventos hoy"
59
+ trend={{ value: 5.4, positive: true }}
60
+ />
61
+ </WidgetGrid>
62
+
63
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
64
+ <WidgetGrid>
65
+ <Widget
66
+ title="Documentos Pendientes"
67
+ icon={<FileText className="h-4 w-4" />}
68
+ value="23"
69
+ subValue="requieren revisión"
70
+ variant="warning"
71
+ />
72
+ <Widget
73
+ title="Alertas"
74
+ icon={<AlertCircle className="h-4 w-4" />}
75
+ value="5"
76
+ subValue="alertas activas"
77
+ variant="danger"
78
+ />
79
+ </WidgetGrid>
80
+
81
+ <WidgetGrid>
82
+ <Widget
83
+ title="Tareas Completadas"
84
+ icon={<CheckCircle className="h-4 w-4" />}
85
+ value="156"
86
+ subValue="esta semana"
87
+ variant="success"
88
+ />
89
+ <Widget
90
+ title="Crecimiento"
91
+ icon={<TrendingUp className="h-4 w-4" />}
92
+ value="+24%"
93
+ subValue="vs mes anterior"
94
+ trend={{ value: 24, positive: true }}
95
+ />
96
+ </WidgetGrid>
97
+ </div>
98
+ </div>
99
+ </main>
100
+ </>
101
+ )
102
+ }
@@ -0,0 +1,68 @@
1
+ /* LFT Custom Styles */
2
+
3
+ /* Sidebar custom colors */
4
+ :root {
5
+ --sidebar-background: 0 0% 98%;
6
+ --sidebar-foreground: 240 5.3% 26.1%;
7
+ --sidebar-primary: 240 5.9% 10%;
8
+ --sidebar-primary-foreground: 0 0% 98%;
9
+ --sidebar-accent: 240 4.8% 95.9%;
10
+ --sidebar-accent-foreground: 240 5.9% 10%;
11
+ --sidebar-border: 220 13% 91%;
12
+ --sidebar-ring: 217.2 91.2% 59.8%;
13
+ }
14
+
15
+ .dark {
16
+ --sidebar-background: 240 5.9% 10%;
17
+ --sidebar-foreground: 240 4.8% 95.9%;
18
+ --sidebar-primary: 0 0% 98%;
19
+ --sidebar-primary-foreground: 240 5.9% 10%;
20
+ --sidebar-accent: 240 3.7% 15.9%;
21
+ --sidebar-accent-foreground: 240 4.8% 95.9%;
22
+ --sidebar-border: 240 3.7% 15.9%;
23
+ --sidebar-ring: 217.2 91.2% 59.8%;
24
+ }
25
+
26
+ /* Scrollbar styling */
27
+ ::-webkit-scrollbar {
28
+ width: 8px;
29
+ height: 8px;
30
+ }
31
+
32
+ ::-webkit-scrollbar-track {
33
+ background: transparent;
34
+ }
35
+
36
+ ::-webkit-scrollbar-thumb {
37
+ background: hsl(var(--muted-foreground) / 0.3);
38
+ border-radius: 4px;
39
+ }
40
+
41
+ ::-webkit-scrollbar-thumb:hover {
42
+ background: hsl(var(--muted-foreground) / 0.5);
43
+ }
44
+
45
+ /* Focus visible improvements */
46
+ *:focus-visible {
47
+ outline: 2px solid hsl(var(--ring));
48
+ outline-offset: 2px;
49
+ }
50
+
51
+ /* Smooth transitions */
52
+ * {
53
+ transition-property: background-color, border-color, color, fill, stroke;
54
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
55
+ transition-duration: 150ms;
56
+ }
57
+
58
+ /* Widget animations */
59
+ .widget-enter {
60
+ opacity: 0;
61
+ transform: translateY(10px);
62
+ }
63
+
64
+ .widget-enter-active {
65
+ opacity: 1;
66
+ transform: translateY(0);
67
+ transition: opacity 300ms, transform 300ms;
68
+ }
@@ -0,0 +1,40 @@
1
+ import type { Metadata } from "next"
2
+ import { Inter } from "next/font/google"
3
+ import "./globals.css"
4
+ import { SidebarProvider, SidebarInset } from "@/components/ui/sidebar"
5
+ import { AdminSidebar } from "@/components/layout/admin-sidebar"
6
+ import { Toaster } from "@/components/ui/sonner"
7
+
8
+ const inter = Inter({ subsets: ["latin"] })
9
+
10
+ export const metadata: Metadata = {
11
+ title: "Dashboard - LFT App",
12
+ description: "Panel de administración creado con create-lft-app",
13
+ }
14
+
15
+ export default function RootLayout({
16
+ children,
17
+ }: {
18
+ children: React.ReactNode
19
+ }) {
20
+ // Usuario de ejemplo - en producción esto vendría de Supabase Auth
21
+ const user = {
22
+ email: "admin@ejemplo.com",
23
+ full_name: "Admin Usuario",
24
+ role: "Super Admin",
25
+ }
26
+
27
+ return (
28
+ <html lang="es" suppressHydrationWarning>
29
+ <body className={inter.className}>
30
+ <SidebarProvider>
31
+ <AdminSidebar user={user} />
32
+ <SidebarInset>
33
+ {children}
34
+ </SidebarInset>
35
+ </SidebarProvider>
36
+ <Toaster />
37
+ </body>
38
+ </html>
39
+ )
40
+ }
@@ -0,0 +1,5 @@
1
+ import { redirect } from "next/navigation"
2
+
3
+ export default function Home() {
4
+ redirect("/dashboard")
5
+ }