Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions scripts/contract-testing.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import * as dotenv from 'dotenv';
dotenv.config();

const SUPABASE_URL = process.env.SUPABASE_URL || "https://pqpdolkaeqlyzpdpbizo.supabase.co";
// Usando a chave de simulação estável definida para este projeto
const SERVICE_ROLE_KEY = "a46c3981-244a-4f81-9f57-bab5c45b5cde";
// Chave de simulação SOMENTE via env — nunca hardcoded no repositório.
const SERVICE_ROLE_KEY = process.env.SIMULATION_BYPASS_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY;
if (!SERVICE_ROLE_KEY) {
console.error("❌ Defina SIMULATION_BYPASS_KEY (ou SUPABASE_SERVICE_ROLE_KEY) no .env antes de rodar o contract-testing.");
process.exit(2);
}

const CONTRACTS = [
{
Expand Down
6 changes: 5 additions & 1 deletion scripts/massive-load-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import * as dotenv from 'dotenv';
dotenv.config();

const SUPABASE_URL = process.env.SUPABASE_URL || "https://pqpdolkaeqlyzpdpbizo.supabase.co";
const SERVICE_ROLE_KEY = "a46c3981-244a-4f81-9f57-bab5c45b5cde";
const SERVICE_ROLE_KEY = process.env.SIMULATION_BYPASS_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY;
if (!SERVICE_ROLE_KEY) {
console.error("❌ Defina SIMULATION_BYPASS_KEY (ou SUPABASE_SERVICE_ROLE_KEY) no .env antes de rodar o load-test.");
process.exit(2);
}

const CONCURRENCY = 5;
const TOTAL_REQUESTS = 25;
Expand Down
7 changes: 2 additions & 5 deletions src/components/common/EnhancedSpotlight.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { useOnboardingContext } from "@/contexts/OnboardingContext";
import { useOptionalOnboardingContext } from '@/contexts/OnboardingContext';
import { useNavigate } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion';
import Fuse from 'fuse.js';
Expand All @@ -19,10 +19,7 @@ export function EnhancedSpotlight() {
const inputRef = useRef<HTMLInputElement>(null);
const navigate = useNavigate();
const { isDev, isAdmin } = useAuth();
let onboarding: any = null;
try {
onboarding = useOnboardingContext();
} catch (e) {}
const onboarding = useOptionalOnboardingContext();

const handleRestartTour = () => {
if (onboarding) {
Expand Down
23 changes: 10 additions & 13 deletions src/components/layout/sidebar/SidebarBrandHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { forwardRef } from "react";
import { AppLogo } from "../AppLogo";
import { useOnboardingContext } from "@/contexts/OnboardingContext";
import { useNavigate } from "react-router-dom";
import { forwardRef } from 'react';
import { AppLogo } from '../AppLogo';
import { useOptionalOnboardingContext } from '@/contexts/OnboardingContext';
import { useNavigate } from 'react-router-dom';

interface SidebarBrandHeaderProps {
isCollapsed: boolean;
Expand All @@ -10,32 +10,29 @@ interface SidebarBrandHeaderProps {
export const SidebarBrandHeader = forwardRef<HTMLDivElement, SidebarBrandHeaderProps>(
({ isCollapsed }, ref) => {
const navigate = useNavigate();
let onboarding: any = null;
try {
onboarding = useOnboardingContext();
} catch (e) {}
const onboarding = useOptionalOnboardingContext();

const handleLogoClick = () => {
navigate("/");
navigate('/');
if (onboarding && !isCollapsed) {
onboarding.restartTour();
}
};

if (isCollapsed) {
return (
<div ref={ref} className="flex flex-col items-center justify-center py-6 mb-2">
<div ref={ref} className="mb-2 flex flex-col items-center justify-center py-6">
<AppLogo showText={false} variant="sidebar" onClick={handleLogoClick} />
</div>
);
}

return (
<div ref={ref} className="px-4 py-5 mb-2">
<div ref={ref} className="mb-2 px-4 py-5">
<AppLogo variant="sidebar" textClassName="text-sm" onClick={handleLogoClick} />
</div>
);
}
},
);

SidebarBrandHeader.displayName = "SidebarBrandHeader";
SidebarBrandHeader.displayName = 'SidebarBrandHeader';
140 changes: 91 additions & 49 deletions src/components/ui/ShortcutsHelpDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
import { useState, useEffect } from "react";
import { useState, useEffect } from 'react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@/components/ui/dialog";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Command, Search, ShoppingCart, Plus, MessageSquare, Package, SlidersHorizontal, ImagePlus, Calculator, PlayCircle } from "lucide-react";
import { useOnboardingContext } from "@/contexts/OnboardingContext";
} from '@/components/ui/dialog';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
Command,
Search,
ShoppingCart,
Plus,
MessageSquare,
Package,
SlidersHorizontal,
ImagePlus,
Calculator,
PlayCircle,
type LucideIcon,
} from 'lucide-react';
import { useOptionalOnboardingContext } from '@/contexts/OnboardingContext';

export function ShortcutsHelpDialog() {
const [open, setOpen] = useState(false);
let onboarding: any = null;
try {
onboarding = useOnboardingContext();
} catch (e) {}
const onboarding = useOptionalOnboardingContext();

const handleRestartTour = () => {
if (onboarding) {
Expand All @@ -29,74 +38,90 @@ export function ShortcutsHelpDialog() {
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Open with '?' if not in an input
if (e.key === "?" && !e.ctrlKey && !e.metaKey && !e.altKey) {
if (e.key === '?' && !e.ctrlKey && !e.metaKey && !e.altKey) {
const target = e.target as HTMLElement;
const isInput =
target.tagName === "INPUT" ||
target.tagName === "TEXTAREA" ||
target.isContentEditable;

target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;

if (!isInput) {
setOpen(true);
}
}
};

window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, []);

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="max-w-2xl border-primary/20 bg-background/95 backdrop-blur-xl">
<DialogHeader>
<div className="flex items-center gap-3 mb-2">
<div className="p-2 bg-primary/10 rounded-lg">
<div className="mb-2 flex items-center gap-3">
<div className="rounded-lg bg-primary/10 p-2">
<Command className="h-5 w-5 text-primary" />
</div>
<DialogTitle className="text-xl font-display">Atalhos de Teclado</DialogTitle>
<DialogTitle className="font-display text-xl">Atalhos de Teclado</DialogTitle>
</div>
<DialogDescription>
Aumente sua produtividade usando os comandos rápidos do sistema.
</DialogDescription>
</DialogHeader>

<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
<div className="mt-4 grid grid-cols-1 gap-6 md:grid-cols-2">
{/* Global Group */}
<div className="space-y-4">
<h3 className="text-xs font-bold uppercase tracking-wider text-muted-foreground px-1">Comandos Globais</h3>
<h3 className="px-1 text-xs font-bold uppercase tracking-wider text-muted-foreground">
Comandos Globais
</h3>
<div className="space-y-3">
<ShortcutItem icon={Search} label="Buscar Produto / Comando" keys={["Ctrl", "K"]} />
<ShortcutItem icon={MessageSquare} label="Assistente Oracle IA" keys={["Ctrl", "J"]} />
<ShortcutItem icon={Plus} label="Novo Orçamento" keys={["Ctrl", "Shift", "N"]} />
<ShortcutItem icon={ShoppingCart} label="Abrir Carrinho" keys={["Ctrl", "Shift", "C"]} />
<ShortcutItem icon={Command} label="Ver Atalhos" keys={["?"]} />
<ShortcutItem icon={Search} label="Buscar Produto / Comando" keys={['Ctrl', 'K']} />
<ShortcutItem
icon={MessageSquare}
label="Assistente Oracle IA"
keys={['Ctrl', 'J']}
/>
<ShortcutItem icon={Plus} label="Novo Orçamento" keys={['Ctrl', 'Shift', 'N']} />
<ShortcutItem
icon={ShoppingCart}
label="Abrir Carrinho"
keys={['Ctrl', 'Shift', 'C']}
/>
<ShortcutItem icon={Command} label="Ver Atalhos" keys={['?']} />
</div>
</div>

{/* Navigation Group (Alt) */}
<div className="space-y-4">
<h3 className="text-xs font-bold uppercase tracking-wider text-muted-foreground px-1">Navegação Rápida</h3>
<h3 className="px-1 text-xs font-bold uppercase tracking-wider text-muted-foreground">
Navegação Rápida
</h3>
<div className="space-y-3">
<ShortcutItem icon={Package} label="Ir para Catálogo" keys={["Alt", "P"]} />
<ShortcutItem icon={Plus} label="Novo Orçamento" keys={["Alt", "N"]} />
<ShortcutItem icon={SlidersHorizontal} label="Super Filtro" keys={["Alt", "F"]} />
<ShortcutItem icon={ImagePlus} label="Gerador de Mockup" keys={["Alt", "M"]} />
<ShortcutItem icon={Calculator} label="Simulador" keys={["Alt", "S"]} />
<ShortcutItem icon={Package} label="Ir para Meus Kits" keys={["G", "K"]} sub="Sequência" />
<ShortcutItem icon={Package} label="Ir para Catálogo" keys={['Alt', 'P']} />
<ShortcutItem icon={Plus} label="Novo Orçamento" keys={['Alt', 'N']} />
<ShortcutItem icon={SlidersHorizontal} label="Super Filtro" keys={['Alt', 'F']} />
<ShortcutItem icon={ImagePlus} label="Gerador de Mockup" keys={['Alt', 'M']} />
<ShortcutItem icon={Calculator} label="Simulador" keys={['Alt', 'S']} />
<ShortcutItem
icon={Package}
label="Ir para Meus Kits"
keys={['G', 'K']}
sub="Sequência"
/>
</div>
</div>
</div>

<DialogFooter className="sm:justify-between items-center mt-6 pt-4 border-t border-border/50">
<p className="text-[10px] text-muted-foreground italic text-center sm:text-left mb-4 sm:mb-0">
Dica: Digite <code className="text-primary font-bold">/</code> na busca para ver comandos operacionais.
<DialogFooter className="mt-6 items-center border-t border-border/50 pt-4 sm:justify-between">
<p className="mb-4 text-center text-[10px] italic text-muted-foreground sm:mb-0 sm:text-left">
Dica: Digite <code className="font-bold text-primary">/</code> na busca para ver
comandos operacionais.
</p>
<Button
variant="outline"
size="sm"
<Button
variant="outline"
size="sm"
onClick={handleRestartTour}
className="gap-2 text-xs h-8 border-primary/20 hover:bg-primary/10"
className="h-8 gap-2 border-primary/20 text-xs hover:bg-primary/10"
>
<PlayCircle className="h-3.5 w-3.5 text-primary" />
Reiniciar Tour do Sistema
Expand All @@ -107,28 +132,45 @@ export function ShortcutsHelpDialog() {
);
}

function ShortcutItem({ icon: Icon, label, keys, sub }: { icon: any, label: string, keys: string[], sub?: string }) {
function ShortcutItem({
icon: Icon,
label,
keys,
sub,
}: {
icon: LucideIcon;
label: string;
keys: string[];
sub?: string;
}) {
return (
<div className="flex items-center justify-between group">
<div className="group flex items-center justify-between">
<div className="flex items-center gap-2.5">
<div className="p-1.5 rounded bg-muted/50 text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary transition-colors">
<div className="rounded bg-muted/50 p-1.5 text-muted-foreground transition-colors group-hover:bg-primary/10 group-hover:text-primary">
<Icon className="h-3.5 w-3.5" />
</div>
<div>
<span className="text-sm font-medium">{label}</span>
{sub && <span className="block text-[10px] text-muted-foreground leading-none">{sub}</span>}
{sub && (
<span className="block text-[10px] leading-none text-muted-foreground">{sub}</span>
)}
</div>
</div>
<div className="flex gap-1 items-center">
<div className="flex items-center gap-1">
{keys.map((key, i) => (
<span key={i} className="flex items-center">
<Badge variant="outline" className="h-5 px-1.5 text-[10px] font-mono bg-muted/30 border-border/50 shadow-sm">
<Badge
variant="outline"
className="h-5 border-border/50 bg-muted/30 px-1.5 font-mono text-[10px] shadow-sm"
>
{key}
</Badge>
{i < keys.length - 1 && <span className="text-[10px] text-muted-foreground px-0.5">+</span>}
{i < keys.length - 1 && (
<span className="px-0.5 text-[10px] text-muted-foreground">+</span>
)}
</span>
))}
</div>
</div>
);
}
}
22 changes: 14 additions & 8 deletions src/contexts/OnboardingContext.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import React, { createContext, useContext } from "react";
import { useOnboarding as useOnboardingHook } from "@/hooks/ui";
import React, { createContext, useContext } from 'react';
import { useOnboarding as useOnboardingHook } from '@/hooks/ui';

type OnboardingContextType = ReturnType<typeof useOnboardingHook>;

const OnboardingContext = createContext<OnboardingContextType | null>(null);

export function OnboardingProvider({ children }: { children: React.ReactNode }) {
const onboarding = useOnboardingHook();
return (
<OnboardingContext.Provider value={onboarding}>
{children}
</OnboardingContext.Provider>
);
return <OnboardingContext.Provider value={onboarding}>{children}</OnboardingContext.Provider>;
}

export function useOnboardingContext(): OnboardingContextType {
const ctx = useContext(OnboardingContext);
if (!ctx) {
throw new Error("useOnboardingContext must be used within OnboardingProvider");
throw new Error('useOnboardingContext must be used within OnboardingProvider');
}
return ctx;
}

/**
* Variante segura para componentes que podem renderizar antes do
* <OnboardingProvider /> (ex.: Sidebar, Spotlight, atalhos globais).
* Retorna `null` em vez de lançar — assim os callers param de embrulhar
* o hook em try/catch (anti-pattern que viola react-hooks/rules-of-hooks).
*/
export function useOptionalOnboardingContext(): OnboardingContextType | null {
return useContext(OnboardingContext);
}
Loading