diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..47c4db0 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,3 @@ +module.exports = { + extends: ["next/core-web-vitals", "next/typescript"], +}; diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 3722418..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["next/core-web-vitals", "next/typescript"] -} diff --git a/.gitignore b/.gitignore index 8547d02..d838331 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -<<<<<<< HEAD # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies @@ -40,7 +39,6 @@ next-env.d.ts #database /prisma/db /prisma/migrations -======= # Logs logs *.log @@ -171,4 +169,8 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* ->>>>>>> ad0ba4c28c763a4748bd81d9039ccce4feab6b9e + +.env* +.flaskenv* +!.env.project +!.env.vault \ No newline at end of file diff --git a/.graphifyignore b/.graphifyignore new file mode 100644 index 0000000..d58fa88 --- /dev/null +++ b/.graphifyignore @@ -0,0 +1,21 @@ +node_modules/ +.next/ +dist/ +build/ +out/ +coverage/ +.git/ +.pnpm-store/ +.cache/ +*.log +.env +.env.* +__pycache__/ +*.pyc +.venv/ +venv/ +target/ +*.exe +*.dll +*.so +*.dylib diff --git a/app/api/products/[barcode]/route.ts b/app/api/products/[barcode]/route.ts index 48e3253..476d6f2 100644 --- a/app/api/products/[barcode]/route.ts +++ b/app/api/products/[barcode]/route.ts @@ -5,9 +5,9 @@ const USER_AGENT = "NutriScan/1.0 (https://nutriscan.com.br)"; export async function GET( request: NextRequest, - { params }: { params: { barcode: string } } + { params }: { params: Promise<{ barcode: string }> } ) { - const { barcode } = params; + const { barcode } = await params; const searchParams = request.nextUrl.searchParams; const cacheOnly = searchParams.get("cache_only") === "true"; diff --git a/app/components/CardList.tsx b/app/components/CardList.tsx index bd4e9ce..fe301d9 100644 --- a/app/components/CardList.tsx +++ b/app/components/CardList.tsx @@ -21,7 +21,7 @@ export async function CardList({ listaId }: CardListProps) { if (produtos.length === 0) { return ( -
+
Nenhum produto cadastrado ainda.
); @@ -47,7 +47,7 @@ export async function CardList({ listaId }: CardListProps) { {produtosComprados.length > 0 && (
-

+

Produtos Comprados

diff --git a/app/components/DeleteButton.tsx b/app/components/DeleteButton.tsx index 6776c84..14d5d93 100644 --- a/app/components/DeleteButton.tsx +++ b/app/components/DeleteButton.tsx @@ -42,7 +42,6 @@ export function DeleteButton({ produtoId, produtoNome, listaId }: DeleteButtonPr @@ -59,7 +58,7 @@ export function DeleteButton({ produtoId, produtoNome, listaId }: DeleteButtonPr Cancelar {isDeleting ? 'Excluindo...' : 'Excluir'} diff --git a/app/components/DonateCard.tsx b/app/components/DonateCard.tsx index a48eb8b..b184598 100644 --- a/app/components/DonateCard.tsx +++ b/app/components/DonateCard.tsx @@ -1,8 +1,7 @@ "use client"; -import { Card, CardContent } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Heart, Check, Code } from "lucide-react"; +import { Heart, Check } from "lucide-react"; +import { cn } from "@/lib/utils"; interface DonateCardProps { className?: string; @@ -10,141 +9,97 @@ interface DonateCardProps { } export function DonateCard({ className = "", variant = "default" }: DonateCardProps) { - // Layout mobile horizontal + /* ── Mobile horizontal ───────────────────────────────────────────────── */ if (variant === "mobile") { return ( - -
- {/* Ícone - tamanho ajustado */} -
- -
- - {/* Conteúdo - melhor espaçamento */} -
-
- {/* Título */} -

- Ajude o Projeto -

- - {/* Texto persuasivo */} -

- Mantenha este serviço gratuito e acessível para todos -

- - {/* Benefício rápido */} -
- - Qualquer valor faz diferença -
-
- - {/* Botão CTA - menor */} - - - +
+
+

+ Apoie o Projeto +

+

+ Mantenha o NutriScan gratuito e independente. +

+ + + Contribuir +
- + ); } - // Layout desktop padrão + /* ── Desktop card ────────────────────────────────────────────────────── */ return ( - - {/* Header com ícone animado */} -
-
- +
+ {/* Eyebrow */} + Apoie o Projeto + + {/* Headline */} +
+

+ Transparência alimentar,{" "} + para todos. +

+

+ O NutriScan opera sem anúncios e sem venda de dados. Sua + contribuição mantém o projeto vivo. +

- - {/* Overlay sutil */} -
- - {/* Badge de destaque */} -
-
- 💛 Apoie -
+ + {/* Benefits */} +
    + {[ + "Servidores sempre disponíveis", + "Novas funcionalidades", + "Acesso gratuito para todos", + ].map((item) => ( +
  • + + {item} +
  • + ))} +
+ + {/* CTA */} +
+ + + Contribuir agora + +

+ Qualquer valor ajuda +

- - - {/* Título principal */} -
-

- Apoie a Transparência Alimentar -

-

- Projeto mantido por um estudante dedicado -

-
- - {/* Benefícios */} -
-
- - Servidores sempre disponíveis -
-
- - Novas funcionalidades -
-
- - Acesso gratuito para todos -
-
- - {/* Mensagem humanizada */} -
-
- - - Desenvolvido com amor - -
-

- Cada doação ajuda a manter este projeto vivo e acessível -

-
- - {/* Botão CTA principal */} - - - - - {/* Texto de incentivo */} -

- Qualquer valor faz diferença 💛 -

-
- + ); } diff --git a/app/components/FilterSidebar.tsx b/app/components/FilterSidebar.tsx index f3de95b..2d8a513 100644 --- a/app/components/FilterSidebar.tsx +++ b/app/components/FilterSidebar.tsx @@ -120,106 +120,37 @@ export function FilterSidebar({ }); return ( -
- {/* Header */} -
-
- -

Filtros

+
+ + {/* ── Header ────────────────────────────────────────────────────────── */} +
+
+ + + Filtros avançados +
{hasActiveFilters && ( - + + Limpar tudo + )}
- {/* Resultados */} + {/* ── Result count ──────────────────────────────────────────────────── */} {totalResults > 0 && ( -
- {totalResults.toLocaleString()} produto{totalResults !== 1 ? "s" : ""}{" "} - encontrado{totalResults !== 1 ? "s" : ""} +
+ {totalResults.toLocaleString()} produto{totalResults !== 1 ? "s" : ""} encontrado{totalResults !== 1 ? "s" : ""}
)} - {/* Controles de Visualização e Cache */} -
- {/* Modo de Visualização */} - {onViewModeChange && ( -
-
Modo de visualização
-
- - -
-
- )} - - {/* Controles de Cache */} - {(onRefresh || onClearCache) && ( -
-
Controles
-
- {onRefresh && ( - - )} - {onClearCache && isCached && ( - - )} -
-
- )} -
- - {/* Filtros Ativos */} + {/* ── Active filter badges ───────────────────────────────────────────── */} {onRemoveFilter && hasActiveFilters && ( -
-
Filtros ativos
+
)} - - {/* Ordenação */} - - -
- - Ordenação -
-
- -
-
Ordenar por
- {onSortChange && ( - - )} -
-
-
+ {/* ── Cache / Refresh controls ──────────────────────────────────────── */} + {(onRefresh || onClearCache) && ( +
+ {onRefresh && ( + + )} + {onClearCache && isCached && ( + + )} +
+ )} - {/* Nutri-Score */} - - -
- - Classificação Nutricional -
-
- -
-
Nutri-Score
-
- {NUTRITION_GRADES.map((grade) => ( -
- - updateArrayFilter( - "nutritionGrades", - grade.grade, - checked as boolean - ) - } - /> - -
- ))} -
- - - -
- NOVA (Processamento) -
- - updateFilter( - "novaGroups", - value ? [parseInt(value) as 1 | 2 | 3 | 4] : [] - ) - } - className="space-y-2" + {/* ── View mode ─────────────────────────────────────────────────────── */} + {onViewModeChange && ( +
+
Visualização
+
+ {(["grid", "list"] as const).map((mode) => ( + + ))} +
+
+ )} + + {/* ── Accordion sections ────────────────────────────────────────────── */} + + + {/* Ordenação */} + {onSortChange && ( + + +
+ + Ordenação +
+
+ +
+ +
+
+
+ )} + + {/* Nutri-Score & NOVA */} + + +
+ + Classificação Nutricional +
+
+ +
+
+
Nutri-Score
+
+ {NUTRITION_GRADES.map((grade) => ( +
+ updateArrayFilter("nutritionGrades", grade.grade, checked as boolean)} + /> + +
+ ))} +
+
+ +
+ +
+
NOVA — Nível de processamento
+ updateFilter("novaGroups", value ? [parseInt(value) as 1 | 2 | 3 | 4] : [])} + className="space-y-2" + > + {NOVA_GROUPS.map((group) => ( +
+ + +
+ ))} +
+
{/* Categorias */} - + -
- - Categorias +
+ + Categorias
- -
+ +
setSearchCategories(e.target.value)} - className="h-8" + style={{ height: 34, fontSize: 13, borderColor: "var(--border-subtle, #e8e0d0)", background: "var(--bg-base, #f9f6f0)" }} /> -
+
{POPULAR_CATEGORIES_WITH_ICONS.filter( (cat) => - cat.label - .toLowerCase() - .includes(searchCategories.toLowerCase()) || - cat.value - .toLowerCase() - .includes(searchCategories.toLowerCase()) + cat.label.toLowerCase().includes(searchCategories.toLowerCase()) || + cat.value.toLowerCase().includes(searchCategories.toLowerCase()) ).map((category) => { - const IconComponent = LucideIcons[ - category.icon as keyof typeof LucideIcons - ] as React.ComponentType<{ className?: string }> | undefined; + const IconComponent = LucideIcons[category.icon as keyof typeof LucideIcons] as React.ComponentType<{ style?: React.CSSProperties }> | undefined; return ( -
+
- updateArrayFilter( - "categories", - category.value, - checked as boolean - ) - } + checked={filters.categories?.includes(category.value) || false} + onCheckedChange={(checked) => updateArrayFilter("categories", category.value, checked as boolean)} /> -
@@ -416,70 +319,53 @@ export function FilterSidebar({ {/* Marcas */} - + -
- - Marcas +
+ + Marcas
- -
+ +
setSearchBrands(e.target.value)} - className="h-8" + style={{ height: 34, fontSize: 13, borderColor: "var(--border-subtle, #e8e0d0)", background: "var(--bg-base, #f9f6f0)" }} /> -
- Digite o nome da marca para buscar -
+
Digite para filtrar por marca
{/* Alérgenos */} - + -
- - Alérgenos +
+ + Alérgenos
- -
+ +
setSearchAllergens(e.target.value)} - className="h-8" + style={{ height: 34, fontSize: 13, borderColor: "var(--border-subtle, #e8e0d0)", background: "var(--bg-base, #f9f6f0)" }} /> -
+
{COMMON_ALLERGENS.filter((allergen) => allergen.toLowerCase().includes(searchAllergens.toLowerCase()) ).map((allergen) => ( -
+
- updateArrayFilter( - "allergens", - allergen, - checked as boolean - ) - } + onCheckedChange={(checked) => updateArrayFilter("allergens", allergen, checked as boolean)} /> -
@@ -490,32 +376,26 @@ export function FilterSidebar({ {/* Aditivos */} - + -
- - Aditivos +
+ + Aditivos
- -
-
-