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.
- package/README.md +100 -0
- package/bin/index.js +163 -0
- package/package.json +35 -0
- package/templates/react-supabase/.env.example +5 -0
- package/templates/react-supabase/README.md +64 -0
- package/templates/react-supabase/index.html +12 -0
- package/templates/react-supabase/package.json +20 -0
- package/templates/react-supabase/src/App.jsx +54 -0
- package/templates/react-supabase/src/components/Dashboard.jsx +117 -0
- package/templates/react-supabase/src/components/Login.jsx +181 -0
- package/templates/react-supabase/src/index.css +17 -0
- package/templates/react-supabase/src/lib/supabaseClient.js +19 -0
- package/templates/react-supabase/src/main.jsx +10 -0
- package/templates/react-supabase/vite.config.js +6 -0
- package/templates/react-supabase-tailwind/.env.example +5 -0
- package/templates/react-supabase-tailwind/index.html +12 -0
- package/templates/react-supabase-tailwind/package.json +23 -0
- package/templates/react-supabase-tailwind/postcss.config.js +6 -0
- package/templates/react-supabase-tailwind/src/App.jsx +32 -0
- package/templates/react-supabase-tailwind/src/components/Dashboard.jsx +45 -0
- package/templates/react-supabase-tailwind/src/components/Login.jsx +94 -0
- package/templates/react-supabase-tailwind/src/index.css +3 -0
- package/templates/react-supabase-tailwind/src/lib/supabaseClient.js +13 -0
- package/templates/react-supabase-tailwind/src/main.jsx +10 -0
- package/templates/react-supabase-tailwind/tailwind.config.js +8 -0
- package/templates/react-supabase-tailwind/vite.config.js +6 -0
- package/templates/react-supabase-ts/.env.example +5 -0
- package/templates/react-supabase-ts/index.html +12 -0
- package/templates/react-supabase-ts/package.json +23 -0
- package/templates/react-supabase-ts/src/App.tsx +33 -0
- package/templates/react-supabase-ts/src/components/Dashboard.tsx +56 -0
- package/templates/react-supabase-ts/src/components/Login.tsx +104 -0
- package/templates/react-supabase-ts/src/index.css +8 -0
- package/templates/react-supabase-ts/src/lib/supabaseClient.ts +13 -0
- package/templates/react-supabase-ts/src/main.tsx +10 -0
- package/templates/react-supabase-ts/tsconfig.json +20 -0
- package/templates/react-supabase-ts/vite.config.ts +6 -0
- package/templates/react-supabase-ts-tailwind/.env.example +5 -0
- package/templates/react-supabase-ts-tailwind/index.html +12 -0
- package/templates/react-supabase-ts-tailwind/package.json +26 -0
- package/templates/react-supabase-ts-tailwind/postcss.config.js +6 -0
- package/templates/react-supabase-ts-tailwind/src/App.tsx +33 -0
- package/templates/react-supabase-ts-tailwind/src/components/Dashboard.tsx +50 -0
- package/templates/react-supabase-ts-tailwind/src/components/Login.tsx +94 -0
- package/templates/react-supabase-ts-tailwind/src/index.css +3 -0
- package/templates/react-supabase-ts-tailwind/src/lib/supabaseClient.ts +13 -0
- package/templates/react-supabase-ts-tailwind/src/main.tsx +10 -0
- package/templates/react-supabase-ts-tailwind/tailwind.config.js +8 -0
- package/templates/react-supabase-ts-tailwind/tsconfig.json +20 -0
- package/templates/react-supabase-ts-tailwind/vite.config.ts +6 -0
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# create-supa-kit
|
|
2
|
+
|
|
3
|
+
> Scaffold a React + Vite + Supabase Auth app in seconds.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx create-supa-kit
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What it does
|
|
12
|
+
|
|
13
|
+
`create-supa-kit` is an interactive CLI that generates a ready-to-use React project with Supabase authentication already wired up — login, sign up, persistent sessions, and a protected dashboard, all out of the box.
|
|
14
|
+
|
|
15
|
+
## Templates
|
|
16
|
+
|
|
17
|
+
Four templates are available based on your answers:
|
|
18
|
+
|
|
19
|
+
| TypeScript | Tailwind CSS | Template used |
|
|
20
|
+
|:---:|:---:|---|
|
|
21
|
+
| No | No | `react-supabase` |
|
|
22
|
+
| Yes | No | `react-supabase-ts` |
|
|
23
|
+
| No | Yes | `react-supabase-tailwind` |
|
|
24
|
+
| Yes | Yes | `react-supabase-ts-tailwind` |
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx create-supa-kit
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The CLI will ask three questions:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
? Project name? my-app
|
|
36
|
+
? Use TypeScript? No
|
|
37
|
+
? Include Tailwind CSS? No
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then it scaffolds the project and prints the next steps:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cd my-app
|
|
44
|
+
npm install
|
|
45
|
+
cp .env.example .env # add your Supabase keys
|
|
46
|
+
npm run dev
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Environment variables
|
|
50
|
+
|
|
51
|
+
Open `.env` and fill in your project credentials from [app.supabase.com](https://app.supabase.com) → **Settings → API**:
|
|
52
|
+
|
|
53
|
+
```env
|
|
54
|
+
VITE_SUPABASE_URL = https://your-project.supabase.co
|
|
55
|
+
VITE_SUPABASE_ANON_KEY = eyJ...
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## What's included in every template
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
src/
|
|
62
|
+
├── lib/
|
|
63
|
+
│ └── supabaseClient.js # single Supabase client instance
|
|
64
|
+
├── components/
|
|
65
|
+
│ ├── Login.jsx # email/password login + sign up (toggle)
|
|
66
|
+
│ └── Dashboard.jsx # protected page
|
|
67
|
+
├── App.jsx # session control via onAuthStateChange
|
|
68
|
+
└── main.jsx
|
|
69
|
+
.env.example # env variable template
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Auth features
|
|
73
|
+
|
|
74
|
+
| Feature | How |
|
|
75
|
+
|---|---|
|
|
76
|
+
| Login | `supabase.auth.signInWithPassword` |
|
|
77
|
+
| Sign up | `supabase.auth.signUp` |
|
|
78
|
+
| Persistent session | Automatic — stored in `localStorage` |
|
|
79
|
+
| Logout | `supabase.auth.signOut` |
|
|
80
|
+
| Protected route | `App.jsx` renders `Dashboard` only when session exists |
|
|
81
|
+
|
|
82
|
+
## Requirements
|
|
83
|
+
|
|
84
|
+
- Node.js >= 18
|
|
85
|
+
|
|
86
|
+
## Local development
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
git clone https://github.com/your-github-user/create-supa-kit
|
|
90
|
+
cd create-supa-kit
|
|
91
|
+
npm install
|
|
92
|
+
npm link
|
|
93
|
+
|
|
94
|
+
# now available globally
|
|
95
|
+
create-supa-kit
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { existsSync, mkdirSync, readdirSync, copyFileSync, statSync } from 'fs';
|
|
7
|
+
import { resolve, join, dirname } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
// Absolute path to the templates/ folder bundled inside this package
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const TEMPLATES_DIR = resolve(__dirname, '../templates');
|
|
13
|
+
|
|
14
|
+
// ─── Banner ────────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
console.log(
|
|
17
|
+
chalk.cyan.bold(`
|
|
18
|
+
╔═══════════════════════════════════╗
|
|
19
|
+
║ create-supa-kit 🚀 ║
|
|
20
|
+
║ React + Vite + Supabase Auth ║
|
|
21
|
+
╚═══════════════════════════════════╝
|
|
22
|
+
`)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// ─── Template folder names ─────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
const TEMPLATES = {
|
|
28
|
+
js: 'react-supabase',
|
|
29
|
+
ts: 'react-supabase-ts',
|
|
30
|
+
jsTailwind: 'react-supabase-tailwind',
|
|
31
|
+
tsTailwind: 'react-supabase-ts-tailwind',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// ─── Prompts ───────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
async function promptUser() {
|
|
37
|
+
return inquirer.prompt([
|
|
38
|
+
{
|
|
39
|
+
type: 'input',
|
|
40
|
+
name: 'projectName',
|
|
41
|
+
message: chalk.white('Project name?'),
|
|
42
|
+
default: 'my-supa-app',
|
|
43
|
+
validate(value) {
|
|
44
|
+
const trimmed = value.trim();
|
|
45
|
+
if (!trimmed) return 'Project name cannot be empty.';
|
|
46
|
+
if (/[^a-zA-Z0-9-_]/.test(trimmed))
|
|
47
|
+
return 'Use only letters, numbers, hyphens, or underscores.';
|
|
48
|
+
return true;
|
|
49
|
+
},
|
|
50
|
+
filter: (v) => v.trim(),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: 'confirm',
|
|
54
|
+
name: 'useTypeScript',
|
|
55
|
+
message: chalk.white('Use TypeScript?'),
|
|
56
|
+
default: false,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: 'confirm',
|
|
60
|
+
name: 'useTailwind',
|
|
61
|
+
message: chalk.white('Include Tailwind CSS?'),
|
|
62
|
+
default: false,
|
|
63
|
+
},
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── Select template ───────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
function selectTemplate({ useTypeScript, useTailwind }) {
|
|
70
|
+
if (useTypeScript && useTailwind) return TEMPLATES.tsTailwind;
|
|
71
|
+
if (useTypeScript) return TEMPLATES.ts;
|
|
72
|
+
if (useTailwind) return TEMPLATES.jsTailwind;
|
|
73
|
+
return TEMPLATES.js;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ─── Copy template locally ─────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
const IGNORE = new Set(['node_modules', '.git', 'package-lock.json']);
|
|
79
|
+
|
|
80
|
+
function copyDir(src, dest) {
|
|
81
|
+
mkdirSync(dest, { recursive: true });
|
|
82
|
+
|
|
83
|
+
for (const entry of readdirSync(src)) {
|
|
84
|
+
if (IGNORE.has(entry)) continue;
|
|
85
|
+
|
|
86
|
+
const srcPath = join(src, entry);
|
|
87
|
+
const destPath = join(dest, entry);
|
|
88
|
+
|
|
89
|
+
if (statSync(srcPath).isDirectory()) {
|
|
90
|
+
copyDir(srcPath, destPath);
|
|
91
|
+
} else {
|
|
92
|
+
copyFileSync(srcPath, destPath);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ─── Final instructions ────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
function printNextSteps(projectName) {
|
|
100
|
+
console.log('');
|
|
101
|
+
console.log(chalk.green.bold(' ✅ Project created successfully!'));
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log(chalk.white(' Next steps:'));
|
|
104
|
+
console.log('');
|
|
105
|
+
console.log(chalk.cyan(` cd ${projectName}`));
|
|
106
|
+
console.log(chalk.cyan(' npm install'));
|
|
107
|
+
console.log(chalk.cyan(' cp .env.example .env'));
|
|
108
|
+
console.log('');
|
|
109
|
+
console.log(chalk.yellow(' Open .env and fill in your Supabase credentials:'));
|
|
110
|
+
console.log('');
|
|
111
|
+
console.log(chalk.gray(' VITE_SUPABASE_URL') + chalk.white(' = https://your-project.supabase.co'));
|
|
112
|
+
console.log(chalk.gray(' VITE_SUPABASE_ANON_KEY') + chalk.white(' = eyJ...'));
|
|
113
|
+
console.log('');
|
|
114
|
+
console.log(chalk.gray(' Get these values at ') + chalk.underline('https://app.supabase.com') + chalk.gray(' → Settings → API'));
|
|
115
|
+
console.log('');
|
|
116
|
+
console.log(chalk.cyan(' npm run dev'));
|
|
117
|
+
console.log('');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
async function main() {
|
|
123
|
+
try {
|
|
124
|
+
const { projectName, useTypeScript, useTailwind } = await promptUser();
|
|
125
|
+
|
|
126
|
+
// Make sure the target folder does not already exist
|
|
127
|
+
const targetDir = resolve(process.cwd(), projectName);
|
|
128
|
+
if (existsSync(targetDir)) {
|
|
129
|
+
console.log('');
|
|
130
|
+
console.error(
|
|
131
|
+
chalk.red(` ✖ Folder "${projectName}" already exists. Choose a different name or remove it first.`)
|
|
132
|
+
);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const templateName = selectTemplate({ useTypeScript, useTailwind });
|
|
137
|
+
const templateDir = join(TEMPLATES_DIR, templateName);
|
|
138
|
+
|
|
139
|
+
// Summary
|
|
140
|
+
console.log('');
|
|
141
|
+
console.log(chalk.gray(' Selected configuration:'));
|
|
142
|
+
console.log(chalk.gray(` • Project : ${chalk.white(projectName)}`));
|
|
143
|
+
console.log(chalk.gray(` • TypeScript : ${chalk.white(useTypeScript ? 'yes' : 'no')}`));
|
|
144
|
+
console.log(chalk.gray(` • Tailwind : ${chalk.white(useTailwind ? 'yes' : 'no')}`));
|
|
145
|
+
console.log('');
|
|
146
|
+
|
|
147
|
+
const spinner = ora({ text: chalk.cyan('Scaffolding project...'), color: 'cyan' }).start();
|
|
148
|
+
|
|
149
|
+
copyDir(templateDir, targetDir);
|
|
150
|
+
|
|
151
|
+
spinner.succeed(chalk.green('Done!'));
|
|
152
|
+
|
|
153
|
+
printNextSteps(projectName);
|
|
154
|
+
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.log('');
|
|
157
|
+
console.error(chalk.red(' ✖ Something went wrong:'));
|
|
158
|
+
console.error(chalk.red(` ${err.message}`));
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-supa-kit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI para generar proyectos React + Vite + Supabase Auth listos para usar",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-supa-kit": "bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"start": "node bin/index.js"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"chalk": "^5.3.0",
|
|
18
|
+
"inquirer": "^9.2.12",
|
|
19
|
+
"ora": "^8.0.1"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.0.0"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"react",
|
|
26
|
+
"vite",
|
|
27
|
+
"supabase",
|
|
28
|
+
"cli",
|
|
29
|
+
"create",
|
|
30
|
+
"starter",
|
|
31
|
+
"template"
|
|
32
|
+
],
|
|
33
|
+
"author": "",
|
|
34
|
+
"license": "MIT"
|
|
35
|
+
}
|
|
@@ -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,64 @@
|
|
|
1
|
+
# template-react-supabase
|
|
2
|
+
|
|
3
|
+
Template base generado por **create-supa-kit**.
|
|
4
|
+
|
|
5
|
+
Stack: React 18 · Vite 5 · Supabase Auth
|
|
6
|
+
|
|
7
|
+
## Estructura
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/
|
|
11
|
+
├── lib/
|
|
12
|
+
│ └── supabaseClient.js # cliente único de Supabase
|
|
13
|
+
├── components/
|
|
14
|
+
│ ├── Login.jsx # formulario login / registro
|
|
15
|
+
│ └── Dashboard.jsx # ruta protegida
|
|
16
|
+
├── App.jsx # control de sesión
|
|
17
|
+
├── main.jsx
|
|
18
|
+
└── index.css
|
|
19
|
+
.env.example # variables de entorno requeridas
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Configurar Supabase
|
|
23
|
+
|
|
24
|
+
1. Crea un proyecto en [supabase.com](https://supabase.com)
|
|
25
|
+
2. Ve a **Settings → API** y copia la URL y la anon key
|
|
26
|
+
3. Crea el archivo `.env`:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cp .env.example .env
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
4. Pega los valores:
|
|
33
|
+
|
|
34
|
+
```env
|
|
35
|
+
VITE_SUPABASE_URL=https://tu-proyecto.supabase.co
|
|
36
|
+
VITE_SUPABASE_ANON_KEY=eyJ...
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Arrancar en local
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install
|
|
43
|
+
npm run dev
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Autenticación incluida
|
|
47
|
+
|
|
48
|
+
| Función | Estado |
|
|
49
|
+
|---|---|
|
|
50
|
+
| Login email / password | ✅ |
|
|
51
|
+
| Registro | ✅ |
|
|
52
|
+
| Persistencia de sesión | ✅ (localStorage) |
|
|
53
|
+
| Logout | ✅ |
|
|
54
|
+
| Ruta protegida | ✅ |
|
|
55
|
+
|
|
56
|
+
> Para habilitar el registro asegúrate de que en tu proyecto Supabase esté activada
|
|
57
|
+
> la opción **Email confirmations** o desactívala en Auth → Settings durante desarrollo.
|
|
58
|
+
|
|
59
|
+
## Próximos pasos sugeridos
|
|
60
|
+
|
|
61
|
+
- Añadir React Router para múltiples páginas
|
|
62
|
+
- Conectar tablas de Supabase con `supabase.from('tabla').select()`
|
|
63
|
+
- Añadir OAuth (GitHub, Google) con `supabase.auth.signInWithOAuth`
|
|
64
|
+
- Proteger rutas con un componente `<PrivateRoute>`
|
|
@@ -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.jsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "template-react-supabase",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "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
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
18
|
+
"vite": "^5.1.4"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { supabase } from './lib/supabaseClient';
|
|
3
|
+
import Login from './components/Login';
|
|
4
|
+
import Dashboard from './components/Dashboard';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* App — controla el estado de sesión global.
|
|
8
|
+
*
|
|
9
|
+
* Flujo:
|
|
10
|
+
* 1. Al montar, recupera la sesión activa (si el usuario ya inició sesión).
|
|
11
|
+
* 2. Se suscribe a cambios de auth (login / logout) y actualiza `session`.
|
|
12
|
+
* 3. Renderiza <Login> o <Dashboard> según corresponda.
|
|
13
|
+
*/
|
|
14
|
+
export default function App() {
|
|
15
|
+
const [session, setSession] = useState(null);
|
|
16
|
+
const [loading, setLoading] = useState(true);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
// Recuperar sesión persistida en localStorage (gestionada por Supabase)
|
|
20
|
+
supabase.auth.getSession().then(({ data: { session } }) => {
|
|
21
|
+
setSession(session);
|
|
22
|
+
setLoading(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Suscribirse a cambios de autenticación
|
|
26
|
+
const { data: { subscription } } = supabase.auth.onAuthStateChange(
|
|
27
|
+
(_event, session) => {
|
|
28
|
+
setSession(session);
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Limpiar suscripción al desmontar
|
|
33
|
+
return () => subscription.unsubscribe();
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
if (loading) {
|
|
37
|
+
return (
|
|
38
|
+
<div style={styles.centered}>
|
|
39
|
+
<p>Cargando...</p>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return session ? <Dashboard session={session} /> : <Login />;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const styles = {
|
|
48
|
+
centered: {
|
|
49
|
+
display: 'flex',
|
|
50
|
+
justifyContent: 'center',
|
|
51
|
+
alignItems: 'center',
|
|
52
|
+
minHeight: '100vh',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { supabase } from '../lib/supabaseClient';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dashboard — ruta protegida.
|
|
5
|
+
*
|
|
6
|
+
* Solo se renderiza cuando existe una sesión activa (gestionado por App.jsx).
|
|
7
|
+
* Muestra información del usuario autenticado y un botón para cerrar sesión.
|
|
8
|
+
*
|
|
9
|
+
* Props:
|
|
10
|
+
* - session: objeto de sesión de Supabase ({ user, access_token, ... })
|
|
11
|
+
*/
|
|
12
|
+
export default function Dashboard({ session }) {
|
|
13
|
+
const { user } = session;
|
|
14
|
+
|
|
15
|
+
async function handleLogout() {
|
|
16
|
+
const { error } = await supabase.auth.signOut();
|
|
17
|
+
if (error) console.error('Error al cerrar sesión:', error.message);
|
|
18
|
+
// App.jsx detecta session = null y vuelve a renderizar <Login>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div style={styles.wrapper}>
|
|
23
|
+
<div style={styles.card}>
|
|
24
|
+
{/* ── Cabecera ─────────────────────────────────────────────────────── */}
|
|
25
|
+
<header style={styles.header}>
|
|
26
|
+
<h1 style={styles.title}>Dashboard</h1>
|
|
27
|
+
<button style={styles.logoutBtn} onClick={handleLogout}>
|
|
28
|
+
Cerrar sesión
|
|
29
|
+
</button>
|
|
30
|
+
</header>
|
|
31
|
+
|
|
32
|
+
{/* ── Contenido ────────────────────────────────────────────────────── */}
|
|
33
|
+
<section style={styles.section}>
|
|
34
|
+
<p style={styles.welcome}>
|
|
35
|
+
¡Hola, <strong>{user.email}</strong>!
|
|
36
|
+
</p>
|
|
37
|
+
<p style={styles.hint}>
|
|
38
|
+
Estás autenticado correctamente. 🎉
|
|
39
|
+
</p>
|
|
40
|
+
</section>
|
|
41
|
+
|
|
42
|
+
{/* ── Info de sesión (útil en desarrollo) ──────────────────────────── */}
|
|
43
|
+
<details style={styles.details}>
|
|
44
|
+
<summary style={styles.summary}>Ver datos de sesión (debug)</summary>
|
|
45
|
+
<pre style={styles.pre}>
|
|
46
|
+
{JSON.stringify({ id: user.id, email: user.email, role: user.role }, null, 2)}
|
|
47
|
+
</pre>
|
|
48
|
+
</details>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─── Estilos inline mínimos ───────────────────────────────────────────────────
|
|
55
|
+
const styles = {
|
|
56
|
+
wrapper: {
|
|
57
|
+
display: 'flex',
|
|
58
|
+
justifyContent: 'center',
|
|
59
|
+
alignItems: 'flex-start',
|
|
60
|
+
minHeight: '100vh',
|
|
61
|
+
padding: '2rem 1rem',
|
|
62
|
+
background: '#f8fafc',
|
|
63
|
+
},
|
|
64
|
+
card: {
|
|
65
|
+
background: '#fff',
|
|
66
|
+
borderRadius: '12px',
|
|
67
|
+
padding: '2rem',
|
|
68
|
+
width: '100%',
|
|
69
|
+
maxWidth: '640px',
|
|
70
|
+
boxShadow: '0 4px 24px rgba(0,0,0,0.08)',
|
|
71
|
+
},
|
|
72
|
+
header: {
|
|
73
|
+
display: 'flex',
|
|
74
|
+
justifyContent: 'space-between',
|
|
75
|
+
alignItems: 'center',
|
|
76
|
+
marginBottom: '1.5rem',
|
|
77
|
+
},
|
|
78
|
+
title: {
|
|
79
|
+
fontSize: '1.5rem',
|
|
80
|
+
fontWeight: 700,
|
|
81
|
+
},
|
|
82
|
+
logoutBtn: {
|
|
83
|
+
padding: '0.5rem 1rem',
|
|
84
|
+
borderRadius: '8px',
|
|
85
|
+
background: '#f1f5f9',
|
|
86
|
+
border: '1px solid #e2e8f0',
|
|
87
|
+
fontWeight: 500,
|
|
88
|
+
fontSize: '0.875rem',
|
|
89
|
+
},
|
|
90
|
+
section: {
|
|
91
|
+
marginBottom: '1.5rem',
|
|
92
|
+
},
|
|
93
|
+
welcome: {
|
|
94
|
+
fontSize: '1.125rem',
|
|
95
|
+
marginBottom: '0.5rem',
|
|
96
|
+
},
|
|
97
|
+
hint: {
|
|
98
|
+
color: '#64748b',
|
|
99
|
+
fontSize: '0.9rem',
|
|
100
|
+
},
|
|
101
|
+
details: {
|
|
102
|
+
marginTop: '1rem',
|
|
103
|
+
},
|
|
104
|
+
summary: {
|
|
105
|
+
cursor: 'pointer',
|
|
106
|
+
fontSize: '0.875rem',
|
|
107
|
+
color: '#94a3b8',
|
|
108
|
+
},
|
|
109
|
+
pre: {
|
|
110
|
+
marginTop: '0.75rem',
|
|
111
|
+
background: '#f1f5f9',
|
|
112
|
+
borderRadius: '8px',
|
|
113
|
+
padding: '1rem',
|
|
114
|
+
fontSize: '0.8rem',
|
|
115
|
+
overflowX: 'auto',
|
|
116
|
+
},
|
|
117
|
+
};
|