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 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
- // Always needed pages
133
- const alwaysCopyPages = ['LoginPage.tsx', 'ForgotPasswordPage.tsx', 'VerifyEmailPage.tsx', 'ResetPasswordPage.tsx'];
134
- const optionalPages: Record<string, boolean> = {
135
- 'LoginPage.tsx': hasLogin,
136
- 'ForgotPasswordPage.tsx': hasLogin,
137
- 'VerifyEmailPage.tsx': hasLogin,
138
- 'ResetPasswordPage.tsx': hasLogin,
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 [file, shouldCopy] of Object.entries(optionalPages)) {
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 srcPath = path.join(srcPagesDir, file);
147
- if (await fs.pathExists(srcPath)) {
148
- await fs.copy(srcPath, path.join(targetDir, 'src', 'pages', file));
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 [file, shouldCopy] of Object.entries(optionalPages)) {
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 srcPath = path.join(srcPagesDir, file);
601
- if (await fs.pathExists(srcPath)) {
602
- await fs.copy(srcPath, path.join(targetDir, "src", "pages", file));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xertica-ui",
3
- "version": "1.4.10",
3
+ "version": "1.4.11",
4
4
  "description": "Xertica UI - Design System completo com componentes React e Tailwind CSS",
5
5
  "type": "module",
6
6
  "main": "./dist/index.umd.js",
@@ -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.
@@ -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
 
@@ -8,6 +8,6 @@
8
8
  </head>
9
9
  <body>
10
10
  <div id="root"></div>
11
- <script type="module" src="/src/main.tsx"></script>
11
+ <script type="module" src="/src/app/main.tsx"></script>
12
12
  </body>
13
13
  </html>
@@ -11,7 +11,7 @@
11
11
  "type-check": "tsc --noEmit"
12
12
  },
13
13
  "dependencies": {
14
- "xertica-ui": "^1.4.10",
14
+ "xertica-ui": "^1.4.11",
15
15
  "react": "^18.3.1",
16
16
  "react-dom": "^18.3.1",
17
17
  "react-router-dom": "^7.1.3",
@@ -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';
@@ -1,6 +1,6 @@
1
1
  import { StrictMode } from 'react';
2
2
  import { createRoot } from 'react-dom/client';
3
- import '../index.css';
3
+ import '../../index.css';
4
4
  import App from './App';
5
5
 
6
6
  createRoot(document.getElementById('root')!).render(
@@ -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 ForgotPasswordPage() {
6
+ export function ForgotPasswordContent() {
7
7
  const navigate = useNavigate();
8
8
  const [email, setEmail] = useState('');
9
9
  const [isLoading, setIsLoading] = useState(false);
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import { ForgotPasswordContent } from './ForgotPassword/ForgotPasswordContent';
3
+
4
+ export function ForgotPasswordPage() {
5
+ return <ForgotPasswordContent />;
6
+ }
@@ -6,7 +6,7 @@ import {
6
6
  useLayout,
7
7
  generateDemoResponse,
8
8
  } from 'xertica-ui';
9
- import { HomeContent } from '../content/HomeContent';
9
+ import { HomeContent } from './Home/HomeContent';
10
10
  import { routes } from '../routes';
11
11
 
12
12
  const richSuggestions = [
@@ -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 LoginPageProps {
7
- onLogin: (email: string, password: string) => boolean;
8
- }
9
-
10
- export function LoginPage({ onLogin }: LoginPageProps) {
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 ResetPasswordPage() {
6
+ export function ResetPasswordContent() {
7
7
  const navigate = useNavigate();
8
8
  const [password, setPassword] = useState('');
9
9
  const [confirmPassword, setConfirmPassword] = useState('');
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import { ResetPasswordContent } from './ResetPassword/ResetPasswordContent';
3
+
4
+ export function ResetPasswordPage() {
5
+ return <ResetPasswordContent />;
6
+ }
@@ -5,7 +5,7 @@ import {
5
5
  XerticaAssistant,
6
6
  useLayout,
7
7
  } from 'xertica-ui';
8
- import { TemplateContent } from '../content/TemplateContent';
8
+ import { TemplateContent } from './Template/TemplateContent';
9
9
  import { routes } from '../routes';
10
10
 
11
11
  interface TemplatePageProps {
@@ -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 VerifyEmailPage() {
6
+ export function VerifyEmailContent() {
7
7
  const navigate = useNavigate();
8
8
  const location = useLocation();
9
9
  const email = location.state?.email || 'seu@email.com';
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import { VerifyEmailContent } from './VerifyEmail/VerifyEmailContent';
3
+
4
+ export function VerifyEmailPage() {
5
+ return <VerifyEmailContent />;
6
+ }
File without changes