xertica-ui 1.4.10 → 1.4.11
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/bin/cli.ts +39 -52
- package/dist/cli.js +32 -40
- package/package.json +1 -1
- package/templates/guidelines/Guidelines.md +16 -0
- package/templates/index.css +3 -3
- package/templates/index.html +1 -1
- package/templates/package.json +1 -1
- package/templates/src/{App.tsx → app/App.tsx} +0 -1
- package/templates/src/{main.tsx → app/main.tsx} +1 -1
- package/templates/src/{pages/ForgotPasswordPage.tsx → app/pages/ForgotPassword/ForgotPasswordContent.tsx} +1 -1
- package/templates/src/app/pages/ForgotPasswordPage.tsx +6 -0
- package/templates/src/{pages → app/pages}/HomePage.tsx +1 -1
- package/templates/src/{pages/LoginPage.tsx → app/pages/Login/LoginContent.tsx} +183 -183
- package/templates/src/app/pages/LoginPage.tsx +10 -0
- package/templates/src/{pages/ResetPasswordPage.tsx → app/pages/ResetPassword/ResetPasswordContent.tsx} +1 -1
- package/templates/src/app/pages/ResetPasswordPage.tsx +6 -0
- package/templates/src/{pages → app/pages}/TemplatePage.tsx +1 -1
- package/templates/src/{pages/VerifyEmailPage.tsx → app/pages/VerifyEmail/VerifyEmailContent.tsx} +1 -1
- package/templates/src/app/pages/VerifyEmailPage.tsx +6 -0
- /package/templates/src/{content → app/pages/Home}/HomeContent.tsx +0 -0
- /package/templates/src/{content → app/pages/Template}/TemplateContent.tsx +0 -0
- /package/templates/src/{routes.tsx → app/routes.tsx} +0 -0
- /package/templates/{styles → src/styles}/xertica/tokens.css +0 -0
package/bin/cli.ts
CHANGED
|
@@ -85,6 +85,7 @@ program
|
|
|
85
85
|
'eslint.config.js',
|
|
86
86
|
'index.css',
|
|
87
87
|
'.env.example',
|
|
88
|
+
'guidelines',
|
|
88
89
|
];
|
|
89
90
|
|
|
90
91
|
for (const file of rootFilesToCopy) {
|
|
@@ -105,69 +106,56 @@ program
|
|
|
105
106
|
await fs.writeJson(path.join(targetDir, 'package.json'), pkgContent, { spaces: 2 });
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
// 4. Ensure src directory exists
|
|
109
|
-
await fs.ensureDir(path.join(targetDir, 'src'));
|
|
109
|
+
// 4. Ensure src/app directory exists
|
|
110
|
+
await fs.ensureDir(path.join(targetDir, 'src', 'app'));
|
|
110
111
|
|
|
111
|
-
// 5. Copy src/App.tsx
|
|
112
|
+
// 5. Copy src/app/App.tsx
|
|
112
113
|
await fs.copy(
|
|
113
|
-
path.join(templatesDir, 'src', 'App.tsx'),
|
|
114
|
-
path.join(targetDir, 'src', 'App.tsx')
|
|
114
|
+
path.join(templatesDir, 'src', 'app', 'App.tsx'),
|
|
115
|
+
path.join(targetDir, 'src', 'app', 'App.tsx')
|
|
115
116
|
);
|
|
116
117
|
|
|
117
|
-
// 6. Copy src/main.tsx
|
|
118
|
+
// 6. Copy src/app/main.tsx
|
|
118
119
|
await fs.copy(
|
|
119
|
-
path.join(templatesDir, 'src', 'main.tsx'),
|
|
120
|
-
path.join(targetDir, 'src', 'main.tsx')
|
|
120
|
+
path.join(templatesDir, 'src', 'app', 'main.tsx'),
|
|
121
|
+
path.join(targetDir, 'src', 'app', 'main.tsx')
|
|
121
122
|
);
|
|
122
123
|
|
|
123
|
-
// 7. Copy src/routes.tsx
|
|
124
|
+
// 7. Copy src/app/routes.tsx
|
|
124
125
|
await fs.copy(
|
|
125
|
-
path.join(templatesDir, 'src', 'routes.tsx'),
|
|
126
|
-
path.join(targetDir, 'src', 'routes.tsx')
|
|
126
|
+
path.join(templatesDir, 'src', 'app', 'routes.tsx'),
|
|
127
|
+
path.join(targetDir, 'src', 'app', 'routes.tsx')
|
|
127
128
|
);
|
|
128
129
|
|
|
129
|
-
// 8. Copy pages
|
|
130
|
-
await fs.ensureDir(path.join(targetDir, 'src', 'pages'));
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
'
|
|
136
|
-
'
|
|
137
|
-
'
|
|
138
|
-
'
|
|
139
|
-
'HomePage.tsx': hasHome,
|
|
140
|
-
'TemplatePage.tsx': hasTemplate,
|
|
130
|
+
// 8. Copy pages and contents
|
|
131
|
+
await fs.ensureDir(path.join(targetDir, 'src', 'app', 'pages'));
|
|
132
|
+
|
|
133
|
+
const optionalPages: Record<string, { file: string, dir?: string, shouldCopy: boolean }> = {
|
|
134
|
+
login: { file: 'LoginPage.tsx', dir: 'Login', shouldCopy: hasLogin },
|
|
135
|
+
forgot: { file: 'ForgotPasswordPage.tsx', dir: 'ForgotPassword', shouldCopy: hasLogin },
|
|
136
|
+
verify: { file: 'VerifyEmailPage.tsx', dir: 'VerifyEmail', shouldCopy: hasLogin },
|
|
137
|
+
reset: { file: 'ResetPasswordPage.tsx', dir: 'ResetPassword', shouldCopy: hasLogin },
|
|
138
|
+
home: { file: 'HomePage.tsx', dir: 'Home', shouldCopy: hasHome },
|
|
139
|
+
template: { file: 'TemplatePage.tsx', dir: 'Template', shouldCopy: hasTemplate },
|
|
141
140
|
};
|
|
142
141
|
|
|
143
|
-
const srcPagesDir = path.join(templatesDir, 'src', 'pages');
|
|
144
|
-
for (const
|
|
142
|
+
const srcPagesDir = path.join(templatesDir, 'src', 'app', 'pages');
|
|
143
|
+
for (const key in optionalPages) {
|
|
144
|
+
const { file, dir, shouldCopy } = optionalPages[key];
|
|
145
145
|
if (shouldCopy) {
|
|
146
|
-
const
|
|
147
|
-
if (await fs.pathExists(
|
|
148
|
-
await fs.copy(
|
|
146
|
+
const srcFilePath = path.join(srcPagesDir, file);
|
|
147
|
+
if (await fs.pathExists(srcFilePath)) {
|
|
148
|
+
await fs.copy(srcFilePath, path.join(targetDir, 'src', 'app', 'pages', file));
|
|
149
|
+
}
|
|
150
|
+
if (dir) {
|
|
151
|
+
const srcDirPath = path.join(srcPagesDir, dir);
|
|
152
|
+
if (await fs.pathExists(srcDirPath)) {
|
|
153
|
+
await fs.copy(srcDirPath, path.join(targetDir, 'src', 'app', 'pages', dir));
|
|
154
|
+
}
|
|
149
155
|
}
|
|
150
156
|
}
|
|
151
157
|
}
|
|
152
158
|
|
|
153
|
-
// 9. Copy content components
|
|
154
|
-
await fs.ensureDir(path.join(targetDir, 'src', 'content'));
|
|
155
|
-
const srcContentDir = path.join(templatesDir, 'src', 'content');
|
|
156
|
-
|
|
157
|
-
if (hasHome && await fs.pathExists(path.join(srcContentDir, 'HomeContent.tsx'))) {
|
|
158
|
-
await fs.copy(
|
|
159
|
-
path.join(srcContentDir, 'HomeContent.tsx'),
|
|
160
|
-
path.join(targetDir, 'src', 'content', 'HomeContent.tsx')
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (hasTemplate && await fs.pathExists(path.join(srcContentDir, 'TemplateContent.tsx'))) {
|
|
165
|
-
await fs.copy(
|
|
166
|
-
path.join(srcContentDir, 'TemplateContent.tsx'),
|
|
167
|
-
path.join(targetDir, 'src', 'content', 'TemplateContent.tsx')
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
159
|
// 10. If NOT all pages selected, create a minimal App.tsx
|
|
172
160
|
if (!hasLogin || !hasHome || !hasTemplate) {
|
|
173
161
|
const imports: string[] = [];
|
|
@@ -195,7 +183,6 @@ program
|
|
|
195
183
|
const minimalApp = `import React, { useState, useEffect, useLayoutEffect } from 'react';
|
|
196
184
|
import { BrowserRouter as Router, Routes, Route, Navigate, useNavigate, useLocation } from 'react-router-dom';
|
|
197
185
|
import { XerticaProvider, Toaster } from 'xertica-ui';
|
|
198
|
-
import 'xertica-ui/style.css';
|
|
199
186
|
${imports.join('\n')}
|
|
200
187
|
|
|
201
188
|
export default function App() {
|
|
@@ -213,14 +200,14 @@ ${routes.join('\n')}
|
|
|
213
200
|
);
|
|
214
201
|
}
|
|
215
202
|
`;
|
|
216
|
-
await fs.writeFile(path.join(targetDir, 'src', 'App.tsx'), minimalApp);
|
|
203
|
+
await fs.writeFile(path.join(targetDir, 'src', 'app', 'App.tsx'), minimalApp);
|
|
217
204
|
}
|
|
218
205
|
|
|
219
206
|
// 11. Generate theme tokens
|
|
220
207
|
const selectedTheme = colorThemes.find(t => t.id === response.theme) || colorThemes[0];
|
|
221
208
|
|
|
222
|
-
// Create styles/xertica directory
|
|
223
|
-
const tokensDir = path.join(targetDir, 'styles', 'xertica');
|
|
209
|
+
// Create src/styles/xertica directory
|
|
210
|
+
const tokensDir = path.join(targetDir, 'src', 'styles', 'xertica');
|
|
224
211
|
await fs.ensureDir(tokensDir);
|
|
225
212
|
const newTokensCss = generateTokensCss(selectedTheme);
|
|
226
213
|
await fs.writeFile(path.join(tokensDir, 'tokens.css'), newTokensCss);
|
|
@@ -241,7 +228,7 @@ ${routes.join('\n')}
|
|
|
241
228
|
console.log(chalk.cyan(' npm run dev'));
|
|
242
229
|
console.log();
|
|
243
230
|
console.log(chalk.gray(' Components are imported from the xertica-ui package.'));
|
|
244
|
-
console.log(chalk.gray(' Customize the theme in styles/xertica/tokens.css'));
|
|
231
|
+
console.log(chalk.gray(' Customize the theme in src/styles/xertica/tokens.css'));
|
|
245
232
|
|
|
246
233
|
} catch (error) {
|
|
247
234
|
spinner.fail('Failed to initialize project');
|
|
@@ -272,7 +259,7 @@ program
|
|
|
272
259
|
const spinner = ora('Updating theme...').start();
|
|
273
260
|
|
|
274
261
|
try {
|
|
275
|
-
const tokensPath = path.join(targetDir, 'styles', 'xertica', 'tokens.css');
|
|
262
|
+
const tokensPath = path.join(targetDir, 'src', 'styles', 'xertica', 'tokens.css');
|
|
276
263
|
const selectedTheme = colorThemes.find(t => t.id === themeResponse.theme);
|
|
277
264
|
|
|
278
265
|
if (selectedTheme) {
|
package/dist/cli.js
CHANGED
|
@@ -555,7 +555,8 @@ program.command("init").description("Initialize a new Xertica UI project").argum
|
|
|
555
555
|
"vite-env.d.ts",
|
|
556
556
|
"eslint.config.js",
|
|
557
557
|
"index.css",
|
|
558
|
-
".env.example"
|
|
558
|
+
".env.example",
|
|
559
|
+
"guidelines"
|
|
559
560
|
];
|
|
560
561
|
for (const file of rootFilesToCopy) {
|
|
561
562
|
const srcPath = path.join(templatesDir, file);
|
|
@@ -571,52 +572,44 @@ program.command("init").description("Initialize a new Xertica UI project").argum
|
|
|
571
572
|
pkgContent.name = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
572
573
|
await fs.writeJson(path.join(targetDir, "package.json"), pkgContent, { spaces: 2 });
|
|
573
574
|
}
|
|
574
|
-
await fs.ensureDir(path.join(targetDir, "src"));
|
|
575
|
+
await fs.ensureDir(path.join(targetDir, "src", "app"));
|
|
575
576
|
await fs.copy(
|
|
576
|
-
path.join(templatesDir, "src", "App.tsx"),
|
|
577
|
-
path.join(targetDir, "src", "App.tsx")
|
|
577
|
+
path.join(templatesDir, "src", "app", "App.tsx"),
|
|
578
|
+
path.join(targetDir, "src", "app", "App.tsx")
|
|
578
579
|
);
|
|
579
580
|
await fs.copy(
|
|
580
|
-
path.join(templatesDir, "src", "main.tsx"),
|
|
581
|
-
path.join(targetDir, "src", "main.tsx")
|
|
581
|
+
path.join(templatesDir, "src", "app", "main.tsx"),
|
|
582
|
+
path.join(targetDir, "src", "app", "main.tsx")
|
|
582
583
|
);
|
|
583
584
|
await fs.copy(
|
|
584
|
-
path.join(templatesDir, "src", "routes.tsx"),
|
|
585
|
-
path.join(targetDir, "src", "routes.tsx")
|
|
585
|
+
path.join(templatesDir, "src", "app", "routes.tsx"),
|
|
586
|
+
path.join(targetDir, "src", "app", "routes.tsx")
|
|
586
587
|
);
|
|
587
|
-
await fs.ensureDir(path.join(targetDir, "src", "pages"));
|
|
588
|
-
const alwaysCopyPages = ["LoginPage.tsx", "ForgotPasswordPage.tsx", "VerifyEmailPage.tsx", "ResetPasswordPage.tsx"];
|
|
588
|
+
await fs.ensureDir(path.join(targetDir, "src", "app", "pages"));
|
|
589
589
|
const optionalPages = {
|
|
590
|
-
"LoginPage.tsx": hasLogin,
|
|
591
|
-
"ForgotPasswordPage.tsx": hasLogin,
|
|
592
|
-
"VerifyEmailPage.tsx": hasLogin,
|
|
593
|
-
"ResetPasswordPage.tsx": hasLogin,
|
|
594
|
-
"HomePage.tsx": hasHome,
|
|
595
|
-
"TemplatePage.tsx": hasTemplate
|
|
590
|
+
login: { file: "LoginPage.tsx", dir: "Login", shouldCopy: hasLogin },
|
|
591
|
+
forgot: { file: "ForgotPasswordPage.tsx", dir: "ForgotPassword", shouldCopy: hasLogin },
|
|
592
|
+
verify: { file: "VerifyEmailPage.tsx", dir: "VerifyEmail", shouldCopy: hasLogin },
|
|
593
|
+
reset: { file: "ResetPasswordPage.tsx", dir: "ResetPassword", shouldCopy: hasLogin },
|
|
594
|
+
home: { file: "HomePage.tsx", dir: "Home", shouldCopy: hasHome },
|
|
595
|
+
template: { file: "TemplatePage.tsx", dir: "Template", shouldCopy: hasTemplate }
|
|
596
596
|
};
|
|
597
|
-
const srcPagesDir = path.join(templatesDir, "src", "pages");
|
|
598
|
-
for (const
|
|
597
|
+
const srcPagesDir = path.join(templatesDir, "src", "app", "pages");
|
|
598
|
+
for (const key in optionalPages) {
|
|
599
|
+
const { file, dir, shouldCopy } = optionalPages[key];
|
|
599
600
|
if (shouldCopy) {
|
|
600
|
-
const
|
|
601
|
-
if (await fs.pathExists(
|
|
602
|
-
await fs.copy(
|
|
601
|
+
const srcFilePath = path.join(srcPagesDir, file);
|
|
602
|
+
if (await fs.pathExists(srcFilePath)) {
|
|
603
|
+
await fs.copy(srcFilePath, path.join(targetDir, "src", "app", "pages", file));
|
|
604
|
+
}
|
|
605
|
+
if (dir) {
|
|
606
|
+
const srcDirPath = path.join(srcPagesDir, dir);
|
|
607
|
+
if (await fs.pathExists(srcDirPath)) {
|
|
608
|
+
await fs.copy(srcDirPath, path.join(targetDir, "src", "app", "pages", dir));
|
|
609
|
+
}
|
|
603
610
|
}
|
|
604
611
|
}
|
|
605
612
|
}
|
|
606
|
-
await fs.ensureDir(path.join(targetDir, "src", "content"));
|
|
607
|
-
const srcContentDir = path.join(templatesDir, "src", "content");
|
|
608
|
-
if (hasHome && await fs.pathExists(path.join(srcContentDir, "HomeContent.tsx"))) {
|
|
609
|
-
await fs.copy(
|
|
610
|
-
path.join(srcContentDir, "HomeContent.tsx"),
|
|
611
|
-
path.join(targetDir, "src", "content", "HomeContent.tsx")
|
|
612
|
-
);
|
|
613
|
-
}
|
|
614
|
-
if (hasTemplate && await fs.pathExists(path.join(srcContentDir, "TemplateContent.tsx"))) {
|
|
615
|
-
await fs.copy(
|
|
616
|
-
path.join(srcContentDir, "TemplateContent.tsx"),
|
|
617
|
-
path.join(targetDir, "src", "content", "TemplateContent.tsx")
|
|
618
|
-
);
|
|
619
|
-
}
|
|
620
613
|
if (!hasLogin || !hasHome || !hasTemplate) {
|
|
621
614
|
const imports = [];
|
|
622
615
|
const routes = [];
|
|
@@ -641,7 +634,6 @@ program.command("init").description("Initialize a new Xertica UI project").argum
|
|
|
641
634
|
const minimalApp = `import React, { useState, useEffect, useLayoutEffect } from 'react';
|
|
642
635
|
import { BrowserRouter as Router, Routes, Route, Navigate, useNavigate, useLocation } from 'react-router-dom';
|
|
643
636
|
import { XerticaProvider, Toaster } from 'xertica-ui';
|
|
644
|
-
import 'xertica-ui/style.css';
|
|
645
637
|
${imports.join("\n")}
|
|
646
638
|
|
|
647
639
|
export default function App() {
|
|
@@ -659,10 +651,10 @@ ${routes.join("\n")}
|
|
|
659
651
|
);
|
|
660
652
|
}
|
|
661
653
|
`;
|
|
662
|
-
await fs.writeFile(path.join(targetDir, "src", "App.tsx"), minimalApp);
|
|
654
|
+
await fs.writeFile(path.join(targetDir, "src", "app", "App.tsx"), minimalApp);
|
|
663
655
|
}
|
|
664
656
|
const selectedTheme = colorThemes.find((t) => t.id === response.theme) || colorThemes[0];
|
|
665
|
-
const tokensDir = path.join(targetDir, "styles", "xertica");
|
|
657
|
+
const tokensDir = path.join(targetDir, "src", "styles", "xertica");
|
|
666
658
|
await fs.ensureDir(tokensDir);
|
|
667
659
|
const newTokensCss = generateTokensCss(selectedTheme);
|
|
668
660
|
await fs.writeFile(path.join(tokensDir, "tokens.css"), newTokensCss);
|
|
@@ -681,7 +673,7 @@ ${routes.join("\n")}
|
|
|
681
673
|
console.log(chalk.cyan(" npm run dev"));
|
|
682
674
|
console.log();
|
|
683
675
|
console.log(chalk.gray(" Components are imported from the xertica-ui package."));
|
|
684
|
-
console.log(chalk.gray(" Customize the theme in styles/xertica/tokens.css"));
|
|
676
|
+
console.log(chalk.gray(" Customize the theme in src/styles/xertica/tokens.css"));
|
|
685
677
|
} catch (error) {
|
|
686
678
|
spinner.fail("Failed to initialize project");
|
|
687
679
|
console.error(error);
|
|
@@ -703,7 +695,7 @@ program.command("update").description("Update theme tokens in your project").act
|
|
|
703
695
|
if (!themeResponse.theme) return;
|
|
704
696
|
const spinner = ora("Updating theme...").start();
|
|
705
697
|
try {
|
|
706
|
-
const tokensPath = path.join(targetDir, "styles", "xertica", "tokens.css");
|
|
698
|
+
const tokensPath = path.join(targetDir, "src", "styles", "xertica", "tokens.css");
|
|
707
699
|
const selectedTheme = colorThemes.find((t) => t.id === themeResponse.theme);
|
|
708
700
|
if (selectedTheme) {
|
|
709
701
|
await fs.ensureDir(path.dirname(tokensPath));
|
package/package.json
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Guias de Desenvolvimento, Padrões e Políticas
|
|
2
|
+
|
|
3
|
+
Este é o documento oficial de diretrizes estruturais deste projeto, embasado nas melhores práticas arquiteturais.
|
|
4
|
+
|
|
5
|
+
## Padrão de Componentização e Rotas
|
|
6
|
+
|
|
7
|
+
A stack usa React e a árvore de diretórios principal foi formatada sob os princípios de modularidade:
|
|
8
|
+
|
|
9
|
+
- `/src/app`: O cerne de inicialização lógica e rotemento do framework React.
|
|
10
|
+
- `/src/app/pages`: Ponto de entrada (Entrypoint Roots) de cada rota gerada. Aqui devem residir puramente instâncias injetáveis da Página, responsáveis por amontoar contextos ou metadados de Rota de alto nível.
|
|
11
|
+
- `/src/app/pages/NomeDaPagina`: Neste subdomínio coabita a verdadeira lógica e montagem de componentes ligados umbilicalmente àquela tela (ex: `HomePage` chama o `HomeContent`). Evite misturar blocos de "dumb components" utilitários aqui; tais peças devem vir da pasta `/src/components` da sua arquitetura, se os estender fora do *Design System*.
|
|
12
|
+
|
|
13
|
+
## Sistema de Tokens e Temas Visual
|
|
14
|
+
|
|
15
|
+
- `/src/styles/xertica/tokens.css`: A camada absoluta de definição de variáveis visuais da marca. Ao atualizar cores, fontes nativas ou métricas de Design System, efetue primariamente a edição neste arquivo.
|
|
16
|
+
- O projeto não sofre sobrescrita paralela; Tailwind V4 consumirá estes Tokens base dinamicamente.
|
package/templates/index.css
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;600;700;800&display=swap");
|
|
2
2
|
|
|
3
|
-
/* Your project's theme tokens (generated by CLI, customize here) */
|
|
4
|
-
@import './styles/xertica/tokens.css';
|
|
5
|
-
|
|
6
3
|
/* Xertica UI compiled styles (includes Tailwind v4 base + all component styles) */
|
|
7
4
|
@import 'xertica-ui/style.css';
|
|
8
5
|
|
|
6
|
+
/* Your project's theme tokens (generated by CLI, customize here) */
|
|
7
|
+
@import './src/styles/xertica/tokens.css';
|
|
8
|
+
|
|
9
9
|
/* Tell Tailwind v4 to scan xertica-ui for class names */
|
|
10
10
|
@source './node_modules/xertica-ui';
|
|
11
11
|
|
package/templates/index.html
CHANGED
package/templates/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect, useLayoutEffect } from 'react';
|
|
2
2
|
import { BrowserRouter as Router, Routes, Route, Navigate, useNavigate, useLocation } from 'react-router-dom';
|
|
3
3
|
import { XerticaProvider } from 'xertica-ui';
|
|
4
|
-
import 'xertica-ui/style.css';
|
|
5
4
|
|
|
6
5
|
import { LoginPage } from './pages/LoginPage';
|
|
7
6
|
import { ForgotPasswordPage } from './pages/ForgotPasswordPage';
|
|
@@ -3,7 +3,7 @@ import { Button, Input, Label, XerticaLogo, ImageWithFallback, LanguageSelector
|
|
|
3
3
|
import { ArrowLeft, Lock } from 'lucide-react';
|
|
4
4
|
import { useNavigate } from 'react-router-dom';
|
|
5
5
|
|
|
6
|
-
export function
|
|
6
|
+
export function ForgotPasswordContent() {
|
|
7
7
|
const navigate = useNavigate();
|
|
8
8
|
const [email, setEmail] = useState('');
|
|
9
9
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -1,183 +1,183 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { Button, Input, Label, XerticaLogo, ImageWithFallback, LanguageSelector } from 'xertica-ui';
|
|
3
|
-
import { Lock } from 'lucide-react';
|
|
4
|
-
import { useNavigate } from 'react-router-dom';
|
|
5
|
-
|
|
6
|
-
interface
|
|
7
|
-
onLogin: (email: string, password: string) => boolean;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function
|
|
11
|
-
const navigate = useNavigate();
|
|
12
|
-
const [email, setEmail] = useState('');
|
|
13
|
-
const [password, setPassword] = useState('');
|
|
14
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
15
|
-
const [error, setError] = useState('');
|
|
16
|
-
|
|
17
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
18
|
-
e.preventDefault();
|
|
19
|
-
setError('');
|
|
20
|
-
setIsLoading(true);
|
|
21
|
-
|
|
22
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
23
|
-
|
|
24
|
-
const success = onLogin(email, password);
|
|
25
|
-
|
|
26
|
-
if (!success) {
|
|
27
|
-
setError('Por favor, preencha todos os campos');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
setIsLoading(false);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const handleSocialLogin = (provider: string) => {
|
|
34
|
-
console.log(`Login com ${provider}`);
|
|
35
|
-
onLogin('social@user.com', 'social-auth');
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<div className="min-h-screen flex">
|
|
40
|
-
{/* Lado esquerdo - Imagem de fundo completa */}
|
|
41
|
-
<div className="hidden lg:flex lg:flex-1 relative overflow-hidden">
|
|
42
|
-
<ImageWithFallback
|
|
43
|
-
src="https://images.unsplash.com/photo-1551434678-e076c223a692?w=1200&h=800&fit=crop&auto=format"
|
|
44
|
-
alt="Equipe trabalhando com tecnologia"
|
|
45
|
-
className="absolute inset-0 w-full h-full object-cover"
|
|
46
|
-
/>
|
|
47
|
-
<div className="absolute inset-0 bg-[image:var(--gradient-diagonal)] opacity-80" />
|
|
48
|
-
</div>
|
|
49
|
-
|
|
50
|
-
{/* Lado direito - Formulário */}
|
|
51
|
-
<div className="flex-1 flex items-center justify-center px-4 sm:px-6 lg:px-8 lg:flex-none lg:w-1/2 relative bg-muted">
|
|
52
|
-
{/* Seletor de idioma no canto superior direito */}
|
|
53
|
-
<div className="absolute top-4 right-4 z-20">
|
|
54
|
-
<LanguageSelector variant="minimal" showIcon={false} />
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
{/* Gradiente de fundo para mobile */}
|
|
58
|
-
<div className="absolute inset-0 lg:hidden bg-[image:var(--gradient-diagonal)] opacity-10 dark:opacity-5" />
|
|
59
|
-
<div className="w-full max-w-sm space-y-6 relative z-10">
|
|
60
|
-
{/* Header do formulário */}
|
|
61
|
-
<div className="text-center">
|
|
62
|
-
<div className="flex items-center justify-center mb-4">
|
|
63
|
-
<XerticaLogo
|
|
64
|
-
className="h-12 w-auto text-primary dark:text-foreground"
|
|
65
|
-
variant="theme"
|
|
66
|
-
/>
|
|
67
|
-
</div>
|
|
68
|
-
<h2 className="text-sm text-muted-foreground">Faça login em sua conta</h2>
|
|
69
|
-
</div>
|
|
70
|
-
|
|
71
|
-
{/* Formulário */}
|
|
72
|
-
<form className="space-y-6" onSubmit={handleSubmit}>
|
|
73
|
-
<div className="space-y-2">
|
|
74
|
-
<Label htmlFor="email">E-mail</Label>
|
|
75
|
-
<Input
|
|
76
|
-
id="email"
|
|
77
|
-
name="email"
|
|
78
|
-
type="email"
|
|
79
|
-
required
|
|
80
|
-
className="w-full"
|
|
81
|
-
placeholder="seu@email.com"
|
|
82
|
-
value={email}
|
|
83
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
84
|
-
/>
|
|
85
|
-
</div>
|
|
86
|
-
|
|
87
|
-
<div className="space-y-2">
|
|
88
|
-
<Label htmlFor="password">Senha</Label>
|
|
89
|
-
<Input
|
|
90
|
-
id="password"
|
|
91
|
-
name="password"
|
|
92
|
-
type="password"
|
|
93
|
-
required
|
|
94
|
-
className="w-full"
|
|
95
|
-
placeholder="••••••••"
|
|
96
|
-
value={password}
|
|
97
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
98
|
-
/>
|
|
99
|
-
</div>
|
|
100
|
-
|
|
101
|
-
{error && (
|
|
102
|
-
<div className="text-destructive text-sm text-center">
|
|
103
|
-
{error}
|
|
104
|
-
</div>
|
|
105
|
-
)}
|
|
106
|
-
|
|
107
|
-
<Button
|
|
108
|
-
type="submit"
|
|
109
|
-
className="w-full"
|
|
110
|
-
disabled={isLoading}
|
|
111
|
-
>
|
|
112
|
-
{isLoading ? 'Entrando...' : 'Entrar'}
|
|
113
|
-
</Button>
|
|
114
|
-
|
|
115
|
-
{/* Link para recuperação de senha */}
|
|
116
|
-
<div className="text-center">
|
|
117
|
-
<button
|
|
118
|
-
type="button"
|
|
119
|
-
onClick={() => navigate('/forgot-password')}
|
|
120
|
-
className="text-sm text-primary hover:opacity-80 transition-colors"
|
|
121
|
-
>
|
|
122
|
-
Esqueceu sua senha?
|
|
123
|
-
</button>
|
|
124
|
-
</div>
|
|
125
|
-
</form>
|
|
126
|
-
|
|
127
|
-
{/* Divider */}
|
|
128
|
-
<div className="relative">
|
|
129
|
-
<div className="absolute inset-0 flex items-center">
|
|
130
|
-
<div className="w-full border-t border-border"></div>
|
|
131
|
-
</div>
|
|
132
|
-
<div className="relative flex justify-center text-sm">
|
|
133
|
-
<span className="bg-muted px-2 text-muted-foreground">
|
|
134
|
-
ou continuar com
|
|
135
|
-
</span>
|
|
136
|
-
</div>
|
|
137
|
-
</div>
|
|
138
|
-
|
|
139
|
-
{/* Opções de login social/SSO */}
|
|
140
|
-
<div className="space-y-3">
|
|
141
|
-
{/* Google */}
|
|
142
|
-
<Button
|
|
143
|
-
type="button"
|
|
144
|
-
variant="outline"
|
|
145
|
-
className="w-full justify-center"
|
|
146
|
-
onClick={() => handleSocialLogin('Google')}
|
|
147
|
-
>
|
|
148
|
-
<svg className="w-5 h-5 mr-2" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
|
149
|
-
<path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"></path>
|
|
150
|
-
<path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"></path>
|
|
151
|
-
<path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"></path>
|
|
152
|
-
<path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"></path>
|
|
153
|
-
<path fill="none" d="M0 0h48v48H0z"></path>
|
|
154
|
-
</svg>
|
|
155
|
-
<span>Entrar com Google</span>
|
|
156
|
-
</Button>
|
|
157
|
-
|
|
158
|
-
{/* MT Login */}
|
|
159
|
-
<Button
|
|
160
|
-
type="button"
|
|
161
|
-
variant="outline"
|
|
162
|
-
className="w-full justify-center"
|
|
163
|
-
onClick={() => handleSocialLogin('MT Login')}
|
|
164
|
-
>
|
|
165
|
-
<Lock className="w-5 h-5 mr-2 text-[var(--chart-4)]" />
|
|
166
|
-
<span>Entrar com MT Login</span>
|
|
167
|
-
</Button>
|
|
168
|
-
|
|
169
|
-
{/* gov.br */}
|
|
170
|
-
<Button
|
|
171
|
-
type="button"
|
|
172
|
-
variant="outline"
|
|
173
|
-
className="w-full justify-center font-normal"
|
|
174
|
-
onClick={() => handleSocialLogin('gov.br')}
|
|
175
|
-
>
|
|
176
|
-
<span>Entrar com <strong className="font-semibold">gov.br</strong></span>
|
|
177
|
-
</Button>
|
|
178
|
-
</div>
|
|
179
|
-
</div>
|
|
180
|
-
</div>
|
|
181
|
-
</div>
|
|
182
|
-
);
|
|
183
|
-
}
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Button, Input, Label, XerticaLogo, ImageWithFallback, LanguageSelector } from 'xertica-ui';
|
|
3
|
+
import { Lock } from 'lucide-react';
|
|
4
|
+
import { useNavigate } from 'react-router-dom';
|
|
5
|
+
|
|
6
|
+
interface LoginContentProps {
|
|
7
|
+
onLogin: (email: string, password: string) => boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function LoginContent({ onLogin }: LoginContentProps) {
|
|
11
|
+
const navigate = useNavigate();
|
|
12
|
+
const [email, setEmail] = useState('');
|
|
13
|
+
const [password, setPassword] = useState('');
|
|
14
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
15
|
+
const [error, setError] = useState('');
|
|
16
|
+
|
|
17
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
18
|
+
e.preventDefault();
|
|
19
|
+
setError('');
|
|
20
|
+
setIsLoading(true);
|
|
21
|
+
|
|
22
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
23
|
+
|
|
24
|
+
const success = onLogin(email, password);
|
|
25
|
+
|
|
26
|
+
if (!success) {
|
|
27
|
+
setError('Por favor, preencha todos os campos');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setIsLoading(false);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const handleSocialLogin = (provider: string) => {
|
|
34
|
+
console.log(`Login com ${provider}`);
|
|
35
|
+
onLogin('social@user.com', 'social-auth');
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="min-h-screen flex">
|
|
40
|
+
{/* Lado esquerdo - Imagem de fundo completa */}
|
|
41
|
+
<div className="hidden lg:flex lg:flex-1 relative overflow-hidden">
|
|
42
|
+
<ImageWithFallback
|
|
43
|
+
src="https://images.unsplash.com/photo-1551434678-e076c223a692?w=1200&h=800&fit=crop&auto=format"
|
|
44
|
+
alt="Equipe trabalhando com tecnologia"
|
|
45
|
+
className="absolute inset-0 w-full h-full object-cover"
|
|
46
|
+
/>
|
|
47
|
+
<div className="absolute inset-0 bg-[image:var(--gradient-diagonal)] opacity-80" />
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
{/* Lado direito - Formulário */}
|
|
51
|
+
<div className="flex-1 flex items-center justify-center px-4 sm:px-6 lg:px-8 lg:flex-none lg:w-1/2 relative bg-muted">
|
|
52
|
+
{/* Seletor de idioma no canto superior direito */}
|
|
53
|
+
<div className="absolute top-4 right-4 z-20">
|
|
54
|
+
<LanguageSelector variant="minimal" showIcon={false} />
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
{/* Gradiente de fundo para mobile */}
|
|
58
|
+
<div className="absolute inset-0 lg:hidden bg-[image:var(--gradient-diagonal)] opacity-10 dark:opacity-5" />
|
|
59
|
+
<div className="w-full max-w-sm space-y-6 relative z-10">
|
|
60
|
+
{/* Header do formulário */}
|
|
61
|
+
<div className="text-center">
|
|
62
|
+
<div className="flex items-center justify-center mb-4">
|
|
63
|
+
<XerticaLogo
|
|
64
|
+
className="h-12 w-auto text-primary dark:text-foreground"
|
|
65
|
+
variant="theme"
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
<h2 className="text-sm text-muted-foreground">Faça login em sua conta</h2>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
{/* Formulário */}
|
|
72
|
+
<form className="space-y-6" onSubmit={handleSubmit}>
|
|
73
|
+
<div className="space-y-2">
|
|
74
|
+
<Label htmlFor="email">E-mail</Label>
|
|
75
|
+
<Input
|
|
76
|
+
id="email"
|
|
77
|
+
name="email"
|
|
78
|
+
type="email"
|
|
79
|
+
required
|
|
80
|
+
className="w-full"
|
|
81
|
+
placeholder="seu@email.com"
|
|
82
|
+
value={email}
|
|
83
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div className="space-y-2">
|
|
88
|
+
<Label htmlFor="password">Senha</Label>
|
|
89
|
+
<Input
|
|
90
|
+
id="password"
|
|
91
|
+
name="password"
|
|
92
|
+
type="password"
|
|
93
|
+
required
|
|
94
|
+
className="w-full"
|
|
95
|
+
placeholder="••••••••"
|
|
96
|
+
value={password}
|
|
97
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{error && (
|
|
102
|
+
<div className="text-destructive text-sm text-center">
|
|
103
|
+
{error}
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
|
|
107
|
+
<Button
|
|
108
|
+
type="submit"
|
|
109
|
+
className="w-full"
|
|
110
|
+
disabled={isLoading}
|
|
111
|
+
>
|
|
112
|
+
{isLoading ? 'Entrando...' : 'Entrar'}
|
|
113
|
+
</Button>
|
|
114
|
+
|
|
115
|
+
{/* Link para recuperação de senha */}
|
|
116
|
+
<div className="text-center">
|
|
117
|
+
<button
|
|
118
|
+
type="button"
|
|
119
|
+
onClick={() => navigate('/forgot-password')}
|
|
120
|
+
className="text-sm text-primary hover:opacity-80 transition-colors"
|
|
121
|
+
>
|
|
122
|
+
Esqueceu sua senha?
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
</form>
|
|
126
|
+
|
|
127
|
+
{/* Divider */}
|
|
128
|
+
<div className="relative">
|
|
129
|
+
<div className="absolute inset-0 flex items-center">
|
|
130
|
+
<div className="w-full border-t border-border"></div>
|
|
131
|
+
</div>
|
|
132
|
+
<div className="relative flex justify-center text-sm">
|
|
133
|
+
<span className="bg-muted px-2 text-muted-foreground">
|
|
134
|
+
ou continuar com
|
|
135
|
+
</span>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
{/* Opções de login social/SSO */}
|
|
140
|
+
<div className="space-y-3">
|
|
141
|
+
{/* Google */}
|
|
142
|
+
<Button
|
|
143
|
+
type="button"
|
|
144
|
+
variant="outline"
|
|
145
|
+
className="w-full justify-center"
|
|
146
|
+
onClick={() => handleSocialLogin('Google')}
|
|
147
|
+
>
|
|
148
|
+
<svg className="w-5 h-5 mr-2" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
|
149
|
+
<path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"></path>
|
|
150
|
+
<path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"></path>
|
|
151
|
+
<path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"></path>
|
|
152
|
+
<path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"></path>
|
|
153
|
+
<path fill="none" d="M0 0h48v48H0z"></path>
|
|
154
|
+
</svg>
|
|
155
|
+
<span>Entrar com Google</span>
|
|
156
|
+
</Button>
|
|
157
|
+
|
|
158
|
+
{/* MT Login */}
|
|
159
|
+
<Button
|
|
160
|
+
type="button"
|
|
161
|
+
variant="outline"
|
|
162
|
+
className="w-full justify-center"
|
|
163
|
+
onClick={() => handleSocialLogin('MT Login')}
|
|
164
|
+
>
|
|
165
|
+
<Lock className="w-5 h-5 mr-2 text-[var(--chart-4)]" />
|
|
166
|
+
<span>Entrar com MT Login</span>
|
|
167
|
+
</Button>
|
|
168
|
+
|
|
169
|
+
{/* gov.br */}
|
|
170
|
+
<Button
|
|
171
|
+
type="button"
|
|
172
|
+
variant="outline"
|
|
173
|
+
className="w-full justify-center font-normal"
|
|
174
|
+
onClick={() => handleSocialLogin('gov.br')}
|
|
175
|
+
>
|
|
176
|
+
<span>Entrar com <strong className="font-semibold">gov.br</strong></span>
|
|
177
|
+
</Button>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { LoginContent } from './Login/LoginContent';
|
|
3
|
+
|
|
4
|
+
interface LoginPageProps {
|
|
5
|
+
onLogin: (email: string, password: string) => boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function LoginPage({ onLogin }: LoginPageProps) {
|
|
9
|
+
return <LoginContent onLogin={onLogin} />;
|
|
10
|
+
}
|
|
@@ -3,7 +3,7 @@ import { Button, Input, Label, XerticaLogo, ImageWithFallback, LanguageSelector
|
|
|
3
3
|
import { ArrowLeft, CheckCircle2, AlertCircle } from 'lucide-react';
|
|
4
4
|
import { useNavigate } from 'react-router-dom';
|
|
5
5
|
|
|
6
|
-
export function
|
|
6
|
+
export function ResetPasswordContent() {
|
|
7
7
|
const navigate = useNavigate();
|
|
8
8
|
const [password, setPassword] = useState('');
|
|
9
9
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
XerticaAssistant,
|
|
6
6
|
useLayout,
|
|
7
7
|
} from 'xertica-ui';
|
|
8
|
-
import { TemplateContent } from '
|
|
8
|
+
import { TemplateContent } from './Template/TemplateContent';
|
|
9
9
|
import { routes } from '../routes';
|
|
10
10
|
|
|
11
11
|
interface TemplatePageProps {
|
package/templates/src/{pages/VerifyEmailPage.tsx → app/pages/VerifyEmail/VerifyEmailContent.tsx}
RENAMED
|
@@ -3,7 +3,7 @@ import { Button, XerticaLogo, ImageWithFallback, LanguageSelector } from 'xertic
|
|
|
3
3
|
import { ArrowLeft, Mail, CheckCircle2, Lock } from 'lucide-react';
|
|
4
4
|
import { useNavigate, useLocation } from 'react-router-dom';
|
|
5
5
|
|
|
6
|
-
export function
|
|
6
|
+
export function VerifyEmailContent() {
|
|
7
7
|
const navigate = useNavigate();
|
|
8
8
|
const location = useLocation();
|
|
9
9
|
const email = location.state?.email || 'seu@email.com';
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|