xertica-ui 1.0.0 → 1.1.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/bin/cli.ts CHANGED
@@ -96,6 +96,11 @@ program
96
96
  }
97
97
  }
98
98
 
99
+ // Create .env.example
100
+ const envExampleContent = `VITE_GOOGLE_MAPS_API_KEY=
101
+ VITE_GEMINI_API_KEY=`;
102
+ await fs.writeFile(path.join(targetDir, '.env.example'), envExampleContent);
103
+
99
104
  // Handle App.tsx specifically
100
105
  const pages = response.pages || [];
101
106
  const hasLogin = pages.includes('login');
@@ -190,4 +195,80 @@ export default function App() {
190
195
  }
191
196
  });
192
197
 
198
+ program
199
+ .command('update')
200
+ .description('Update components in your project')
201
+ .argument('[components...]', 'Components to update')
202
+ .option('-a, --all', 'Update all installed components')
203
+ .action(async (components, options) => {
204
+ const targetDir = process.cwd();
205
+ const sourceRoot = path.resolve(__dirname, '../');
206
+ const uiComponentsDir = path.join(targetDir, 'components/ui');
207
+ const sourceUiDir = path.join(sourceRoot, 'components/ui');
208
+
209
+ if (!await fs.pathExists(uiComponentsDir)) {
210
+ console.error(chalk.red('Components directory not found. Is this a Xertica UI project?'));
211
+ return;
212
+ }
213
+
214
+ if (!await fs.pathExists(sourceUiDir)) {
215
+ console.error(chalk.red('Source components not found. Try reinstalling the CLI package.'));
216
+ return;
217
+ }
218
+
219
+ let componentsToUpdate: string[] = [];
220
+
221
+ if (options.all) {
222
+ const files = await fs.readdir(uiComponentsDir);
223
+ componentsToUpdate = files.filter(f => f.endsWith('.tsx'));
224
+ } else if (components && components.length > 0) {
225
+ componentsToUpdate = components.map((c: string) => c.endsWith('.tsx') ? c : `${c}.tsx`);
226
+ } else {
227
+ // Interactive mode
228
+ const files = await fs.readdir(uiComponentsDir);
229
+ const installedComponents = files.filter(f => f.endsWith('.tsx'));
230
+
231
+ if (installedComponents.length === 0) {
232
+ console.log(chalk.yellow('No components found to update.'));
233
+ return;
234
+ }
235
+
236
+ const response = await prompts({
237
+ type: 'multiselect',
238
+ name: 'selected',
239
+ message: 'Select components to update',
240
+ choices: installedComponents.map(f => ({ title: f, value: f })),
241
+ min: 1
242
+ });
243
+
244
+ if (!response.selected) return;
245
+ componentsToUpdate = response.selected;
246
+ }
247
+
248
+ const spinner = ora('Updating components...').start();
249
+
250
+ try {
251
+ let updatedCount = 0;
252
+ for (const component of componentsToUpdate) {
253
+ const srcPath = path.join(sourceUiDir, component);
254
+ const destPath = path.join(uiComponentsDir, component);
255
+
256
+ if (await fs.pathExists(srcPath)) {
257
+ await fs.copy(srcPath, destPath);
258
+ updatedCount++;
259
+ } else {
260
+ spinner.warn(`Component ${component} not found in source.`);
261
+ }
262
+ }
263
+ if (updatedCount > 0) {
264
+ spinner.succeed(`Successfully updated ${updatedCount} components!`);
265
+ } else {
266
+ spinner.info('No components updated.');
267
+ }
268
+ } catch (error) {
269
+ spinner.fail('Failed to update components');
270
+ console.error(error);
271
+ }
272
+ });
273
+
193
274
  program.parse();
