create-supa-kit 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 (50) hide show
  1. package/README.md +100 -0
  2. package/bin/index.js +163 -0
  3. package/package.json +35 -0
  4. package/templates/react-supabase/.env.example +5 -0
  5. package/templates/react-supabase/README.md +64 -0
  6. package/templates/react-supabase/index.html +12 -0
  7. package/templates/react-supabase/package.json +20 -0
  8. package/templates/react-supabase/src/App.jsx +54 -0
  9. package/templates/react-supabase/src/components/Dashboard.jsx +117 -0
  10. package/templates/react-supabase/src/components/Login.jsx +181 -0
  11. package/templates/react-supabase/src/index.css +17 -0
  12. package/templates/react-supabase/src/lib/supabaseClient.js +19 -0
  13. package/templates/react-supabase/src/main.jsx +10 -0
  14. package/templates/react-supabase/vite.config.js +6 -0
  15. package/templates/react-supabase-tailwind/.env.example +5 -0
  16. package/templates/react-supabase-tailwind/index.html +12 -0
  17. package/templates/react-supabase-tailwind/package.json +23 -0
  18. package/templates/react-supabase-tailwind/postcss.config.js +6 -0
  19. package/templates/react-supabase-tailwind/src/App.jsx +32 -0
  20. package/templates/react-supabase-tailwind/src/components/Dashboard.jsx +45 -0
  21. package/templates/react-supabase-tailwind/src/components/Login.jsx +94 -0
  22. package/templates/react-supabase-tailwind/src/index.css +3 -0
  23. package/templates/react-supabase-tailwind/src/lib/supabaseClient.js +13 -0
  24. package/templates/react-supabase-tailwind/src/main.jsx +10 -0
  25. package/templates/react-supabase-tailwind/tailwind.config.js +8 -0
  26. package/templates/react-supabase-tailwind/vite.config.js +6 -0
  27. package/templates/react-supabase-ts/.env.example +5 -0
  28. package/templates/react-supabase-ts/index.html +12 -0
  29. package/templates/react-supabase-ts/package.json +23 -0
  30. package/templates/react-supabase-ts/src/App.tsx +33 -0
  31. package/templates/react-supabase-ts/src/components/Dashboard.tsx +56 -0
  32. package/templates/react-supabase-ts/src/components/Login.tsx +104 -0
  33. package/templates/react-supabase-ts/src/index.css +8 -0
  34. package/templates/react-supabase-ts/src/lib/supabaseClient.ts +13 -0
  35. package/templates/react-supabase-ts/src/main.tsx +10 -0
  36. package/templates/react-supabase-ts/tsconfig.json +20 -0
  37. package/templates/react-supabase-ts/vite.config.ts +6 -0
  38. package/templates/react-supabase-ts-tailwind/.env.example +5 -0
  39. package/templates/react-supabase-ts-tailwind/index.html +12 -0
  40. package/templates/react-supabase-ts-tailwind/package.json +26 -0
  41. package/templates/react-supabase-ts-tailwind/postcss.config.js +6 -0
  42. package/templates/react-supabase-ts-tailwind/src/App.tsx +33 -0
  43. package/templates/react-supabase-ts-tailwind/src/components/Dashboard.tsx +50 -0
  44. package/templates/react-supabase-ts-tailwind/src/components/Login.tsx +94 -0
  45. package/templates/react-supabase-ts-tailwind/src/index.css +3 -0
  46. package/templates/react-supabase-ts-tailwind/src/lib/supabaseClient.ts +13 -0
  47. package/templates/react-supabase-ts-tailwind/src/main.tsx +10 -0
  48. package/templates/react-supabase-ts-tailwind/tailwind.config.js +8 -0
  49. package/templates/react-supabase-ts-tailwind/tsconfig.json +20 -0
  50. package/templates/react-supabase-ts-tailwind/vite.config.ts +6 -0
