diff --git a/README.md b/README.md index 56e4150..d2a25a9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Algoria -![](https://img.shields.io/badge/Versão-1.0.0-black?style=for-the-badge) +![](https://img.shields.io/badge/Versão-1.0.3-black?style=for-the-badge) Plataforma em português para estudar **algoritmos e decisões em código** através de leitura guiada: catálogo de problemas com várias soluções (brute-force, óptima, alternativa), **code player** linha-a-linha com três níveis de explicação, mini-guias em **Conceitos**, **curso modular** com avaliações locais, hub de **inglês técnico para entrevistas** (conteúdo em inglês) e guias de **engenharia aplicada** (front, back, DevOps). diff --git a/app/changelog/page.tsx b/app/changelog/page.tsx index ef2efe0..782e277 100644 --- a/app/changelog/page.tsx +++ b/app/changelog/page.tsx @@ -2,6 +2,7 @@ import { ArrowLeft } from "lucide-react"; import type { Metadata } from "next"; import Link from "next/link"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { getChangelogHtml } from "@/lib/content/loader"; @@ -32,14 +33,17 @@ export default async function ChangelogPage() {
-
-

- Atualizações -

-

+
+ + Últimas Atualizações + +

Novidades

-

+

Descobre as últimas melhorias, novos conteúdos e funcionalidades desenhadas para levar o teu conhecimento técnico ao próximo nível.

diff --git a/app/concepts/page.tsx b/app/concepts/page.tsx index 51a904e..a42366c 100644 --- a/app/concepts/page.tsx +++ b/app/concepts/page.tsx @@ -1,23 +1,24 @@ -import Link from 'next/link'; -import type { Metadata } from 'next'; +import type { Metadata } from "next"; +import Link from "next/link"; -import { ConceptsCatalogClient } from '@/components/concepts/concepts-catalog-client'; -import { getAllConcepts } from '@/lib/content/loader'; -import { buildPublicMetadata } from '@/lib/seo/build-metadata'; +import { ConceptsCatalogClient } from "@/components/concepts/concepts-catalog-client"; +import { Badge } from "@/components/ui/badge"; +import { getAllConcepts } from "@/lib/content/loader"; +import { buildPublicMetadata } from "@/lib/seo/build-metadata"; export const metadata: Metadata = buildPublicMetadata({ - title: 'Conceitos de algoritmos e estruturas de dados', + title: "Conceitos de algoritmos e estruturas de dados", description: - 'Mini-guias sobre Big O, tabelas hash, duas ponteiros, janela deslizante e mais — base para ler as soluções com contexto.', - pathname: '/concepts', + "Mini-guias sobre Big O, tabelas hash, duas ponteiros, janela deslizante e mais — base para ler as soluções com contexto.", + pathname: "/concepts", keywords: [ - 'Big O', - 'complexidade algorítmica', - 'hash table', - 'two pointers', - 'sliding window', - 'fundamentos algoritmos', - 'Algoria conceitos', + "Big O", + "complexidade algorítmica", + "hash table", + "two pointers", + "sliding window", + "fundamentos algoritmos", + "Algoria conceitos", ], }); @@ -36,13 +37,35 @@ export default async function ConceptsPage() { return (
+
+ + Conceitos de algoritmos e estruturas de dados + +

+ Conceitos fundamentais +

+

+ Os pilares fundamentais para entender problemas de algoritmos e + estruturas de dados. +

+
+
-

Nova trilha

-

Prefere um programa com ordem fixa?

+

+ Nova trilha +

+

+ Prefere um programa com ordem fixa? +

- O mesmo conteúdo abaixo entra também no curso de Fundamentos: progresso no browser, dois níveis nos exemplos - interativos e certificado próprio assim que resolveres a avaliação de cada módulo. + O mesmo conteúdo abaixo entra também no curso de Fundamentos: + progresso no browser, dois níveis nos exemplos interativos e + certificado próprio assim que resolveres a avaliação de cada + módulo.

-
-
-
-
- -
-
-

- Algoria.curriculum -

-

- Cursos Guiados -

-

- Cada curso combina leitura, exemplos práticos e exercícios no browser. - Obtém certificados modulares ao concluir as avaliações de cada capítulo. -

-
-
-
+
+ + Trilhas de Aprendizagem + +

+ Cursos Guiados +

+

+ Percursos estruturados com leitura curada, exercícios no browser e certificado modular + ao concluir cada avaliação. +

diff --git a/app/engineering-work/page.tsx b/app/engineering-work/page.tsx index a164761..4cf85e0 100644 --- a/app/engineering-work/page.tsx +++ b/app/engineering-work/page.tsx @@ -1,31 +1,42 @@ -import Link from 'next/link'; -import type { Metadata } from 'next'; -import type { ReactNode } from 'react'; -import { Briefcase, Clock, MonitorSmartphone, Server, CloudCog } from 'lucide-react'; +import { Clock, CloudCog, MonitorSmartphone, Server } from "lucide-react"; +import type { Metadata } from "next"; +import Link from "next/link"; +import type { ReactNode } from "react"; -import { Badge } from '@/components/ui/badge'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { ENGINEERING_WORK_PILLARS, type EngineeringWorkPillar } from '@/lib/content/schemas'; -import { getAllEngineeringWorkGuides } from '@/lib/content/loader'; -import { buildPublicMetadata } from '@/lib/seo/build-metadata'; +import { Badge } from "@/components/ui/badge"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { getAllEngineeringWorkGuides } from "@/lib/content/loader"; +import { + ENGINEERING_WORK_PILLARS, + type EngineeringWorkPillar, +} from "@/lib/content/schemas"; +import { buildPublicMetadata } from "@/lib/seo/build-metadata"; export const metadata: Metadata = buildPublicMetadata({ - title: 'Engenharia no trabalho — guias práticos', + title: "Engenharia no trabalho — guias práticos", description: - 'Guias em português sobre frontend e produto, backend e APIs, DevOps e operação — aplicáveis na sprint real, sem lista de buzzwords.', - pathname: '/engineering-work', + "Guias em português sobre frontend e produto, backend e APIs, DevOps e operação — aplicáveis na sprint real, sem lista de buzzwords.", + pathname: "/engineering-work", keywords: [ - 'engenharia software prática', - 'frontend produção', - 'APIs backend', - 'DevOps dia a dia', - 'observabilidade', - 'segurança aplicações', - 'Algoria guias', + "engenharia software prática", + "frontend produção", + "APIs backend", + "DevOps dia a dia", + "observabilidade", + "segurança aplicações", + "Algoria guias", ], }); -const PILLAR_ORDER = new Map(ENGINEERING_WORK_PILLARS.map((p, i) => [p, i])); +const PILLAR_ORDER = new Map( + ENGINEERING_WORK_PILLARS.map((p, i) => [p, i]), +); const PILLAR_ICON: Record = { frontend: , @@ -34,21 +45,26 @@ const PILLAR_ICON: Record = { }; const PILLAR_TITLE: Record = { - frontend: 'Frontend e produto', - backend: 'Backend e APIs', - devops: 'DevOps e sistema', + frontend: "Frontend e produto", + backend: "Backend e APIs", + devops: "DevOps e sistema", }; const PILLAR_TAGLINE: Record = { - frontend: 'Performance real, segurança em superfícies web e SEO técnico honesto.', - backend: 'Identidade, permissões, contratos estáveis e resiliência sob carga.', - devops: 'Entrega contínua, observabilidade e segurança operacional sem teatro.', + frontend: + "Performance real, segurança em superfícies web e SEO técnico honesto.", + backend: + "Identidade, permissões, contratos estáveis e resiliência sob carga.", + devops: + "Entrega contínua, observabilidade e segurança operacional sem teatro.", }; export default async function EngineeringWorkHubPage() { const guides = await getAllEngineeringWorkGuides(); guides.sort((a, b) => { - const pd = (PILLAR_ORDER.get(a.meta.pillar) ?? 99) - (PILLAR_ORDER.get(b.meta.pillar) ?? 99); + const pd = + (PILLAR_ORDER.get(a.meta.pillar) ?? 99) - + (PILLAR_ORDER.get(b.meta.pillar) ?? 99); if (pd !== 0) return pd; return a.meta.title.localeCompare(b.meta.title); }); @@ -61,48 +77,58 @@ export default async function EngineeringWorkHubPage() { return (
-
-
-
- -
-
-

Área aplicada

-

Engenharia no trabalho

-

- Material pensado para ler com calma e usar na segunda-feira: cada guia separa o que é conceito, o que é decisão de produto e o que é - checklist na prática. Sem pressupor cloud específica nem framework único — quando há exemplos, são ilustrativos. -

-

- Voltaste da página inicial? Os mesmos três pilares (front, back, DevOps) estão aqui expandidos em capítulos longos. Usa o tempo estimado - como bloco de foco — pausa entre guias para experimentar no teu projeto. -

-
-
+
+ + Conteúdo prático de engenharia + +

+ Engenharia no trabalho +

+

+ Frontend, Backend e DevOps aplicados ao mundo real. Material pensado + para ler com calma e usar na segunda-feira. +

{byPillar.map(({ pillar, guides: list }) => ( -
+
{PILLAR_ICON[pillar]}
- + Pilar · {pillar} -

+

{PILLAR_TITLE[pillar]}

-

{PILLAR_TAGLINE[pillar]}

+

+ {PILLAR_TAGLINE[pillar]} +

{list.length === 0 ? ( -

Conteúdo deste pilar em preparação.

+

+ Conteúdo deste pilar em preparação. +

) : (
{list.map((g) => ( @@ -117,11 +143,14 @@ export default async function EngineeringWorkHubPage() { {g.meta.title} - ~{g.meta.estimatedMinutes} min de leitura + ~ + {g.meta.estimatedMinutes} min de leitura -

{g.meta.summary}

+

+ {g.meta.summary} +

@@ -133,8 +162,10 @@ export default async function EngineeringWorkHubPage() {

- Sugestão de uso: escolhe um pilar por sprint, lê um guia na sexta ou segunda, e implementa uma única melhoria mensurável (métrica de produto ou - observabilidade) em vez de uma lista enorme de "best practices". + Sugestão de uso: escolhe um pilar por sprint, lê um guia na sexta ou + segunda, e implementa uma única melhoria mensurável (métrica de + produto ou observabilidade) em vez de uma lista enorme de "best + practices".

diff --git a/app/explorer/page.tsx b/app/explorer/page.tsx index 5df9dde..09b35f1 100644 --- a/app/explorer/page.tsx +++ b/app/explorer/page.tsx @@ -2,6 +2,7 @@ import { Pagination } from "@/components/explorer/pagination"; import { SearchBar } from "@/components/explorer/search-bar"; import { TechFilter } from "@/components/explorer/tech-filter"; import { UserCard } from "@/components/explorer/user-card"; +import { Badge } from "@/components/ui/badge"; import { db } from "@/lib/db"; import { user, userProfile } from "@/lib/db/schema"; import { buildPublicMetadata } from "@/lib/seo/build-metadata"; @@ -80,22 +81,19 @@ export default async function ExplorerPage({ return (
-
-
-
- -
-

- Comunidade Algoria -

-
-

- Explorar Talentos +
+ + Comunidade Algoria + +

+ Explorar Talentos

-

- Conecta-te com outros engenheiros da plataforma. Filtra por stack - tecnológica, percurso profissional ou procura diretamente por nomes - e especialidades. +

+ Conecta-te com outros engenheiros da plataforma. Filtra por stack tecnológica, + percurso profissional ou procura diretamente por nomes e especialidades.

diff --git a/app/interview-en/page.tsx b/app/interview-en/page.tsx index ad8cb41..f342a48 100644 --- a/app/interview-en/page.tsx +++ b/app/interview-en/page.tsx @@ -1,27 +1,29 @@ -import type { Metadata } from 'next'; -import { Languages } from 'lucide-react'; +import type { Metadata } from "next"; -import { Badge } from '@/components/ui/badge'; -import { InterviewCatalogClient } from '@/components/interview-en/interview-catalog-client'; -import { INTERVIEW_EN_TRACKS, type InterviewEnglishTrack } from '@/lib/content/schemas'; -import { getAllInterviewEnglishTopics } from '@/lib/content/loader'; -import { buildPublicMetadata } from '@/lib/seo/build-metadata'; +import { InterviewCatalogClient } from "@/components/interview-en/interview-catalog-client"; +import { Badge } from "@/components/ui/badge"; +import { getAllInterviewEnglishTopics } from "@/lib/content/loader"; +import { + INTERVIEW_EN_TRACKS, + type InterviewEnglishTrack, +} from "@/lib/content/schemas"; +import { buildPublicMetadata } from "@/lib/seo/build-metadata"; export const metadata: Metadata = buildPublicMetadata({ - title: 'Technical English for interviews — hub Algoria', + title: "Technical English for interviews — hub Algoria", description: - 'English-only hub: vocabulary for data structures & algorithms, live coding talk tracks, behavioral STAR answers and system design phrases for hiring loops.', - pathname: '/interview-en', + "English-only hub: vocabulary for data structures & algorithms, live coding talk tracks, behavioral STAR answers and system design phrases for hiring loops.", + pathname: "/interview-en", keywords: [ - 'technical English interviews', - 'coding interview English', - 'live coding phrases', - 'STAR method behavioral', - 'system design vocabulary', - 'FAANG interview English', - 'Algoria', + "technical English interviews", + "coding interview English", + "live coding phrases", + "STAR method behavioral", + "system design vocabulary", + "FAANG interview English", + "Algoria", ], - openGraphLocale: 'en_US', + openGraphLocale: "en_US", }); const TRACK_ORDER = new Map( @@ -31,7 +33,9 @@ const TRACK_ORDER = new Map( export default async function InterviewEnglishIndexPage() { const topics = await getAllInterviewEnglishTopics(); topics.sort((a, b) => { - const td = (TRACK_ORDER.get(a.meta.track) ?? 99) - (TRACK_ORDER.get(b.meta.track) ?? 99); + const td = + (TRACK_ORDER.get(a.meta.track) ?? 99) - + (TRACK_ORDER.get(b.meta.track) ?? 99); if (td !== 0) return td; return a.meta.title.localeCompare(b.meta.title); }); @@ -39,36 +43,22 @@ export default async function InterviewEnglishIndexPage() { return (
-
-
-
-
- -
-
-

Nova área · conteúdo 100% EN

-

Technical English for interviews

-

- Toda a matéria das páginas abaixo está em inglês, orientada para quem já programa e está entre B1/B2 — - foco em soar claro sob pressão: vocabulário de entrevistas, pensar em voz alta em DSA, - comportamental (STAR), design de sistemas e e-mails típicos da pipeline de hiring. - Usa repetição activa antes de rounds reais. -

-
-
-
-
- -
- - Interview.EN +
+ + Inglês Técnico -

Speak like you are on the call

-

- Vocabulary, narration templates, STAR stories, and framing phrases — everything below is written for hiring conversations, - not generic textbook English. +

+ Inglês Técnico para Entrevistas +

+

+ Vocabulário, modelos de narração, histórias STAR e frases de + orientação — tudo escrito para conversas de contratação, não para o + inglês genérico de livros didáticos.

-
+

({ diff --git a/app/pricing/page.tsx b/app/pricing/page.tsx index b540045..5fa810f 100644 --- a/app/pricing/page.tsx +++ b/app/pricing/page.tsx @@ -6,6 +6,7 @@ import { CheckoutButton } from "@/components/billing/checkout-button"; import { ManageSubscriptionButton } from "@/components/billing/manage-subscription-button"; import { PricingPageAnalytics } from "@/components/billing/pricing-analytics"; import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; import { auth } from "@/lib/auth"; import { userHasPro } from "@/lib/billing/entitlements"; import { @@ -46,17 +47,19 @@ export default async function PricingPage() { Início -
-

- Monetização transparente -

-

- Planos +
+ + Monetização Transparente + +

+ Planos e Preços

-

- Dez problemas e funcionalidades essenciais gratuitos. Pro - desbloqueia o catálogo completo, sincronização de progresso e - investimento contínuo em conteúdo. +

+ Compara o plano gratuito com a subscrição Pro: desbloqueia o catálogo completo, + sincronização de progresso e investimento contínuo em conteúdo.

diff --git a/app/profile/page.tsx b/app/profile/page.tsx index 662ad51..a45e722 100644 --- a/app/profile/page.tsx +++ b/app/profile/page.tsx @@ -1,5 +1,6 @@ import { eq } from "drizzle-orm"; import { ArrowLeft, Trash2, User } from "lucide-react"; +import type { Metadata } from "next"; import { headers } from "next/headers"; import Link from "next/link"; import { redirect } from "next/navigation"; @@ -19,24 +20,33 @@ import { } from "@/components/ui/card"; import { auth } from "@/lib/auth"; import { db } from "@/lib/db"; -import { technicalAssessmentResults, subscription, userProfile, userProgress } from "@/lib/db/schema"; +import { + subscription, + technicalAssessmentResults, + userProfile, + userProgress, +} from "@/lib/db/schema"; import { desc } from "drizzle-orm"; -import { ProgressBlobSchema } from "@/lib/progress/local-progress-schema"; -import { buildPublicMetadata } from "@/lib/seo/build-metadata"; -import Image from "next/image"; import { ProfileActionsClient } from "@/components/profile/profile-actions-client"; import { ProfileAssessmentVisibilityToggle } from "@/components/profile/profile-assessment-visibility-toggle"; import { AssessmentCard } from "@/components/profile/public/assessment-card"; -import { cn } from "@/lib/utils"; +import { ProgressBlobSchema } from "@/lib/progress/local-progress-schema"; +import { buildPublicMetadata } from "@/lib/seo/build-metadata"; +import Image from "next/image"; +export async function generateMetadata(): Promise { + const session = await auth.api.getSession({ headers: await headers() }); + const user = session?.user; -export const metadata = buildPublicMetadata({ - title: "Meu Perfil", - description: - "Gerencia o teu perfil, progresso de estudo e subscrições na Algoria.", - pathname: "/profile", -}); + return buildPublicMetadata({ + title: "Meu Perfil", + description: + "Gerencia o teu perfil, progresso de estudo e subscrições na Algoria.", + pathname: "/profile", + image: user?.image || undefined, + }); +} export default async function ProfilePage() { const session = await auth.api.getSession({ headers: await headers() }); @@ -48,29 +58,29 @@ export default async function ProfilePage() { const { user } = session; // Buscar progressos e subscrição - const [progressRows, subRows, profileRows, assessmentRows] = await Promise.all([ - db - .select() - .from(userProgress) - .where(eq(userProgress.userId, user.id)) - .limit(1), - db - .select() - .from(subscription) - .where(eq(subscription.userId, user.id)) - .limit(1), - db - .select() - .from(userProfile) - .where(eq(userProfile.userId, user.id)) - .limit(1), - db - .select() - .from(technicalAssessmentResults) - .where(eq(technicalAssessmentResults.userId, user.id)) - .orderBy(desc(technicalAssessmentResults.completedAt)), - ]); - + const [progressRows, subRows, profileRows, assessmentRows] = + await Promise.all([ + db + .select() + .from(userProgress) + .where(eq(userProgress.userId, user.id)) + .limit(1), + db + .select() + .from(subscription) + .where(eq(subscription.userId, user.id)) + .limit(1), + db + .select() + .from(userProfile) + .where(eq(userProfile.userId, user.id)) + .limit(1), + db + .select() + .from(technicalAssessmentResults) + .where(eq(technicalAssessmentResults.userId, user.id)) + .orderBy(desc(technicalAssessmentResults.completedAt)), + ]); const activeSub = subRows[0]; const isPro = activeSub?.status === "active"; @@ -262,25 +272,27 @@ export default async function ProfilePage() {
-

Resultados de Assessments

+

+ Resultados de Assessments +

- + {assessmentRows.length > 0 ? (
{assessmentRows.map((result) => ( -
-
- +
- - ))}
) : ( @@ -306,7 +316,11 @@ export default async function ProfilePage() {

Ainda não completaste nenhum assessment técnico.

-
diff --git a/app/tests/page.tsx b/app/tests/page.tsx index fb4c9d6..82c182b 100644 --- a/app/tests/page.tsx +++ b/app/tests/page.tsx @@ -1,7 +1,8 @@ -import { ArrowRight, Code2, Layout, Database, Server } from "lucide-react"; +import { ArrowRight, Database, Layout, Server } from "lucide-react"; import type { Metadata } from "next"; import Link from "next/link"; +import { Badge } from "@/components/ui/badge"; import { buildPublicMetadata } from "@/lib/seo/build-metadata"; export const metadata: Metadata = buildPublicMetadata({ @@ -13,21 +14,22 @@ export const metadata: Metadata = buildPublicMetadata({ export default function TechnicalTestsIndexPage() { const tracks = [ - { - id: "frontend", - title: "Frontend", - description: "React, Next.js, Performance, Acessibilidade e Ecossistema Web.", + { + id: "frontend", + title: "Frontend", + description: + "React, Next.js, Performance, Acessibilidade e Ecossistema Web.", icon: Layout, }, - { - id: "backend", - title: "Backend", + { + id: "backend", + title: "Backend", description: "Node.js, APIs, Bases de Dados, Segurança e Arquitetura.", icon: Database, }, - { - id: "devops", - title: "DevOps", + { + id: "devops", + title: "DevOps", description: "Docker, CI/CD, Cloud, Monitorização e Infraestrutura.", icon: Server, }, @@ -36,26 +38,20 @@ export default function TechnicalTestsIndexPage() { return (
-
-
-
-
- -
-
-

- Assessment -

-

- Escolhe a tua Trilha -

-

- Seleciona a tua área de especialização para ver os simulados disponíveis. - Cada trilha contém testes de diferentes níveis e tópicos específicos. -

-
-
-
+
+ + Trilhas de Testes Técnicos + +

+ Avaliações Técnicas +

+

+ Simulados reais para vagas de Frontend, Backend e DevOps. Escolhe a + tua trilha e começa a tua avaliação profissional. +

diff --git a/app/tracks/page.tsx b/app/tracks/page.tsx index 1377fc1..6c18242 100644 --- a/app/tracks/page.tsx +++ b/app/tracks/page.tsx @@ -1,17 +1,27 @@ -import Link from 'next/link'; -import type { Metadata } from 'next'; +import type { Metadata } from "next"; +import Link from "next/link"; -import { Badge } from '@/components/ui/badge'; -import { Card, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { getAllStudyTracks } from '@/lib/content/track-loader'; -import { buildPublicMetadata } from '@/lib/seo/build-metadata'; +import { Badge } from "@/components/ui/badge"; +import { + Card, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { getAllStudyTracks } from "@/lib/content/track-loader"; +import { buildPublicMetadata } from "@/lib/seo/build-metadata"; export const metadata: Metadata = buildPublicMetadata({ - title: 'Trilhos curados de problemas', + title: "Trilhos curados de problemas", description: - 'Listas editoriais ordenadas — fundamentos na ordem recomendada ou foco arrays & hashing — para estudar sem escolher tu próprio a sequência.', - pathname: '/tracks', - keywords: ['trilho estudo', 'roadmap algoritmos', 'arrays hashing', 'ordem recomendada'], + "Listas editoriais ordenadas — fundamentos na ordem recomendada ou foco arrays & hashing — para estudar sem escolher tu próprio a sequência.", + pathname: "/tracks", + keywords: [ + "trilho estudo", + "roadmap algoritmos", + "arrays hashing", + "ordem recomendada", + ], }); export default async function TracksIndexPage() { @@ -20,26 +30,40 @@ export default async function TracksIndexPage() { return (
-
- - STUDY.TRACKS +
+ + Roadmaps Algorítmicos -

Trilhos curados

-

- Cada trilho é uma lista ordenada de slugs em content/tracks/ — mantém ritmo editorial sem backend. +

+ Trilhas Recomendadas +

+

+ Listas editoriais organizadas por tópicos específicos ou fundamentos + gerais.

{tracks.map((t) => ( - + {t.title} - {t.summary} -

{t.problemSlugs.length} problemas

+ + {t.summary} + +

+ {t.problemSlugs.length} problemas +

@@ -47,7 +71,10 @@ export default async function TracksIndexPage() {

- + Voltar ao catálogo completo

diff --git a/app/user/[id]/page.tsx b/app/user/[id]/page.tsx index 09ceb65..c5b4447 100644 --- a/app/user/[id]/page.tsx +++ b/app/user/[id]/page.tsx @@ -7,21 +7,25 @@ import type { Experience, Project, } from "@/components/profile/profile-sections"; +import { AssessmentCard } from "@/components/profile/public/assessment-card"; import { ExperienceSection } from "@/components/profile/public/experience-section"; import { ProfileDashboard } from "@/components/profile/public/profile-dashboard"; import { ProfileHeader } from "@/components/profile/public/profile-header"; import { ProjectsSection } from "@/components/profile/public/projects-section"; import { Button } from "@/components/ui/button"; import { db } from "@/lib/db"; -import { user, userProfile, userProgress, technicalAssessmentResults } from "@/lib/db/schema"; +import { + technicalAssessmentResults, + user, + userProfile, + userProgress, +} from "@/lib/db/schema"; import { calculateTotalExperienceMonths, formatExperienceString, processUserProgress, } from "@/lib/profile/profile-utils"; import { buildPublicMetadata } from "@/lib/seo/build-metadata"; -import { AssessmentCard } from "@/components/profile/public/assessment-card"; - interface PublicProfileProps { params: Promise<{ id: string }>; @@ -43,6 +47,7 @@ export async function generateMetadata({ params }: PublicProfileProps) { title: `${userData[0].name} | Perfil Algoria`, description: `Vê o perfil público, tecnologias e progresso de ${userData[0].name} na Algoria.`, pathname: `/user/${id}`, + image: userData[0].image || undefined, }); } @@ -51,18 +56,26 @@ export default async function PublicProfilePage({ }: PublicProfileProps) { const { id } = await params; - const [userRows, profileRows, progressRows, assessmentRows] = await Promise.all([ - db.select().from(user).where(eq(user.id, id)).limit(1), - db.select().from(userProfile).where(eq(userProfile.userId, id)).limit(1), - db.select().from(userProgress).where(eq(userProgress.userId, id)).limit(1), - db.select().from(technicalAssessmentResults).where( - and( - eq(technicalAssessmentResults.userId, id), - eq(technicalAssessmentResults.isPublic, true) - ) - ).orderBy(desc(technicalAssessmentResults.completedAt)), - ]); - + const [userRows, profileRows, progressRows, assessmentRows] = + await Promise.all([ + db.select().from(user).where(eq(user.id, id)).limit(1), + db.select().from(userProfile).where(eq(userProfile.userId, id)).limit(1), + db + .select() + .from(userProgress) + .where(eq(userProgress.userId, id)) + .limit(1), + db + .select() + .from(technicalAssessmentResults) + .where( + and( + eq(technicalAssessmentResults.userId, id), + eq(technicalAssessmentResults.isPublic, true), + ), + ) + .orderBy(desc(technicalAssessmentResults.completedAt)), + ]); const userData = userRows[0]; if (!userData) { @@ -138,9 +151,11 @@ export default async function PublicProfilePage({
-

Avaliações Técnicas

+

+ Avaliações Técnicas +

- +
{assessmentRows.map((result) => ( - - ))}
-
)}
-
); } diff --git a/components/branding/algoria-logo.tsx b/components/branding/algoria-logo.tsx index eeb96af..b53b913 100644 --- a/components/branding/algoria-logo.tsx +++ b/components/branding/algoria-logo.tsx @@ -1,37 +1,22 @@ import Link from "next/link"; +import Image from "next/image"; import { cn } from "@/lib/utils"; type Size = "header" | "footer"; -/** Marca SVG: mouldura + árvore binária mínima (decisões algorítmicas), alinhado ao estilo técnico do site */ +/** Marca: logo oficial em PNG, alinhado ao estilo técnico do site */ export function AlgoriaMark({ className }: { className?: string }) { return ( - - - + - - +
); } @@ -44,44 +29,17 @@ export function AlgoriaBrand({ size?: Size; className?: string; }) { - const markSize = size === "header" ? "h-10 w-10" : "h-9 w-9"; - const titleSize = - size === "header" - ? "text-lg md:text-xl leading-none tracking-[0.12em]" - : "text-base md:text-lg leading-none tracking-[0.14em]"; - const kickerHidden = size === "footer"; + const markSize = size === "header" ? "h-10 w-36" : "h-9 w-32"; return ( - - - - - - Algoria - - {!kickerHidden ? ( - // - // Leitura de código guiada - // - <> - ) : ( - - Catálogo · Player · Curso - - )} - + ); } diff --git a/components/catalog/problems-catalog-client.tsx b/components/catalog/problems-catalog-client.tsx index a0bd59e..d3343fe 100644 --- a/components/catalog/problems-catalog-client.tsx +++ b/components/catalog/problems-catalog-client.tsx @@ -1,43 +1,54 @@ -'use client'; +"use client"; -import { useMemo, useState } from 'react'; -import Link from 'next/link'; -import { Clock } from 'lucide-react'; +import { Clock } from "lucide-react"; +import Link from "next/link"; +import { useMemo, useState } from "react"; -import { Badge } from '@/components/ui/badge'; -import { CatalogReviewSection } from '@/components/catalog/catalog-review-section'; -import { ProgressBackupControls } from '@/components/catalog/progress-backup-controls'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { DifficultyBadge } from '@/components/catalog/difficulty-badge'; -import { ProblemStatusBadge } from '@/components/catalog/problem-status-badge'; -import { Input } from '@/components/ui/input'; -import type { ProblemsCatalogProblem } from '@/lib/catalog/problem-card-model'; -import { catalogCategoryLabels, categoryLabelPt } from '@/lib/catalog/category-labels'; -import type { SortMode } from '@/lib/catalog/problem-filters'; +import { CatalogReviewSection } from "@/components/catalog/catalog-review-section"; +import { DifficultyBadge } from "@/components/catalog/difficulty-badge"; +import { ProblemStatusBadge } from "@/components/catalog/problem-status-badge"; +import { ProgressBackupControls } from "@/components/catalog/progress-backup-controls"; +import { Badge } from "@/components/ui/badge"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { + catalogCategoryLabels, + categoryLabelPt, +} from "@/lib/catalog/category-labels"; +import type { ProblemsCatalogProblem } from "@/lib/catalog/problem-card-model"; +import type { SortMode } from "@/lib/catalog/problem-filters"; import { DIFFICULTY_LABEL_PT, filterProblems, sortCatalogProblems, type CategoryFilter, -} from '@/lib/catalog/problem-filters'; -import type { Category, Difficulty } from '@/lib/content/schemas'; +} from "@/lib/catalog/problem-filters"; +import type { Category, Difficulty } from "@/lib/content/schemas"; interface Props { problems: ProblemsCatalogProblem[]; } const SORT_LABEL: Record = { - recommended: 'Ordem recomendada', - difficulty_asc: 'Dificuldade (fácil → difícil)', - title_az: 'Título (A–Z)', + recommended: "Ordem recomendada", + difficulty_asc: "Dificuldade (fácil → difícil)", + title_az: "Título (A–Z)", }; export function ProblemsCatalogClient({ problems }: Props) { const categories = catalogCategoryLabels(); - const [q, setQ] = useState(''); - const [difficultyFilter, setDifficultyFilter] = useState('all'); - const [categoryFilter, setCategoryFilter] = useState('all'); - const [sortMode, setSortMode] = useState('recommended'); + const [q, setQ] = useState(""); + const [difficultyFilter, setDifficultyFilter] = useState( + "all", + ); + const [categoryFilter, setCategoryFilter] = useState("all"); + const [sortMode, setSortMode] = useState("recommended"); const filteredSorted = useMemo(() => { const f = filterProblems(problems, q, difficultyFilter, categoryFilter); @@ -47,16 +58,20 @@ export function ProblemsCatalogClient({ problems }: Props) { return (
-
- - SYSTEM.CATALOG_v1.0 +
+ + Lista de problemas -

- Catálogo de
problemas +

+ Catálogo de problemas

-

- Filtra por dificuldade e categoria, pesquisa por título e segue a ordem recomendada de aprendizagem. - O progresso fica no teu browser (localStorage); com conta Pro podes sincronizar na nuvem após iniciar sessão. +

+ Filtra por dificuldade e categoria, pesquisa por título e segue a + ordem recomendada. O progresso fica no teu browser; sincroniza na + nuvem com conta Pro.

Trilhos curados (por tema e ordem editorial) - + Novidades

@@ -75,7 +93,10 @@ export function ProblemsCatalogClient({ problems }: Props) {
-
-
-
-
-
-