@@ -29,14 +29,19 @@ declare global {
29
29
  * Função auxiliar para ler a API key do localStorage de forma síncrona
30
30
  */
31
31
  function getInitialApiKey(): string | undefined {
32
+ // 1. Check Environment Variable (Vite)
33
+ if (typeof import.meta !== 'undefined' && import.meta.env && import.meta.env.VITE_GOOGLE_MAPS_API_KEY) {
34
+ return import.meta.env.VITE_GOOGLE_MAPS_API_KEY as string;
35
+ }
36
+
32
37
  if (typeof window === 'undefined') return undefined;
33
-
38
+
34
39
  const savedKey = localStorage.getItem('xertica-googlemaps-api-key');
35
-
40
+
36
41
  if (savedKey && savedKey.trim().length > 0) {
37
42
  return savedKey;
38
43
  }
39
-
44
+
40
45
  return undefined;
41
46
  }
42
47
 
@@ -45,23 +50,23 @@ function getInitialApiKey(): string | undefined {
45
50
  */
46
51
  function removeExistingScript(): void {
47
52
  if (typeof window === 'undefined') return;
48
-
53
+
49
54
  // Remover callback global se existir
50
55
  if ((window as any).__googleMapsCallback) {
51
56
  delete (window as any).__googleMapsCallback;
52
57
  }
53
-
58
+
54
59
  // Remover script existente
55
60
  const existingScript = document.querySelector(`script[src*=\"maps.googleapis.com/maps/api/js\"]`);
56
61
  if (existingScript) {
57
62
  existingScript.remove();
58
63
  }
59
-
64
+
60
65
  // Limpar Google Maps do window
61
66
  if ((window as any).google?.maps) {
62
67
  delete (window as any).google.maps;
63
68
  }
64
-
69
+
65
70
  // Limpar singleton para permitir novo carregamento
66
71
  if (window.__XERTICA_GOOGLE_MAPS_LOADER__) {
67
72
  window.__XERTICA_GOOGLE_MAPS_LOADER__.isLoaded = false;
@@ -121,7 +126,7 @@ function loadGoogleMapsScript(apiKey?: string): Promise<void> {
121
126
  reject(new Error('API key changed. Please reload the page to apply changes.'));
122
127
  return;
123
128
  }
124
-
129
+
125
130
  // Aguardar o script carregar
126
131
  if (isGoogleMapsAlreadyLoaded() && isMarkerLibraryLoaded()) {
127
132
  resolve();
@@ -175,7 +180,7 @@ function loadGoogleMapsScript(apiKey?: string): Promise<void> {
175
180
  });
176
181
 
177
182
  document.head.appendChild(script);
178
-
183
+
179
184
  // Salvar referência ao script
180
185
  const singleton = getOrCreateSingleton();
181
186
  if (singleton) {
@@ -189,17 +194,17 @@ function loadGoogleMapsScript(apiKey?: string): Promise<void> {
189
194
  */
190
195
  function getOrCreateSingleton() {
191
196
  if (typeof window === 'undefined') return null;
192
-
197
+
193
198
  if (!window.__XERTICA_GOOGLE_MAPS_LOADER__) {
194
199
  const isPreloaded = isGoogleMapsAlreadyLoaded();
195
-
200
+
196
201
  window.__XERTICA_GOOGLE_MAPS_LOADER__ = {
197
202
  isLoaded: isPreloaded,
198
203
  loadError: undefined,
199
204
  listeners: new Set(),
200
205
  };
201
206
  }
202
-
207
+
203
208
  return window.__XERTICA_GOOGLE_MAPS_LOADER__;
204
209
  }
205
210
 
@@ -209,10 +214,10 @@ function getOrCreateSingleton() {
209
214
  function updateSingleton(state: Partial<GoogleMapsContextType>) {
210
215
  const singleton = getOrCreateSingleton();
211
216
  if (!singleton) return;
212
-
217
+
213
218
  if (state.isLoaded !== undefined) singleton.isLoaded = state.isLoaded;
214
219
  if (state.loadError !== undefined) singleton.loadError = state.loadError;
215
-
220
+
216
221
  const newState = { isLoaded: singleton.isLoaded, loadError: singleton.loadError };
217
222
  singleton.listeners.forEach(listener => listener(newState));
218
223
  }
@@ -224,26 +229,26 @@ const SingletonLoaderWrapper = ({ children }: { children: ReactNode }) => {
224
229
  const [state, setState] = useState<GoogleMapsContextType>(() => {
225
230
  const singleton = getOrCreateSingleton();
226
231
  if (!singleton) return { isLoaded: false, loadError: undefined };
227
-
232
+
228
233
  return {
229
234
  isLoaded: singleton.isLoaded,
230
235
  loadError: singleton.loadError,
231
236
  };
232
237
  });
233
-
238
+
234
239
  useEffect(() => {
235
240
  const singleton = getOrCreateSingleton();
236
241
  if (!singleton) return;
237
-
242
+
238
243
  const listener = (newState: GoogleMapsContextType) => {
239
244
  setState(newState);
240
245
  };
241
-
246
+
242
247
  singleton.listeners.add(listener);
243
-
248
+
244
249
  // Sincronizar estado inicial
245
250
  listener({ isLoaded: singleton.isLoaded, loadError: singleton.loadError });
246
-
251
+
247
252
  return () => {
248
253
  singleton.listeners.delete(listener);
249
254
  };
@@ -281,16 +286,16 @@ const LoaderInitializer = () => {
281
286
  hasInitializedRef.current = true;
282
287
 
283
288
  const apiKey = getInitialApiKey();
284
-
289
+
285
290
  // Se não houver API key, apenas marcar como não carregado (sem erro)
286
291
  if (!apiKey) {
287
- updateSingleton({
288
- isLoaded: false,
292
+ updateSingleton({
293
+ isLoaded: false,
289
294
  loadError: undefined // Não definir erro quando não há API key
290
295
  });
291
296
  return;
292
297
  }
293
-
298
+
294
299
  loadGoogleMapsScript(apiKey)
295
300
  .then(() => {
296
301
  updateSingleton({ isLoaded: true, loadError: undefined });
@@ -313,13 +318,13 @@ export const GoogleMapsLoaderProvider = ({ children }: { children: ReactNode })
313
318
  const [shouldInitialize] = useState(() => {
314
319
  const singleton = getOrCreateSingleton();
315
320
  if (!singleton) return false;
316
-
321
+
317
322
  // Se já está carregado, não inicializar
318
323
  if (singleton.isLoaded || isGoogleMapsAlreadyLoaded() || isMarkerLibraryLoaded()) {
319
324
  singleton.isLoaded = true;
320
325
  return false;
321
326
  }
322
-
327
+
323
328
  return true;
324
329
  });
325
330
 
@@ -112,7 +112,7 @@ const MapContent = React.forwardRef<HTMLDivElement, MapProps & { apiKey: string
112
112
  });
113
113
 
114
114
  mapRef.current = map;
115
-
115
+
116
116
  if (onMapLoad) {
117
117
  onMapLoad(map);
118
118
  }
@@ -147,7 +147,7 @@ const MapContent = React.forwardRef<HTMLDivElement, MapProps & { apiKey: string
147
147
 
148
148
  const iconContainer = document.createElement('div');
149
149
  iconContainer.className = 'flex items-center justify-center rotate-45';
150
-
150
+
151
151
  if (markerData.iconSvg) {
152
152
  const div = document.createElement('div');
153
153
  div.innerHTML = markerData.iconSvg;
@@ -367,18 +367,18 @@ MapContent.displayName = "MapContent";
367
367
 
368
368
  export const Map = React.forwardRef<HTMLDivElement, MapProps>(
369
369
  (props, ref) => {
370
- const effectiveApiKey = props.apiKey || "";
370
+ const effectiveApiKey = props.apiKey || (typeof import.meta !== 'undefined' && import.meta.env && import.meta.env.VITE_GOOGLE_MAPS_API_KEY) || "";
371
371
 
372
- const isValidKey = effectiveApiKey &&
373
- effectiveApiKey !== "YOUR_GOOGLE_MAPS_API_KEY_HERE" &&
374
- effectiveApiKey.startsWith("AIza");
372
+ const isValidKey = effectiveApiKey &&
373
+ effectiveApiKey !== "YOUR_GOOGLE_MAPS_API_KEY_HERE" &&
374
+ effectiveApiKey.startsWith("AIza");
375
375
 
376
376
  if (!isValidKey) {
377
- const {
378
- center, zoom, markers, circle, polygon, height, apiKey,
379
- mapContainerClassName, disableDefaultUI, zoomControl,
380
- streetViewControl, mapTypeControl, fullscreenControl,
381
- gestureHandling, layers, ...divProps
377
+ const {
378
+ center, zoom, markers, circle, polygon, height, apiKey,
379
+ mapContainerClassName, disableDefaultUI, zoomControl,
380
+ streetViewControl, mapTypeControl, fullscreenControl,
381
+ gestureHandling, layers, ...divProps
382
382
  } = props;
383
383
 
384
384
  return (
@@ -91,7 +91,7 @@ const RouteMapContent = React.forwardRef<HTMLDivElement, RouteMapProps & { apiKe
91
91
  useEffect(() => {
92
92
  const map = mapRef.current;
93
93
  const renderer = directionsRendererRef.current;
94
-
94
+
95
95
  if (!map || !renderer || !isLoaded || isCalculatingRef.current) return;
96
96
  if (!origin || !destination) return;
97
97
 
@@ -111,28 +111,28 @@ const RouteMapContent = React.forwardRef<HTMLDivElement, RouteMapProps & { apiKe
111
111
 
112
112
  directionsService.route(request, (result, status) => {
113
113
  isCalculatingRef.current = false;
114
-
114
+
115
115
  if (status === 'OK' && result) {
116
116
  renderer.setDirections(result);
117
-
117
+
118
118
  // Calculate total distance and duration
119
119
  const route = result.routes[0];
120
120
  if (route?.legs?.length > 0 && onRouteCalculated) {
121
121
  let totalDistance = 0;
122
122
  let totalDuration = 0;
123
-
123
+
124
124
  route.legs.forEach(leg => {
125
125
  if (leg.distance) totalDistance += leg.distance.value;
126
126
  if (leg.duration) totalDuration += leg.duration.value;
127
127
  });
128
-
128
+
129
129
  const distanceKm = (totalDistance / 1000).toFixed(1);
130
130
  const distanceText = `${distanceKm} km`;
131
-
131
+
132
132
  const hours = Math.floor(totalDuration / 3600);
133
133
  const minutes = Math.floor((totalDuration % 3600) / 60);
134
134
  const durationText = hours > 0 ? `${hours}h ${minutes}min` : `${minutes} min`;
135
-
135
+
136
136
  onRouteCalculated(distanceText, durationText);
137
137
  }
138
138
  }
@@ -201,18 +201,18 @@ RouteMapContent.displayName = "RouteMapContent";
201
201
 
202
202
  export const RouteMap = React.forwardRef<HTMLDivElement, RouteMapProps>(
203
203
  (props, ref) => {
204
- const effectiveApiKey = props.apiKey || "";
204
+ const effectiveApiKey = props.apiKey || (typeof import.meta !== 'undefined' && import.meta.env && import.meta.env.VITE_GOOGLE_MAPS_API_KEY) || "";
205
205
 
206
- const isValidKey = effectiveApiKey &&
207
- effectiveApiKey !== "YOUR_GOOGLE_MAPS_API_KEY_HERE" &&
208
- effectiveApiKey.startsWith("AIza");
206
+ const isValidKey = effectiveApiKey &&
207
+ effectiveApiKey !== "YOUR_GOOGLE_MAPS_API_KEY_HERE" &&
208
+ effectiveApiKey.startsWith("AIza");
209
209
 
210
210
  if (!isValidKey) {
211
- const {
211
+ const {
212
212
  origin, destination, waypoints, travelMode, height, apiKey,
213
213
  mapContainerClassName, disableDefaultUI, zoomControl,
214
214
  streetViewControl, mapTypeControl, fullscreenControl,
215
- onRouteCalculated, ...divProps
215
+ onRouteCalculated, ...divProps
216
216
  } = props;
217
217
 
218
218
  return (