@@ -0,0 +1,104 @@
1
+ import { useState, FormEvent } from 'react';
2
+ import { supabase } from '../lib/supabaseClient';
3
+
4
+ export default function Login() {
5
+ const [email, setEmail] = useState('');
6
+ const [password, setPassword] = useState('');
7
+ const [isSignUp, setIsSignUp] = useState(false);
8
+ const [loading, setLoading] = useState(false);
9
+ const [error, setError] = useState('');
10
+ const [message, setMessage] = useState('');
11
+
12
+ async function handleSubmit(e: FormEvent<HTMLFormElement>) {
13
+ e.preventDefault();
14
+ setLoading(true);
15
+ setError('');
16
+ setMessage('');
17
+
18
+ try {
19
+ if (isSignUp) {
20
+ const { error } = await supabase.auth.signUp({ email, password });
21
+ if (error) throw error;
22
+ setMessage('✅ Revisa tu email para confirmar tu cuenta.');
23
+ } else {
24
+ const { error } = await supabase.auth.signInWithPassword({ email, password });
25
+ if (error) throw error;
26
+ }
27
+ } catch (err: unknown) {
28
+ setError(err instanceof Error ? err.message : 'Error desconocido');
29
+ } finally {
30
+ setLoading(false);
31
+ }
32
+ }
33
+
34
+ return (
35
+ <div style={styles.wrapper}>
36
+ <div style={styles.card}>
37
+ <h1 style={styles.title}>
38
+ {isSignUp ? 'Crear cuenta' : 'Iniciar sesión'}
39
+ </h1>
40
+
41
+ <form onSubmit={handleSubmit} style={styles.form}>
42
+ <label style={styles.label}>
43
+ Email
44
+ <input
45
+ style={styles.input}
46
+ type="email"
47
+ value={email}
48
+ onChange={(e) => setEmail(e.target.value)}
49
+ placeholder="tu@email.com"
50
+ required
51
+ autoComplete="email"
52
+ />
53
+ </label>
54
+
55
+ <label style={styles.label}>
56
+ Contraseña
57
+ <input
58
+ style={styles.input}
59
+ type="password"
60
+ value={password}
61
+ onChange={(e) => setPassword(e.target.value)}
62
+ placeholder="••••••••"
63
+ required
64
+ minLength={6}
65
+ autoComplete={isSignUp ? 'new-password' : 'current-password'}
66
+ />
67
+ </label>
68
+
69
+ {error && <p style={styles.error}>{error}</p>}
70
+ {message && <p style={styles.success}>{message}</p>}
71
+
72
+ <button style={styles.button} type="submit" disabled={loading}>
73
+ {loading ? 'Procesando...' : isSignUp ? 'Registrarse' : 'Entrar'}
74
+ </button>
75
+ </form>
76
+
77
+ <p style={styles.toggle}>
78
+ {isSignUp ? '¿Ya tienes cuenta?' : '¿No tienes cuenta?'}{' '}
79
+ <button
80
+ style={styles.link}
81
+ type="button"
82
+ onClick={() => { setIsSignUp(!isSignUp); setError(''); setMessage(''); }}
83
+ >
84
+ {isSignUp ? 'Inicia sesión' : 'Regístrate'}
85
+ </button>
86
+ </p>
87
+ </div>
88
+ </div>
89
+ );
90
+ }
91
+
92
+ const styles: Record<string, React.CSSProperties> = {
93
+ wrapper: { display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh', padding: '1rem' },
94
+ card: { background: '#fff', borderRadius: '12px', padding: '2rem', width: '100%', maxWidth: '400px', boxShadow: '0 4px 24px rgba(0,0,0,0.08)' },
95
+ title: { fontSize: '1.5rem', fontWeight: 700, marginBottom: '1.5rem', textAlign: 'center' },
96
+ form: { display: 'flex', flexDirection: 'column', gap: '1rem' },
97
+ label: { display: 'flex', flexDirection: 'column', gap: '4px', fontSize: '0.875rem', fontWeight: 500 },
98
+ input: { padding: '0.625rem 0.75rem', borderRadius: '8px', border: '1px solid #cbd5e1', fontSize: '1rem', outline: 'none' },
99
+ button: { marginTop: '0.5rem', padding: '0.75rem', borderRadius: '8px', background: '#3ecf8e', color: '#fff', fontWeight: 600, fontSize: '1rem', border: 'none' },
100
+ error: { color: '#ef4444', fontSize: '0.875rem' },
101
+ success: { color: '#22c55e', fontSize: '0.875rem' },
102
+ toggle: { marginTop: '1.25rem', textAlign: 'center', fontSize: '0.875rem', color: '#64748b' },
103
+ link: { background: 'none', border: 'none', color: '#3ecf8e', fontWeight: 600, fontSize: '0.875rem', textDecoration: 'underline' },
104
+ };
@@ -0,0 +1,8 @@
1
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
2
+ body {
3
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
4
+ background: #f8fafc;
5
+ color: #1e293b;
6
+ min-height: 100vh;
7
+ }
8
+ button { cursor: pointer; }
@@ -0,0 +1,13 @@
1
+ import { createClient } from '@supabase/supabase-js';
2
+
3
+ const supabaseUrl = import.meta.env.VITE_SUPABASE_URL as string;
4
+ const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY as string;
5
+
6
+ if (!supabaseUrl || !supabaseKey) {
7
+ throw new Error(
8
+ '❌ Faltan variables de entorno de Supabase.\n' +
9
+ 'Copia .env.example a .env y completa VITE_SUPABASE_URL y VITE_SUPABASE_ANON_KEY.'
10
+ );
11
+ }
12
+
13
+ export const supabase = createClient(supabaseUrl, supabaseKey);
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import App from './App';
4
+ import './index.css';
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>
10
+ );
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true
18
+ },
19
+ "include": ["src"]
20
+ }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ });
@@ -0,0 +1,5 @@
1
+ # ─── Supabase ──────────────────────────────────────────────────────────────────
2
+ # Obtén estos valores en: https://app.supabase.com → Settings → API
3
+
4
+ VITE_SUPABASE_URL=https://xxxxxxxxxxxxxxxxxxx.supabase.co
5
+ VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="es">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Mi App Supabase</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "template-react-supabase-ts-tailwind",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "@supabase/supabase-js": "^2.39.7",
13
+ "react": "^18.2.0",
14
+ "react-dom": "^18.2.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/react": "^18.2.55",
18
+ "@types/react-dom": "^18.2.19",
19
+ "@vitejs/plugin-react": "^4.2.1",
20
+ "autoprefixer": "^10.4.17",
21
+ "postcss": "^8.4.35",
22
+ "tailwindcss": "^3.4.1",
23
+ "typescript": "^5.3.3",
24
+ "vite": "^5.1.4"
25
+ }
26
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -0,0 +1,33 @@
1
+ import { useEffect, useState } from 'react';
2
+ import type { Session } from '@supabase/supabase-js';
3
+ import { supabase } from './lib/supabaseClient';
4
+ import Login from './components/Login';
5
+ import Dashboard from './components/Dashboard';
6
+
7
+ export default function App() {
8
+ const [session, setSession] = useState<Session | null>(null);
9
+ const [loading, setLoading] = useState(true);
10
+
11
+ useEffect(() => {
12
+ supabase.auth.getSession().then(({ data: { session } }) => {
13
+ setSession(session);
14
+ setLoading(false);
15
+ });
16
+
17
+ const { data: { subscription } } = supabase.auth.onAuthStateChange(
18
+ (_event, session) => setSession(session)
19
+ );
20
+
21
+ return () => subscription.unsubscribe();
22
+ }, []);
23
+
24
+ if (loading) {
25
+ return (
26
+ <div className="flex items-center justify-center min-h-screen">
27
+ <p className="text-slate-500">Cargando...</p>
28
+ </div>
29
+ );
30
+ }
31
+
32
+ return session ? <Dashboard session={session} /> : <Login />;
33
+ }
@@ -0,0 +1,50 @@
1
+ import type { Session } from '@supabase/supabase-js';
2
+ import { supabase } from '../lib/supabaseClient';
3
+
4
+ interface Props {
5
+ session: Session;
6
+ }
7
+
8
+ export default function Dashboard({ session }: Props) {
9
+ const { user } = session;
10
+
11
+ async function handleLogout() {
12
+ const { error } = await supabase.auth.signOut();
13
+ if (error) console.error('Error al cerrar sesión:', error.message);
14
+ }
15
+
16
+ return (
17
+ <div className="min-h-screen bg-slate-50 flex justify-center items-start py-8 px-4">
18
+ <div className="bg-white rounded-2xl shadow-lg p-8 w-full max-w-2xl">
19
+ {/* Cabecera */}
20
+ <header className="flex items-center justify-between mb-6">
21
+ <h1 className="text-2xl font-bold text-slate-800">Dashboard</h1>
22
+ <button
23
+ onClick={handleLogout}
24
+ className="px-4 py-2 text-sm font-medium rounded-lg border border-slate-200 bg-slate-100 hover:bg-slate-200 transition-colors"
25
+ >
26
+ Cerrar sesión
27
+ </button>
28
+ </header>
29
+
30
+ {/* Contenido */}
31
+ <section className="mb-6">
32
+ <p className="text-lg text-slate-700 mb-1">
33
+ ¡Hola, <span className="font-semibold">{user.email}</span>!
34
+ </p>
35
+ <p className="text-slate-500 text-sm">Estás autenticado correctamente. 🎉</p>
36
+ </section>
37
+
38
+ {/* Debug */}
39
+ <details className="mt-4">
40
+ <summary className="text-xs text-slate-400 cursor-pointer select-none">
41
+ Ver datos de sesión (debug)
42
+ </summary>
43
+ <pre className="mt-3 bg-slate-100 rounded-lg p-4 text-xs overflow-x-auto text-slate-600">
44
+ {JSON.stringify({ id: user.id, email: user.email, role: user.role }, null, 2)}
45
+ </pre>
46
+ </details>
47
+ </div>
48
+ </div>
49
+ );
50
+ }
@@ -0,0 +1,94 @@
1
+ import { useState, FormEvent } from 'react';
2
+ import { supabase } from '../lib/supabaseClient';
3
+
4
+ export default function Login() {
5
+ const [email, setEmail] = useState('');
6
+ const [password, setPassword] = useState('');
7
+ const [isSignUp, setIsSignUp] = useState(false);
8
+ const [loading, setLoading] = useState(false);
9
+ const [error, setError] = useState('');
10
+ const [message, setMessage] = useState('');
11
+
12
+ async function handleSubmit(e: FormEvent<HTMLFormElement>) {
13
+ e.preventDefault();
14
+ setLoading(true);
15
+ setError('');
16
+ setMessage('');
17
+
18
+ try {
19
+ if (isSignUp) {
20
+ const { error } = await supabase.auth.signUp({ email, password });
21
+ if (error) throw error;
22
+ setMessage('✅ Revisa tu email para confirmar tu cuenta.');
23
+ } else {
24
+ const { error } = await supabase.auth.signInWithPassword({ email, password });
25
+ if (error) throw error;
26
+ }
27
+ } catch (err: unknown) {
28
+ setError(err instanceof Error ? err.message : 'Error desconocido');
29
+ } finally {
30
+ setLoading(false);
31
+ }
32
+ }
33
+
34
+ return (
35
+ <div className="flex items-center justify-center min-h-screen bg-slate-50 px-4">
36
+ <div className="bg-white rounded-2xl shadow-lg p-8 w-full max-w-md">
37
+ <h1 className="text-2xl font-bold text-center mb-6 text-slate-800">
38
+ {isSignUp ? 'Crear cuenta' : 'Iniciar sesión'}
39
+ </h1>
40
+
41
+ <form onSubmit={handleSubmit} className="flex flex-col gap-4">
42
+ <label className="flex flex-col gap-1 text-sm font-medium text-slate-700">
43
+ Email
44
+ <input
45
+ type="email"
46
+ value={email}
47
+ onChange={(e) => setEmail(e.target.value)}
48
+ placeholder="tu@email.com"
49
+ required
50
+ autoComplete="email"
51
+ className="px-3 py-2.5 rounded-lg border border-slate-300 text-base focus:outline-none focus:ring-2 focus:ring-emerald-400"
52
+ />
53
+ </label>
54
+
55
+ <label className="flex flex-col gap-1 text-sm font-medium text-slate-700">
56
+ Contraseña
57
+ <input
58
+ type="password"
59
+ value={password}
60
+ onChange={(e) => setPassword(e.target.value)}
61
+ placeholder="••••••••"
62
+ required
63
+ minLength={6}
64
+ autoComplete={isSignUp ? 'new-password' : 'current-password'}
65
+ className="px-3 py-2.5 rounded-lg border border-slate-300 text-base focus:outline-none focus:ring-2 focus:ring-emerald-400"
66
+ />
67
+ </label>
68
+
69
+ {error && <p className="text-red-500 text-sm">{error}</p>}
70
+ {message && <p className="text-emerald-600 text-sm">{message}</p>}
71
+
72
+ <button
73
+ type="submit"
74
+ disabled={loading}
75
+ className="mt-1 py-3 rounded-lg bg-emerald-500 hover:bg-emerald-600 text-white font-semibold text-base transition-colors disabled:opacity-60"
76
+ >
77
+ {loading ? 'Procesando...' : isSignUp ? 'Registrarse' : 'Entrar'}
78
+ </button>
79
+ </form>
80
+
81
+ <p className="mt-5 text-center text-sm text-slate-500">
82
+ {isSignUp ? '¿Ya tienes cuenta?' : '¿No tienes cuenta?'}{' '}
83
+ <button
84
+ type="button"
85
+ onClick={() => { setIsSignUp(!isSignUp); setError(''); setMessage(''); }}
86
+ className="text-emerald-600 font-semibold underline"
87
+ >
88
+ {isSignUp ? 'Inicia sesión' : 'Regístrate'}
89
+ </button>
90
+ </p>
91
+ </div>
92
+ </div>
93
+ );
94
+ }
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -0,0 +1,13 @@
1
+ import { createClient } from '@supabase/supabase-js';
2
+
3
+ const supabaseUrl = import.meta.env.VITE_SUPABASE_URL as string;
4
+ const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY as string;
5
+
6
+ if (!supabaseUrl || !supabaseKey) {
7
+ throw new Error(
8
+ '❌ Faltan variables de entorno de Supabase.\n' +
9
+ 'Copia .env.example a .env y completa VITE_SUPABASE_URL y VITE_SUPABASE_ANON_KEY.'
10
+ );
11
+ }
12
+
13
+ export const supabase = createClient(supabaseUrl, supabaseKey);
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import App from './App';
4
+ import './index.css';
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>
10
+ );
@@ -0,0 +1,8 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: ['./index.html', './src/**/*.{ts,tsx}'],
4
+ theme: {
5
+ extend: {},
6
+ },
7
+ plugins: [],
8
+ };
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true
18
+ },
19
+ "include": ["src"]
20
+ }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ });