From 3308ba506fbdd87826631a881fc13c7224481d66 Mon Sep 17 00:00:00 2001 From: brunomachadors Date: Mon, 6 Jan 2025 14:56:10 +0000 Subject: [PATCH] feat(contact-page): add loading spinner, accessibility improvements, and test IDs - Integrated `LoadingSpinner` component to display a loading state during form submission. - Added `data-test-id` attributes for improved testability. - Enhanced accessibility by adding `aria-label`, `aria-labelledby`, and other relevant attributes. - Replaced social media icons with optimized images from the `/public/icons` directory. - Adjusted styles and transitions for better UX and visual consistency. --- public/icons/email.png | Bin 0 -> 1166 bytes public/icons/github.png | Bin 0 -> 3305 bytes public/icons/instagram.png | Bin 0 -> 1981 bytes public/icons/linkedin.png | Bin 0 -> 1448 bytes public/icons/medium.png | Bin 0 -> 1940 bytes src/app/about/page.tsx | 22 ++++ .../AboutSections/AboutSections.tsx | 39 +++--- src/app/components/Button/SkillButton.tsx | 10 +- src/app/components/Footer/Footer.tsx | 10 +- src/app/components/Loading/Loading.tsx | 36 ++++++ src/app/components/layout.tsx | 4 +- src/app/contacts/page.tsx | 103 ++++++++++++---- src/app/content/skills.ts | 4 +- src/app/experience/[companyYear]/page.tsx | 88 ++++++++++++-- src/app/page.tsx | 26 ++++ src/app/posts/page.tsx | 115 ++++++++++++++---- src/app/projects/page.tsx | 98 ++++++++++++--- src/app/resume/page.tsx | 107 +++++++++++++--- src/app/skills/page.tsx | 89 +++++++++++--- src/app/styles/skillStyles.ts | 2 +- tests/about.spec.ts | 2 +- tests/pages/AboutPage.ts | 12 +- 22 files changed, 624 insertions(+), 143 deletions(-) create mode 100644 public/icons/email.png create mode 100644 public/icons/github.png create mode 100644 public/icons/instagram.png create mode 100644 public/icons/linkedin.png create mode 100644 public/icons/medium.png create mode 100644 src/app/components/Loading/Loading.tsx diff --git a/public/icons/email.png b/public/icons/email.png new file mode 100644 index 0000000000000000000000000000000000000000..e4e4ad928f369e18cf23d1e91f68702d82ffff48 GIT binary patch literal 1166 zcmeAS@N?(olHy`uVBq!ia0vp^DIma-1H|F;9g__2o|w#A(jO!ZR=zz~&ilaW!Qwjcwt9y0ZF6118S>cbRwpn0 zCr}V@oAW7G0mFtBryP#A*vzo_=KEmDw#lb^lg|9P5tzDW@lxRg$4A$GN$ptnR;8G6 z2h$HFqrTk@)_)B5w*Fw&_t;bOkL_Kq>17^BP zzA42f!D%x|Vsq$$Lg&8qdMXhI?y&z6=G?$2VSk~Amw67?n%_@4uk`pcx54r!d;Ycjn5@naEfnrxxc2sWUgHMAqt>D~WF@D6 zZ|t-^_ddQO{{csWWZ^t^x1EVh1r{Gll62x2`c_>ywvTgVLFgX#Ij^nw3dA;4#?Keq z%E2kM``~`(wok?ew@XD5tUfrayr16?S$#9C)~Tj>Ib;6x7|pqwPe1>ktZ4SQhTURu zzu9Y1p)-5sRnK`P^qiJ?;PK$5OP_bc;a@r1>lB^Cme;#l{oyR=JT3B|=D}gLRbstd z9~sxN?DM&jpI1JU@eF%i?~~8G1-ZO$&9#rPTS``RmuUWXm|lLeMz)~Jw{VvDBWBq* zr&$f-*!>FJ82cFGe82oHd@w;Onb*xj;DgwM?;c6k4X5WAHgq?>?Yw(=w^WCDgreOG z*8|5pRo)l6G34p)tWYx-DgNNpbo_ue&jZ5?&mWf-F3MkT`m-X}*CXIm{1frsjp9#l zs6CFJ)38NL<;9UblU6<{ZGO@G{&=Wr@R!?cSK7a|{^*;-o72s6EH8eZ#KnR?SCqan zKiH#|JX=I)it;?gbC>E)3yPky=~qo!$KZBd)>v9ml&kD^m-WhH5$R0Qy15%>3JaCw zM0tAtJ|=Z*K3C2B8(KS7&eO3{QQ6UCezlVI`wr&&tmiV-X7h1yZgZWlbgnyen`?WH zxxW9FuX9@8rhRtOI@hEYX0iW=LUMNW<{jraIj?bA3(voHrj)Z`W~cwOeXQ%s+fI0S zFcxraXyej;DL1v@>j8`Oo;NKlTbH$;{`XDmw>D?Rp;zhaBe`xeZ!q}C`1za5$vqZj z3ypoQIdOIEt=#W^zx#aS>A8iQ#Df@QqBkuL|G-wT=yZc0$IFe+=U#nZK38xP`~U6N z`Tk%3z%*TQuJ9ylp(GwoLqv`fNQUJ_?81{~3tZHL=~?`?d=?@j16kqB{~2$}wnRj9 SbsGQ+7zR&QKbLh*2~7Zr76gd^ literal 0 HcmV?d00001 diff --git a/public/icons/github.png b/public/icons/github.png new file mode 100644 index 0000000000000000000000000000000000000000..90be16f61ab36bff90de27bbb82593311f925941 GIT binary patch literal 3305 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 P00000NkvXXu0mjfj0UL8 literal 0 HcmV?d00001 diff --git a/public/icons/linkedin.png b/public/icons/linkedin.png new file mode 100644 index 0000000000000000000000000000000000000000..b22aa05f8e7aa401b75c3e9c957359a54c8f8fd8 GIT binary patch literal 1448 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 aBL4w_EDx0DvF&dF0000 { + const timer = setTimeout(() => { + setIsLoading(false); + }, 1000); // Simulate a loading time, adjust as needed + return () => clearTimeout(timer); + }, []); + + if (isLoading) { + return ( +
+ +
+ ); + } + return (
diff --git a/src/app/components/AboutSections/AboutSections.tsx b/src/app/components/AboutSections/AboutSections.tsx index 0205cf4..6f9f396 100644 --- a/src/app/components/AboutSections/AboutSections.tsx +++ b/src/app/components/AboutSections/AboutSections.tsx @@ -1,6 +1,5 @@ 'use client'; -import React from 'react'; -import { useState } from 'react'; +import React, { useState } from 'react'; const sections = [ { @@ -64,56 +63,62 @@ export default function AboutSections() {

{sections.map((section, index) => ( -
toggleSection(index)} > -
- +

{section.title} - +

toggleSection(index)} - role="button" // Semântica de acessibilidade - aria-expanded={openSection === index} > ▼ -
+ {openSection === index && (
{typeof section.content === 'string' ? ( - - {section.content} - +

{section.content}

) : ( -
+
{React.Children.map( section.content.props.children, (child, childIndex) => ( -

{child} -

+
) )}
)}
)} -
+ ))} ); diff --git a/src/app/components/Button/SkillButton.tsx b/src/app/components/Button/SkillButton.tsx index 87130be..ce14f64 100644 --- a/src/app/components/Button/SkillButton.tsx +++ b/src/app/components/Button/SkillButton.tsx @@ -5,6 +5,7 @@ interface SkillButtonProps { description?: string; isActive: boolean; onClick: () => void; + testId?: string; // Novo parâmetro para test ID } export default function SkillButton({ @@ -12,6 +13,7 @@ export default function SkillButton({ description = 'This is a placeholder description for the skill.', isActive, onClick, + testId, // Recebendo test ID }: SkillButtonProps) { const colorClasses = skillColors[text] || defaultSkillColor; @@ -26,6 +28,7 @@ export default function SkillButton({ alignItems: 'center', justifyContent: 'center', }} + data-test-id={testId} // Adicionando test ID ao container principal > @@ -97,52 +119,101 @@ export default function MediumFeed() { - {/* Posts Grid */} -
+
{filteredPosts.map((post, index) => (
{post.thumbnail && ( -
+
{post.title}
)} -
-

+
+

{post.title}

-

+

{extractFirstParagraph(post.content)}

-

+

{new Date(post.pubDate).toLocaleDateString()} by {post.author}

-
+
{post.categories.map((category, idx) => ( {category} ))}
-
- +
+
))}
-

+
); } diff --git a/src/app/projects/page.tsx b/src/app/projects/page.tsx index c329021..24b5516 100644 --- a/src/app/projects/page.tsx +++ b/src/app/projects/page.tsx @@ -1,65 +1,116 @@ 'use client'; import Image from 'next/image'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { PROJECTS } from '../content/projects'; import { projectStyles } from '../styles/projectStyles'; import LinkButton from '../components/Button/LinkButton'; +import LoadingSpinner from '../components/Loading/Loading'; export default function ProjectsPage() { + const [isClient, setIsClient] = useState(false); const [openSection, setOpenSection] = useState(null); + useEffect(() => { + setIsClient(true); + }, []); + const toggleSection = (title: string, sectionIndex: number) => { const key = `${title}-${sectionIndex}`; setOpenSection(openSection === key ? null : key); }; + if (!isClient) { + return ( +
+ +
+ ); + } + return ( -
-
- {PROJECTS.map((project) => ( +
+

+ Projects +

+ +
+ {PROJECTS.map((project, projectIndex) => (
{/* Logo Section */} -
+
{`${project.title}
{/* Content Section */} -
-

+
+

{project.title}

-

+

{project.description}

- {project.sections.map((section, index) => ( + {project.sections.map((section, sectionIndex) => (
toggleSection(project.title, index)} + onClick={() => toggleSection(project.title, sectionIndex)} + aria-expanded={ + openSection === `${project.title}-${sectionIndex}` + } + aria-controls={`section-content-${projectIndex}-${sectionIndex}`} + data-test-id={`section-toggle-${projectIndex}-${sectionIndex}`} > {section.title}
- {openSection === `${project.title}-${index}` && ( -
+ {openSection === `${project.title}-${sectionIndex}` && ( +
{section.content}
)} @@ -80,9 +135,18 @@ export default function ProjectsPage() {
{/* Go to Posts Button */} -
- +
+
-
+

); } diff --git a/src/app/resume/page.tsx b/src/app/resume/page.tsx index e5c87b5..38c1302 100644 --- a/src/app/resume/page.tsx +++ b/src/app/resume/page.tsx @@ -1,52 +1,119 @@ 'use client'; + +import { useState, useEffect } from 'react'; import Link from 'next/link'; import { EXPERIENCES } from '../content/experiences'; import LinkButton from '../components/Button/LinkButton'; +import LoadingSpinner from '../components/Loading/Loading'; export default function Resume() { + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const timeout = setTimeout(() => setIsLoading(false), 500); + return () => clearTimeout(timeout); + }, []); + + if (isLoading) { + return ( +
+ +
+ ); + } + return ( -
- {/* Linha do Tempo */} -
+
+

+ Resume Page +

+ + {/* Timeline */} +
{EXPERIENCES.map((item, index) => ( -
- {/* Ano */} -
+
+ {/* Year */} +
{item.year}
-
+ - {/* Detalhes da Vaga */} -
-

+ {/* Job Details */} +
+

{item.company}

-

{item.period}

-

+

+ {item.period} +

+

{item.role}

-

+

{item.shortDescription}

- {/* Centralizar texto e botão */} -
+ {/* Centralize button */} +
Know more -

))}

- {/* Botão Centralizado Abaixo */} -
- + + {/* Button to go to Skills */} +
+
-
+
); } diff --git a/src/app/skills/page.tsx b/src/app/skills/page.tsx index 4a4c9af..5fd6fa5 100644 --- a/src/app/skills/page.tsx +++ b/src/app/skills/page.tsx @@ -1,13 +1,20 @@ 'use client'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import SkillButton from '../components/Button/SkillButton'; import { SKILLS } from '../content/skills'; import LinkButton from '../components/Button/LinkButton'; +import LoadingSpinner from '../components/Loading/Loading'; export default function SkillsPage() { - const [selectedTab, setSelectedTab] = useState('All'); // Aba "All" selecionada por padrão + const [selectedTab, setSelectedTab] = useState('All'); // Default tab const [activeSkill, setActiveSkill] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const timeout = setTimeout(() => setIsLoading(false), 500); // Simulating data loading + return () => clearTimeout(timeout); + }, []); const categories = selectedTab === 'All' @@ -21,10 +28,34 @@ export default function SkillsPage() { setActiveSkill((prev) => (prev === skill ? null : skill)); }; + if (isLoading) { + return ( +
+ +
+ ); + } + return ( -
+
+

+ Skills Page +

+ {/* Tabs */} -
+
{tabs.map((tab, index) => ( @@ -43,13 +78,25 @@ export default function SkillsPage() {
{/* Skills */} -
+
{categories.map((subcategory, subIndex) => ( -
-

+
+

{subcategory.name}

-
+
{subcategory.items.map(({ text, description }, itemIndex) => ( handleSkillClick(text)} + testId={`skill-button-${text.toLowerCase()}`} + aria-pressed={activeSkill === text} /> ))}
-
+
))}

- {/* Divisória - Final das Skills */} -
+ {/* Divider */} +
); } diff --git a/src/app/styles/skillStyles.ts b/src/app/styles/skillStyles.ts index 14bbf1e..6ab8dd5 100644 --- a/src/app/styles/skillStyles.ts +++ b/src/app/styles/skillStyles.ts @@ -62,7 +62,7 @@ export const skillColors: Record = { // Interpersonal Skills 'Public Speaking': 'text-purple-500 border-purple-500', 'Didactic Explanations': 'text-purple-500 border-purple-500', - 'Talks on Accessibility and Security': 'text-purple-500 border-purple-500', + Talks: 'text-purple-500 border-purple-500', 'Commitment to Goals': 'text-blue-400 border-blue-400', 'Critical Thinking': 'text-blue-400 border-blue-400', diff --git a/tests/about.spec.ts b/tests/about.spec.ts index 10c4502..6902585 100644 --- a/tests/about.spec.ts +++ b/tests/about.spec.ts @@ -31,7 +31,7 @@ test.describe('About Page', () => { const { title, content } = ABOUT_DATA.sections[i]; await test.step(`Section ${i}: ${title}`, async () => { - await aboutPage.clickToggleIcon(i); + await aboutPage.clickSession(i); await aboutPage.validateSectionTitleVisible(i, title); await aboutPage.validateSectionContentVisible(i, content); }); diff --git a/tests/pages/AboutPage.ts b/tests/pages/AboutPage.ts index a3b3e94..1954e53 100644 --- a/tests/pages/AboutPage.ts +++ b/tests/pages/AboutPage.ts @@ -43,18 +43,16 @@ export class AboutPage { await expect(containerLocator).toBeVisible(); } - async clickToggleIcon(index: number) { - const toggleIcon = this.page.locator( - `[data-test-id="toggle-icon-${index}"]` - ); - await toggleIcon.click(); + async clickSession(index: number) { + const session = this.page.locator(`[data-test-id="section-${index}"]`); + await session.click(); const contentLocator = this.page.locator( `[data-test-id="section-content-${index}"]` ); await this.page.waitForTimeout(300); if (!(await contentLocator.isVisible())) { - await toggleIcon.click(); + await session.click(); await this.page.waitForTimeout(300); } } @@ -76,7 +74,7 @@ export class AboutPage { ); if (!(await contentLocator.isVisible())) { - await this.clickToggleIcon(index); + await this.clickSession(index); } await expect(contentLocator).toBeVisible();