From 1114c88c5357deb28ed363204adee28517ea1c81 Mon Sep 17 00:00:00 2001 From: Xavier Saliniere Date: Tue, 2 Sep 2025 10:08:51 -0400 Subject: [PATCH 1/6] chore: fix readme empty checkboxes --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0ac0ea2..7a4b622 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ Still a work in progress, but it already lives on the InterPlanetary FileSystem: - [x] responsive design - [x] internationalization - [x] automatic deployment on IPFS -- [] content is still being written -- [] pages are still being added & refined +- [ ] content is still being written +- [ ] pages are still being added & refined ## commands From 538714527389c008717a8526de1f302c6fab299b Mon Sep 17 00:00:00 2001 From: Xavier Saliniere Date: Tue, 2 Sep 2025 11:26:54 -0400 Subject: [PATCH 2/6] feat: replace web3Mission with web2work, web3work and contribs --- src/app/[locale]/contribs/page.tsx | 10 ++++ src/app/[locale]/mission/page.tsx | 10 ---- src/app/[locale]/web2work/page.tsx | 10 ++++ src/app/[locale]/web3work/page.tsx | 10 ++++ src/components/cmd-outputs/ContribsOutput.tsx | 9 ++++ src/components/cmd-outputs/Web2workOutput.tsx | 9 ++++ .../cmd-outputs/Web3MissionOutput.tsx | 9 ---- src/components/cmd-outputs/Web3workOutput.tsx | 9 ++++ .../__tests__/ContribsOutput.test.tsx | 22 +++++++++ .../__tests__/Web2workOutput.test.tsx | 22 +++++++++ .../__tests__/Web3MissionOutput.test.tsx | 23 --------- .../__tests__/Web3workOutput.test.tsx | 22 +++++++++ src/components/contribs/Contribs.tsx | 3 ++ .../layout/__tests__/TermWindow.test.tsx | 4 +- .../terminal/__tests__/CmdLink.test.tsx | 8 ++-- .../__tests__/TerminalEmulator.test.tsx | 14 ++---- src/components/web2work/Web2work.tsx | 3 ++ src/components/web3-mission/Web3Mission.tsx | 3 -- src/components/web3work/Web3work.tsx | 3 ++ src/constants/__tests__/commands.test.ts | 4 +- src/constants/__tests__/routes.test.ts | 4 +- src/constants/commands.ts | 48 ++++++++++++------- src/constants/routes.ts | 4 +- src/i18n/messages/en.json | 16 +++++-- src/i18n/messages/fr.json | 16 +++++-- src/types/__tests__/terminal.test.ts | 5 +- src/types/terminal.ts | 4 +- 27 files changed, 213 insertions(+), 91 deletions(-) create mode 100644 src/app/[locale]/contribs/page.tsx delete mode 100644 src/app/[locale]/mission/page.tsx create mode 100644 src/app/[locale]/web2work/page.tsx create mode 100644 src/app/[locale]/web3work/page.tsx create mode 100644 src/components/cmd-outputs/ContribsOutput.tsx create mode 100644 src/components/cmd-outputs/Web2workOutput.tsx delete mode 100644 src/components/cmd-outputs/Web3MissionOutput.tsx create mode 100644 src/components/cmd-outputs/Web3workOutput.tsx create mode 100644 src/components/cmd-outputs/__tests__/ContribsOutput.test.tsx create mode 100644 src/components/cmd-outputs/__tests__/Web2workOutput.test.tsx delete mode 100644 src/components/cmd-outputs/__tests__/Web3MissionOutput.test.tsx create mode 100644 src/components/cmd-outputs/__tests__/Web3workOutput.test.tsx create mode 100644 src/components/contribs/Contribs.tsx create mode 100644 src/components/web2work/Web2work.tsx delete mode 100644 src/components/web3-mission/Web3Mission.tsx create mode 100644 src/components/web3work/Web3work.tsx diff --git a/src/app/[locale]/contribs/page.tsx b/src/app/[locale]/contribs/page.tsx new file mode 100644 index 0000000..47def9a --- /dev/null +++ b/src/app/[locale]/contribs/page.tsx @@ -0,0 +1,10 @@ +import Contribs from '@/components/contribs/Contribs'; +import type { RouteData } from '@/types/routing'; +import { setPageMeta } from '@/utils/metadata-utils'; + +export const generateMetadata = async (routeData: RouteData) => + await setPageMeta(routeData, 'contribs'); + +export default function ContribsPage() { + return ; +} diff --git a/src/app/[locale]/mission/page.tsx b/src/app/[locale]/mission/page.tsx deleted file mode 100644 index b777840..0000000 --- a/src/app/[locale]/mission/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import Web3Mission from '@/components/web3-mission/Web3Mission'; -import type { RouteData } from '@/types/routing'; -import { setPageMeta } from '@/utils/metadata-utils'; - -export const generateMetadata = async (routeData: RouteData) => - await setPageMeta(routeData, 'mission'); - -export default function MissionPage() { - return ; -} diff --git a/src/app/[locale]/web2work/page.tsx b/src/app/[locale]/web2work/page.tsx new file mode 100644 index 0000000..b9549ee --- /dev/null +++ b/src/app/[locale]/web2work/page.tsx @@ -0,0 +1,10 @@ +import Web2work from '@/components/web2work/Web2work'; +import type { RouteData } from '@/types/routing'; +import { setPageMeta } from '@/utils/metadata-utils'; + +export const generateMetadata = async (routeData: RouteData) => + await setPageMeta(routeData, 'web2work'); + +export default function Web3workPage() { + return ; +} diff --git a/src/app/[locale]/web3work/page.tsx b/src/app/[locale]/web3work/page.tsx new file mode 100644 index 0000000..76ef013 --- /dev/null +++ b/src/app/[locale]/web3work/page.tsx @@ -0,0 +1,10 @@ +import Web3work from '@/components/web3work/Web3work'; +import type { RouteData } from '@/types/routing'; +import { setPageMeta } from '@/utils/metadata-utils'; + +export const generateMetadata = async (routeData: RouteData) => + await setPageMeta(routeData, 'web3work'); + +export default function Web3workPage() { + return ; +} diff --git a/src/components/cmd-outputs/ContribsOutput.tsx b/src/components/cmd-outputs/ContribsOutput.tsx new file mode 100644 index 0000000..10f86a6 --- /dev/null +++ b/src/components/cmd-outputs/ContribsOutput.tsx @@ -0,0 +1,9 @@ +import { Component } from 'react'; +import type { CommandOutputProps } from '@/types/terminal'; +import Contribs from '../contribs/Contribs'; + +export default class ContribsOutput extends Component { + render() { + return ; + } +} diff --git a/src/components/cmd-outputs/Web2workOutput.tsx b/src/components/cmd-outputs/Web2workOutput.tsx new file mode 100644 index 0000000..489e114 --- /dev/null +++ b/src/components/cmd-outputs/Web2workOutput.tsx @@ -0,0 +1,9 @@ +import { Component } from 'react'; +import type { CommandOutputProps } from '@/types/terminal'; +import Web2work from '../web2work/Web2work'; + +export default class Web2workOutput extends Component { + render() { + return ; + } +} diff --git a/src/components/cmd-outputs/Web3MissionOutput.tsx b/src/components/cmd-outputs/Web3MissionOutput.tsx deleted file mode 100644 index 7be0101..0000000 --- a/src/components/cmd-outputs/Web3MissionOutput.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Component } from 'react'; -import type { CommandOutputProps } from '@/types/terminal'; -import Web3Mission from '../web3-mission/Web3Mission'; - -export default class Web3MissionOutput extends Component { - render() { - return ; - } -} diff --git a/src/components/cmd-outputs/Web3workOutput.tsx b/src/components/cmd-outputs/Web3workOutput.tsx new file mode 100644 index 0000000..2a881f3 --- /dev/null +++ b/src/components/cmd-outputs/Web3workOutput.tsx @@ -0,0 +1,9 @@ +import { Component } from 'react'; +import type { CommandOutputProps } from '@/types/terminal'; +import Web3work from '../web3work/Web3work'; + +export default class Web3workOutput extends Component { + render() { + return ; + } +} diff --git a/src/components/cmd-outputs/__tests__/ContribsOutput.test.tsx b/src/components/cmd-outputs/__tests__/ContribsOutput.test.tsx new file mode 100644 index 0000000..50f843b --- /dev/null +++ b/src/components/cmd-outputs/__tests__/ContribsOutput.test.tsx @@ -0,0 +1,22 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import type { Translator } from '@/i18n/intl'; +import { Command, type CommandEntry } from '@/types/terminal'; +import ContribsOutput from '../ContribsOutput'; + +vi.mock('../contribs/Contribs', () => ({ + default: () =>
contribs / todo
, +})); + +describe('ContribsOutput', () => { + const mockT = vi.fn((key: string) => key) as unknown as Translator; + const mockEntry: CommandEntry = { + timestamp: Date.now(), + cmdName: Command.Contribs, + }; + + it('renders the Contribs component', () => { + render(); + expect(screen.getByText('contribs / todo')).toBeInTheDocument(); + }); +}); diff --git a/src/components/cmd-outputs/__tests__/Web2workOutput.test.tsx b/src/components/cmd-outputs/__tests__/Web2workOutput.test.tsx new file mode 100644 index 0000000..e000be4 --- /dev/null +++ b/src/components/cmd-outputs/__tests__/Web2workOutput.test.tsx @@ -0,0 +1,22 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import type { Translator } from '@/i18n/intl'; +import { Command, type CommandEntry } from '@/types/terminal'; +import Web2workOutput from '../Web2workOutput'; + +vi.mock('../web2work/Web2work', () => ({ + default: () =>
web2work / todo
, +})); + +describe('Web2workOutput', () => { + const mockT = vi.fn((key: string) => key) as unknown as Translator; + const mockEntry: CommandEntry = { + timestamp: Date.now(), + cmdName: Command.Web2work, + }; + + it('renders the Web2work component', () => { + render(); + expect(screen.getByText('web2work / todo')).toBeInTheDocument(); + }); +}); diff --git a/src/components/cmd-outputs/__tests__/Web3MissionOutput.test.tsx b/src/components/cmd-outputs/__tests__/Web3MissionOutput.test.tsx deleted file mode 100644 index e88317c..0000000 --- a/src/components/cmd-outputs/__tests__/Web3MissionOutput.test.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import { describe, expect, it, vi } from 'vitest'; -import type { Translator } from '@/i18n/intl'; -import { Command, type CommandEntry } from '@/types/terminal'; -import Web3MissionOutput from '../Web3MissionOutput'; - -// Mock the Web3Mission component - it just renders simple text -vi.mock('../web3-mission/Web3Mission', () => ({ - default: () =>
web3 mission / todo
, -})); - -describe('Web3MissionOutput', () => { - const mockT = vi.fn((key: string) => key) as unknown as Translator; - const mockEntry: CommandEntry = { - timestamp: Date.now(), - cmdName: Command.Web3Mission, - }; - - it('renders the Web3Mission component', () => { - render(); - expect(screen.getByText('web3 mission / todo')).toBeInTheDocument(); - }); -}); diff --git a/src/components/cmd-outputs/__tests__/Web3workOutput.test.tsx b/src/components/cmd-outputs/__tests__/Web3workOutput.test.tsx new file mode 100644 index 0000000..6cdaee8 --- /dev/null +++ b/src/components/cmd-outputs/__tests__/Web3workOutput.test.tsx @@ -0,0 +1,22 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import type { Translator } from '@/i18n/intl'; +import { Command, type CommandEntry } from '@/types/terminal'; +import Web3workOutput from '../Web3workOutput'; + +vi.mock('../web3work/Web3work', () => ({ + default: () =>
web3work / todo
, +})); + +describe('Web3workOutput', () => { + const mockT = vi.fn((key: string) => key) as unknown as Translator; + const mockEntry: CommandEntry = { + timestamp: Date.now(), + cmdName: Command.Web3work, + }; + + it('renders the Web3work component', () => { + render(); + expect(screen.getByText('web3work / todo')).toBeInTheDocument(); + }); +}); diff --git a/src/components/contribs/Contribs.tsx b/src/components/contribs/Contribs.tsx new file mode 100644 index 0000000..b0cef0c --- /dev/null +++ b/src/components/contribs/Contribs.tsx @@ -0,0 +1,3 @@ +export default function Contribs() { + return
contribs / todo
; +} diff --git a/src/components/layout/__tests__/TermWindow.test.tsx b/src/components/layout/__tests__/TermWindow.test.tsx index 9dda2bf..864fda8 100644 --- a/src/components/layout/__tests__/TermWindow.test.tsx +++ b/src/components/layout/__tests__/TermWindow.test.tsx @@ -13,7 +13,7 @@ vi.mock('@/i18n/intl', () => ({ Link: ({ children, href }: { children: React.ReactNode; href: string }) => ( {children} ), - usePathname: () => '/mission', + usePathname: () => '/web2work', })); // Mock LSD React components @@ -67,7 +67,7 @@ describe('TerminalWindow', () => { render({mockChildren}); const tabs = screen.getByTestId('tabs'); - expect(tabs).toHaveAttribute('data-active-tab', 'mission'); + expect(tabs).toHaveAttribute('data-active-tab', 'web2work'); }); it('renders tab items for all routes', () => { diff --git a/src/components/terminal/__tests__/CmdLink.test.tsx b/src/components/terminal/__tests__/CmdLink.test.tsx index 6b02030..de4ebc2 100644 --- a/src/components/terminal/__tests__/CmdLink.test.tsx +++ b/src/components/terminal/__tests__/CmdLink.test.tsx @@ -81,7 +81,7 @@ describe('CmdLink', () => { it('handles click with command argument', () => { const cmdInfo: CommandInfo = { - name: Command.Web3Mission, + name: Command.Web2work, }; const arg: CommandArgument = { name: 'framework', @@ -92,7 +92,7 @@ describe('CmdLink', () => { const button = screen.getByRole('button'); fireEvent.click(button); - expect(mockSetInput).toHaveBeenCalledWith('web3-mission --framework='); + expect(mockSetInput).toHaveBeenCalledWith('web2work --framework='); expect(mockSetSimulatedCmd).not.toHaveBeenCalled(); }); @@ -109,7 +109,7 @@ describe('CmdLink', () => { it('renders argument options when arg has options', () => { const cmdInfo: CommandInfo = { - name: Command.Web3Mission, + name: Command.Web2work, }; const arg: CommandArgument = { name: 'framework', @@ -123,7 +123,7 @@ describe('CmdLink', () => { it('truncates argument options when more than 6', () => { const cmdInfo: CommandInfo = { - name: Command.Web3Mission, + name: Command.Web2work, }; const arg: CommandArgument = { name: 'framework', diff --git a/src/components/terminal/__tests__/TerminalEmulator.test.tsx b/src/components/terminal/__tests__/TerminalEmulator.test.tsx index d8bb7df..fa23385 100644 --- a/src/components/terminal/__tests__/TerminalEmulator.test.tsx +++ b/src/components/terminal/__tests__/TerminalEmulator.test.tsx @@ -47,11 +47,9 @@ vi.mock('@/utils/terminal-utils', () => ({ output: () =>
Whoami Output
, timestamp: Date.now(), }, - 'web3-mission': { - cmdName: Command.Web3Mission, - output: () => ( -
Web3 Mission Output
- ), + web2work: { + cmdName: Command.Web2work, + output: () =>
Web2work Output
, timestamp: Date.now(), }, }; @@ -109,10 +107,8 @@ vi.mock('../../cmd-outputs/WhoamiOutput', () => ({ default: () =>
Whoami Output
, })); -vi.mock('../../cmd-outputs/Web3MissionOutput', () => ({ - default: () => ( -
Web3 Mission Output
- ), +vi.mock('../../cmd-outputs/Web2workOutput', () => ({ + default: () =>
Web2work Output
, })); describe('TerminalEmulator', () => { diff --git a/src/components/web2work/Web2work.tsx b/src/components/web2work/Web2work.tsx new file mode 100644 index 0000000..2dcb8f0 --- /dev/null +++ b/src/components/web2work/Web2work.tsx @@ -0,0 +1,3 @@ +export default function Web2work() { + return
web2work / todo
; +} diff --git a/src/components/web3-mission/Web3Mission.tsx b/src/components/web3-mission/Web3Mission.tsx deleted file mode 100644 index 41707fc..0000000 --- a/src/components/web3-mission/Web3Mission.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Web3Mission() { - return
web3 mission / todo
; -} diff --git a/src/components/web3work/Web3work.tsx b/src/components/web3work/Web3work.tsx new file mode 100644 index 0000000..e039879 --- /dev/null +++ b/src/components/web3work/Web3work.tsx @@ -0,0 +1,3 @@ +export default function Web3work() { + return
web3work / todo
; +} diff --git a/src/constants/__tests__/commands.test.ts b/src/constants/__tests__/commands.test.ts index 9211b5a..e752587 100644 --- a/src/constants/__tests__/commands.test.ts +++ b/src/constants/__tests__/commands.test.ts @@ -13,7 +13,9 @@ describe('commands', () => { expect(commandNames).toContain(Command.Intro); expect(commandNames).toContain(Command.SetLang); expect(commandNames).toContain(Command.Whoami); - expect(commandNames).toContain(Command.Web3Mission); + expect(commandNames).toContain(Command.Web2work); + expect(commandNames).toContain(Command.Web3work); + expect(commandNames).toContain(Command.Contribs); }); }); }); diff --git a/src/constants/__tests__/routes.test.ts b/src/constants/__tests__/routes.test.ts index 8d35819..c33b32e 100644 --- a/src/constants/__tests__/routes.test.ts +++ b/src/constants/__tests__/routes.test.ts @@ -6,7 +6,9 @@ describe('routes', () => { it('should have all expected routes', () => { expect(Routes.terminal).toBe('/'); expect(Routes.whoami).toBe('/whoami'); - expect(Routes.mission).toBe('/mission'); + expect(Routes.web2work).toBe('/web2work'); + expect(Routes.web3work).toBe('/web3work'); + expect(Routes.contribs).toBe('/contribs'); expect(Routes.contact).toBe('/contact'); }); }); diff --git a/src/constants/commands.ts b/src/constants/commands.ts index 90f74c5..f2e27a2 100644 --- a/src/constants/commands.ts +++ b/src/constants/commands.ts @@ -11,8 +11,14 @@ const ContactOutput = lazy( ); const HelpOutput = lazy(() => import('@/components/cmd-outputs/HelpOutput')); const IntroOutput = lazy(() => import('@/components/cmd-outputs/IntroOutput')); -const Web3MissionOutput = lazy( - () => import('@/components/cmd-outputs/Web3MissionOutput'), +const Web2workOutput = lazy( + () => import('@/components/cmd-outputs/Web2workOutput'), +); +const Web3workOutput = lazy( + () => import('@/components/cmd-outputs/Web3workOutput'), +); +const ContribsOutput = lazy( + () => import('@/components/cmd-outputs/ContribsOutput'), ); const WhoamiOutput = lazy( () => import('@/components/cmd-outputs/WhoamiOutput'), @@ -26,35 +32,43 @@ function enumToArg(o: { [s: string]: string } | ArrayLike): string[] { export const Commands: CommandInfo[] = [ { - name: Command.BuildInfo, - output: BuildInfoOutput, + name: Command.Intro, + output: IntroOutput, }, { - name: Command.Clear, + name: Command.Whoami, + output: WhoamiOutput, + }, + { + name: Command.Web2work, + output: Web2workOutput, + }, + { + name: Command.Web3work, + output: Web3workOutput, + }, + { + name: Command.Contribs, + output: ContribsOutput, }, { name: Command.Contact, output: ContactOutput, }, { - name: Command.Help, - output: HelpOutput, + name: Command.BuildInfo, + output: BuildInfoOutput, }, { - name: Command.Intro, - output: IntroOutput, + name: Command.Clear, + }, + { + name: Command.Help, + output: HelpOutput, }, { name: Command.SetLang, options: enumToArg(Lang), // output: SetLangOutput, }, - { - name: Command.Whoami, - output: WhoamiOutput, - }, - { - name: Command.Web3Mission, - output: Web3MissionOutput, - }, ]; diff --git a/src/constants/routes.ts b/src/constants/routes.ts index 0926302..88583aa 100644 --- a/src/constants/routes.ts +++ b/src/constants/routes.ts @@ -1,6 +1,8 @@ export const Routes = { terminal: '/', whoami: '/whoami', - mission: '/mission', + web2work: '/web2work', + web3work: '/web3work', + contribs: '/contribs', contact: '/contact', }; diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index afccd70..d0e83ed 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -19,7 +19,9 @@ "Pages": { "terminal": "terminal", "whoami": "whoami", - "mission": "web3_mission", + "web2work": "web2work", + "web3work": "web3work", + "contribs": "contribs", "contact": "contact" }, "Terminal": { @@ -52,11 +54,17 @@ "set-lang": { "description": "Change language" }, - "web3-mission": { - "description": "Discover what I'm working on in the web3 space" + "web2work": { + "description": "Things I have worked on at web2 companies" + }, + "web3work": { + "description": "How I'm helping building web3" + }, + "contribs": { + "description": "My contributions to open-source projects" }, "whoami": { - "description": "Learn more about me" + "description": "Allow me to introduce myself" } } } diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index d34db54..2c1f964 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -19,7 +19,9 @@ "Pages": { "terminal": "terminal", "whoami": "qui_suis_je", - "mission": "mission_web3", + "web2work": "travail_web2", + "web3work": "travail_web3", + "contribs": "contributions", "contact": "contact" }, "Terminal": { @@ -52,11 +54,17 @@ "set-lang": { "description": "Changer la langue" }, - "web3-mission": { - "description": "Découvrez ce sur quoi je travaille dans l'espace web3" + "web2work": { + "description": "Projets sur lesquels j'ai travaillé dans des entreprises web2" + }, + "web3work": { + "description": "Comment je contribue à la construction du web3" + }, + "contribs": { + "description": "Mes contributions à des projets open source" }, "whoami": { - "description": "En savoir plus sur moi" + "description": "Permettez-moi de me présenter" } } } diff --git a/src/types/__tests__/terminal.test.ts b/src/types/__tests__/terminal.test.ts index 321565f..deaad19 100644 --- a/src/types/__tests__/terminal.test.ts +++ b/src/types/__tests__/terminal.test.ts @@ -3,7 +3,6 @@ import type { CommandArgument, CommandEntry, CommandInfo, - CommandOutputProps, } from '@/types/terminal'; import { Command } from '@/types/terminal'; @@ -15,7 +14,9 @@ describe('terminal types', () => { expect(Command.Contact).toBe('contact'); expect(Command.Help).toBe('help'); expect(Command.Intro).toBe('intro'); - expect(Command.Web3Mission).toBe('web3-mission'); + expect(Command.Web2work).toBe('web2work'); + expect(Command.Web3work).toBe('web3work'); + expect(Command.Contribs).toBe('contribs'); expect(Command.SetLang).toBe('set-lang'); expect(Command.Whoami).toBe('whoami'); }); diff --git a/src/types/terminal.ts b/src/types/terminal.ts index b426a7a..b58a945 100644 --- a/src/types/terminal.ts +++ b/src/types/terminal.ts @@ -7,7 +7,9 @@ export enum Command { Contact = 'contact', Help = 'help', Intro = 'intro', - Web3Mission = 'web3-mission', + Web2work = 'web2work', + Web3work = 'web3work', + Contribs = 'contribs', SetLang = 'set-lang', Whoami = 'whoami', } From fa492b7684f54eab548987d58ec437812922b89c Mon Sep 17 00:00:00 2001 From: Xavier Saliniere Date: Tue, 2 Sep 2025 11:30:09 -0400 Subject: [PATCH 3/6] chore: remove github ai instructions file --- .github/instructions/ai.instructions.md | 54 ------------------------- 1 file changed, 54 deletions(-) delete mode 100644 .github/instructions/ai.instructions.md diff --git a/.github/instructions/ai.instructions.md b/.github/instructions/ai.instructions.md deleted file mode 100644 index 62875cb..0000000 --- a/.github/instructions/ai.instructions.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -applyTo: "**" ---- - -# AI Documentation Guidelines - -## ⚠️ STOP - READ THIS FIRST - -**BEFORE doing ANYTHING else, you MUST follow this workflow:** - -### Required Workflow - -1. **First, check for `.ai` directory documentation** - ```bash - # Search .ai docs for relevant keywords - grep -r "relevant_keywords" .ai/ - ``` - -2. **Read applicable documentation in this order:** - - `.ai/README.md` - Project overview and quick start - - `.ai/ARCHITECTURE.md` - Key components and architecture - - `.ai/DEVELOPMENT.md` - Setup and coding conventions - - `.ai/COMMANDS.md` - Command system documentation (if working with commands) - -3. **Acknowledge understanding by stating:** - - "After reading .ai/[FILE].md, I understand that..." - - Reference specific patterns or conventions you'll follow - -4. **THEN proceed with your task** - -## Purpose - -The `.ai` directory contains comprehensive documentation to help AI agents quickly understand and continue development on the project. It serves as a knowledge base for the project's architecture, features, and development process. - -## Usage Guidelines - -1. **ALWAYS consult the `.ai` documentation when starting work on the project** -2. Use the documentation to understand the project structure and conventions -3. Reference specific patterns from the docs in your implementation -4. When in doubt, search the `.ai` files for guidance - -## Maintenance - -1. Update the documentation when making significant changes to the project -2. Keep the documentation accurate and up-to-date -3. Add new sections when introducing major features or architectural changes -4. Remove outdated information promptly - -## Decision Making - -1. Use the documentation as a reference when making architectural decisions -2. Follow established patterns and conventions documented in `.ai` -3. Consider the impact of changes on documented components and features -4. Update documentation to reflect any approved changes From 31c01491dfcb74d4992cb1a57b21b1d5c55483c0 Mon Sep 17 00:00:00 2001 From: Xavier Saliniere Date: Tue, 2 Sep 2025 11:40:49 -0400 Subject: [PATCH 4/6] refactor: rename intro command to welcome --- .../{IntroOutput.tsx => WelcomeOutput.tsx} | 8 ++-- ...Output.test.tsx => WelcomeOutput.test.tsx} | 20 +++++----- src/components/terminal/TerminalEmulator.tsx | 12 +++--- .../terminal/__tests__/CmdLink.test.tsx | 4 +- .../__tests__/TerminalEmulator.test.tsx | 40 +++++++++---------- src/constants/__tests__/commands.test.ts | 2 +- src/constants/commands.ts | 8 ++-- src/contexts/TerminalContext.tsx | 14 +++---- .../__tests__/TerminalContext.test.tsx | 14 +++---- src/i18n/messages/en.json | 4 +- src/i18n/messages/fr.json | 2 +- src/types/__tests__/terminal.test.ts | 2 +- src/types/terminal.ts | 2 +- 13 files changed, 67 insertions(+), 65 deletions(-) rename src/components/cmd-outputs/{IntroOutput.tsx => WelcomeOutput.tsx} (73%) rename src/components/cmd-outputs/__tests__/{IntroOutput.test.tsx => WelcomeOutput.test.tsx} (76%) diff --git a/src/components/cmd-outputs/IntroOutput.tsx b/src/components/cmd-outputs/WelcomeOutput.tsx similarity index 73% rename from src/components/cmd-outputs/IntroOutput.tsx rename to src/components/cmd-outputs/WelcomeOutput.tsx index d8cff1d..cdfb0ea 100644 --- a/src/components/cmd-outputs/IntroOutput.tsx +++ b/src/components/cmd-outputs/WelcomeOutput.tsx @@ -3,7 +3,7 @@ import asciiArt from '@/assets/ascii-art'; import { Command, type CommandOutputProps } from '@/types/terminal'; import CmdLink from '../terminal/CmdLink'; -export default class IntroOutput extends Component { +export default class WelcomeOutput extends Component { render() { return (
@@ -12,14 +12,14 @@ export default class IntroOutput extends Component {

- {this.props.t.rich('cmds.intro.welcome', { + {this.props.t.rich('cmds.welcome.welcome', { name: (name) => {name}, })}

-

{this.props.t('cmds.intro.site_intro_1')}

+

{this.props.t('cmds.welcome.site_intro_1')}

- {this.props.t.rich('cmds.intro.site_intro_2', { + {this.props.t.rich('cmds.welcome.site_intro_2', { cmd: () => , })}

diff --git a/src/components/cmd-outputs/__tests__/IntroOutput.test.tsx b/src/components/cmd-outputs/__tests__/WelcomeOutput.test.tsx similarity index 76% rename from src/components/cmd-outputs/__tests__/IntroOutput.test.tsx rename to src/components/cmd-outputs/__tests__/WelcomeOutput.test.tsx index d840d66..f449450 100644 --- a/src/components/cmd-outputs/__tests__/IntroOutput.test.tsx +++ b/src/components/cmd-outputs/__tests__/WelcomeOutput.test.tsx @@ -3,7 +3,7 @@ import type { ReactElement } from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { Translator } from '@/i18n/intl'; import { Command, type CommandEntry } from '@/types/terminal'; -import IntroOutput from '../IntroOutput'; +import WelcomeOutput from '../WelcomeOutput'; // Mock the CmdLink component vi.mock('../terminal/CmdLink', () => ({ @@ -12,7 +12,7 @@ vi.mock('../terminal/CmdLink', () => ({ ), })); -describe('IntroOutput', () => { +describe('WelcomeOutput', () => { const mockT = vi.fn((key: string) => key) as unknown as Translator; const mockTRich = vi.fn((key: string, values?: Record) => { if (values && typeof values.name === 'function') { @@ -27,7 +27,7 @@ describe('IntroOutput', () => { const mockTWithRich = Object.assign(mockT, { rich: mockTRich }); const mockEntry: CommandEntry = { timestamp: Date.now(), - cmdName: Command.Intro, + cmdName: Command.Welcome, }; beforeEach(() => { @@ -35,25 +35,25 @@ describe('IntroOutput', () => { }); it('renders welcome message', () => { - render(); + render(); expect(mockTRich).toHaveBeenCalledWith( - 'cmds.intro.welcome', + 'cmds.welcome.welcome', expect.any(Object), ); }); - it('renders site introduction text', () => { - render(); - expect(mockT).toHaveBeenCalledWith('cmds.intro.site_intro_1'); + it('renders site welcome text', () => { + render(); + expect(mockT).toHaveBeenCalledWith('cmds.welcome.site_intro_1'); expect(mockTRich).toHaveBeenCalledWith( - 'cmds.intro.site_intro_2', + 'cmds.welcome.site_intro_2', expect.any(Object), ); }); it('renders ascii art', () => { const { container } = render( - , + , ); const asciiDiv = container.querySelector('.whitespace-break-spaces'); diff --git a/src/components/terminal/TerminalEmulator.tsx b/src/components/terminal/TerminalEmulator.tsx index d8d0d8c..6150111 100644 --- a/src/components/terminal/TerminalEmulator.tsx +++ b/src/components/terminal/TerminalEmulator.tsx @@ -10,12 +10,12 @@ import TerminalPrompt from './TerminalPrompt'; export default function TerminalEmulator() { const { - hasIntroduced, + hasWelcomed, history, input, simulatedCmd, submission, - setHasIntroduced, + setHasWelcomed, setHasRefreshed, setHistory, setInput, @@ -38,11 +38,11 @@ export default function TerminalEmulator() { useEffect(() => { mainPrompt.current?.focus(); - if (!isPrerender && !hasIntroduced && mainPrompt.current) { - setHasIntroduced(true); - mainPrompt.current?.simulate(Command.Intro); + if (!isPrerender && !hasWelcomed && mainPrompt.current) { + setHasWelcomed(true); + mainPrompt.current?.simulate(Command.Welcome); } - }, [hasIntroduced, isPrerender, mainPrompt, setHasIntroduced]); + }, [hasWelcomed, isPrerender, mainPrompt, setHasWelcomed]); useEffect(() => { if (!submission) return; diff --git a/src/components/terminal/__tests__/CmdLink.test.tsx b/src/components/terminal/__tests__/CmdLink.test.tsx index de4ebc2..53ba305 100644 --- a/src/components/terminal/__tests__/CmdLink.test.tsx +++ b/src/components/terminal/__tests__/CmdLink.test.tsx @@ -158,12 +158,12 @@ describe('CmdLink', () => { it('prioritizes cmdName over cmdInfo.name', () => { const cmdInfo: CommandInfo = { - name: Command.Intro, + name: Command.Welcome, }; render(); expect(screen.getByText('help')).toBeInTheDocument(); - expect(screen.queryByText('intro')).not.toBeInTheDocument(); + expect(screen.queryByText('welcome')).not.toBeInTheDocument(); }); }); diff --git a/src/components/terminal/__tests__/TerminalEmulator.test.tsx b/src/components/terminal/__tests__/TerminalEmulator.test.tsx index fa23385..9c0b983 100644 --- a/src/components/terminal/__tests__/TerminalEmulator.test.tsx +++ b/src/components/terminal/__tests__/TerminalEmulator.test.tsx @@ -32,9 +32,9 @@ vi.mock('@/utils/terminal-utils', () => ({ output: () =>
Help Output
, timestamp: Date.now(), }, - intro: { - cmdName: Command.Intro, - output: () =>
Intro Output
, + welcome: { + cmdName: Command.Welcome, + output: () =>
Welcome Output
, timestamp: Date.now(), }, contact: { @@ -95,8 +95,8 @@ vi.mock('../../cmd-outputs/HelpOutput', () => ({ default: () =>
Help Output
, })); -vi.mock('../../cmd-outputs/IntroOutput', () => ({ - default: () =>
Intro Output
, +vi.mock('../../cmd-outputs/WelcomeOutput', () => ({ + default: () =>
Welcome Output
, })); vi.mock('../../cmd-outputs/ContactOutput', () => ({ @@ -113,7 +113,7 @@ vi.mock('../../cmd-outputs/Web2workOutput', () => ({ describe('TerminalEmulator', () => { const mockSetIsTerminal = vi.fn(); - const mockSetHasIntroduced = vi.fn(); + const mockSetHasWelcomed = vi.fn(); const mockSetHasRefreshed = vi.fn(); const mockSetHistory = vi.fn(); const mockSetInput = vi.fn(); @@ -135,12 +135,12 @@ describe('TerminalEmulator', () => { (useTerminalContext as unknown as ReturnType).mockReturnValue( { - hasIntroduced: true, + hasWelcomed: true, history: [], input: '', simulatedCmd: '', submission: '', - setHasIntroduced: mockSetHasIntroduced, + setHasWelcomed: mockSetHasWelcomed, setHasRefreshed: mockSetHasRefreshed, setHistory: mockSetHistory, setInput: mockSetInput, @@ -171,15 +171,15 @@ describe('TerminalEmulator', () => { expect(mockSetIsTerminal).toHaveBeenCalledWith(true); }); - it('sets introduction when not prerendering and not introduced', async () => { + it('sets welcome when not prerendering and not welcomed', async () => { (useTerminalContext as unknown as ReturnType).mockReturnValue( { - hasIntroduced: false, + hasWelcomed: false, history: [], input: '', simulatedCmd: '', submission: '', - setHasIntroduced: mockSetHasIntroduced, + setHasWelcomed: mockSetHasWelcomed, setHasRefreshed: mockSetHasRefreshed, setHistory: mockSetHistory, setInput: mockSetInput, @@ -195,23 +195,23 @@ describe('TerminalEmulator', () => { render(); await waitFor(() => { - expect(mockSetHasIntroduced).toHaveBeenCalledWith(true); + expect(mockSetHasWelcomed).toHaveBeenCalledWith(true); }); await waitFor(() => { - expect(mockPromptMethods.simulate).toHaveBeenCalledWith(Command.Intro); + expect(mockPromptMethods.simulate).toHaveBeenCalledWith(Command.Welcome); }); }); it('handles clear submission command', () => { (useTerminalContext as unknown as ReturnType).mockReturnValue( { - hasIntroduced: true, + hasWelcomed: true, history: [{ cmdName: 'test', timestamp: 123 }], input: '', simulatedCmd: '', submission: 'clear', - setHasIntroduced: mockSetHasIntroduced, + setHasWelcomed: mockSetHasWelcomed, setHasRefreshed: mockSetHasRefreshed, setHistory: mockSetHistory, setInput: mockSetInput, @@ -235,8 +235,8 @@ describe('TerminalEmulator', () => { timestamp: 123, }, { - cmdName: Command.Intro, - output: () =>
Intro Output
, + cmdName: Command.Welcome, + output: () =>
Welcome Output
, timestamp: 456, }, { @@ -247,12 +247,12 @@ describe('TerminalEmulator', () => { (useTerminalContext as unknown as ReturnType).mockReturnValue( { - hasIntroduced: true, + hasWelcomed: true, history: mockHistory, input: '', simulatedCmd: '', submission: '', - setHasIntroduced: mockSetHasIntroduced, + setHasWelcomed: mockSetHasWelcomed, setHasRefreshed: mockSetHasRefreshed, setHistory: mockSetHistory, setInput: mockSetInput, @@ -264,7 +264,7 @@ describe('TerminalEmulator', () => { render(); expect(screen.getByTestId('help-output')).toBeInTheDocument(); - expect(screen.getByTestId('intro-output')).toBeInTheDocument(); + expect(screen.getByTestId('welcome-output')).toBeInTheDocument(); expect(screen.getByTestId('unknown-cmd-output')).toBeInTheDocument(); expect(screen.getByText('unknown-command')).toBeInTheDocument(); }); diff --git a/src/constants/__tests__/commands.test.ts b/src/constants/__tests__/commands.test.ts index e752587..75b3cd3 100644 --- a/src/constants/__tests__/commands.test.ts +++ b/src/constants/__tests__/commands.test.ts @@ -10,7 +10,7 @@ describe('commands', () => { expect(commandNames).toContain(Command.Clear); expect(commandNames).toContain(Command.Contact); expect(commandNames).toContain(Command.Help); - expect(commandNames).toContain(Command.Intro); + expect(commandNames).toContain(Command.Welcome); expect(commandNames).toContain(Command.SetLang); expect(commandNames).toContain(Command.Whoami); expect(commandNames).toContain(Command.Web2work); diff --git a/src/constants/commands.ts b/src/constants/commands.ts index f2e27a2..0e7f47f 100644 --- a/src/constants/commands.ts +++ b/src/constants/commands.ts @@ -10,7 +10,9 @@ const ContactOutput = lazy( () => import('@/components/cmd-outputs/ContactOutput'), ); const HelpOutput = lazy(() => import('@/components/cmd-outputs/HelpOutput')); -const IntroOutput = lazy(() => import('@/components/cmd-outputs/IntroOutput')); +const WelcomeOutput = lazy( + () => import('@/components/cmd-outputs/WelcomeOutput'), +); const Web2workOutput = lazy( () => import('@/components/cmd-outputs/Web2workOutput'), ); @@ -32,8 +34,8 @@ function enumToArg(o: { [s: string]: string } | ArrayLike): string[] { export const Commands: CommandInfo[] = [ { - name: Command.Intro, - output: IntroOutput, + name: Command.Welcome, + output: WelcomeOutput, }, { name: Command.Whoami, diff --git a/src/contexts/TerminalContext.tsx b/src/contexts/TerminalContext.tsx index 8c00cfd..91ff252 100644 --- a/src/contexts/TerminalContext.tsx +++ b/src/contexts/TerminalContext.tsx @@ -7,7 +7,7 @@ interface TerminalState { submission: string; simulatedCmd: string; history: CommandEntry[]; - hasIntroduced: boolean; + hasWelcomed: boolean; hasRefreshed: boolean; lastRouteReq: AppRoute | null; oldRouteReq: AppRoute | null; @@ -15,7 +15,7 @@ interface TerminalState { setSubmission: (value: string) => void; setSimulatedCmd: (value: string) => void; setHistory: (value: CommandEntry[]) => void; - setHasIntroduced: (value: boolean) => void; + setHasWelcomed: (value: boolean) => void; setHasRefreshed: (value: boolean) => void; setLastRouteReq: (value: AppRoute | null) => void; setOldRouteReq: (value: AppRoute | null) => void; @@ -26,7 +26,7 @@ const initialState: TerminalState = { submission: '', simulatedCmd: '', history: [], - hasIntroduced: false, + hasWelcomed: false, hasRefreshed: false, lastRouteReq: null, oldRouteReq: null, @@ -34,7 +34,7 @@ const initialState: TerminalState = { setSubmission: () => {}, setSimulatedCmd: () => {}, setHistory: () => {}, - setHasIntroduced: () => {}, + setHasWelcomed: () => {}, setHasRefreshed: () => {}, setLastRouteReq: () => {}, setOldRouteReq: () => {}, @@ -70,8 +70,8 @@ export const TerminalStateProvider = ({ [], ); - const setHasIntroduced = useCallback( - (value: boolean) => setState((prev) => ({ ...prev, hasIntroduced: value })), + const setHasWelcomed = useCallback( + (value: boolean) => setState((prev) => ({ ...prev, hasWelcomed: value })), [], ); @@ -98,7 +98,7 @@ export const TerminalStateProvider = ({ setSubmission, setSimulatedCmd, setHistory, - setHasIntroduced, + setHasWelcomed, setHasRefreshed, setLastRouteReq, setOldRouteReq, diff --git a/src/contexts/__tests__/TerminalContext.test.tsx b/src/contexts/__tests__/TerminalContext.test.tsx index 1540e27..976abc0 100644 --- a/src/contexts/__tests__/TerminalContext.test.tsx +++ b/src/contexts/__tests__/TerminalContext.test.tsx @@ -17,7 +17,7 @@ describe('TerminalContext', () => { expect(result.current.submission).toBe(''); expect(result.current.simulatedCmd).toBe(''); expect(result.current.history).toEqual([]); - expect(result.current.hasIntroduced).toBe(false); + expect(result.current.hasWelcomed).toBe(false); expect(result.current.hasRefreshed).toBe(false); expect(result.current.lastRouteReq).toBeNull(); expect(result.current.oldRouteReq).toBeNull(); @@ -27,7 +27,7 @@ describe('TerminalContext', () => { expect(typeof result.current.setSubmission).toBe('function'); expect(typeof result.current.setSimulatedCmd).toBe('function'); expect(typeof result.current.setHistory).toBe('function'); - expect(typeof result.current.setHasIntroduced).toBe('function'); + expect(typeof result.current.setHasWelcomed).toBe('function'); expect(typeof result.current.setHasRefreshed).toBe('function'); expect(typeof result.current.setLastRouteReq).toBe('function'); expect(typeof result.current.setOldRouteReq).toBe('function'); @@ -76,16 +76,16 @@ describe('TerminalContext', () => { expect(result.current.history).toEqual(mockHistory); }); - it('should update hasIntroduced when setHasIntroduced is called', () => { + it('should update hasWelcomed when setHasWelcomed is called', () => { const { result } = renderHook(() => useTerminalContext(), { wrapper: TerminalStateProvider, }); act(() => { - result.current.setHasIntroduced(true); + result.current.setHasWelcomed(true); }); - expect(result.current.hasIntroduced).toBe(true); + expect(result.current.hasWelcomed).toBe(true); }); it('should update lastRouteReq when setLastRouteReq is called', () => { @@ -122,13 +122,13 @@ describe('TerminalContext', () => { act(() => { result.current.setInput('complex input'); - result.current.setHasIntroduced(true); + result.current.setHasWelcomed(true); result.current.setHistory(mockHistory); result.current.setLastRouteReq(mockRoute); }); expect(result.current.input).toBe('complex input'); - expect(result.current.hasIntroduced).toBe(true); + expect(result.current.hasWelcomed).toBe(true); expect(result.current.history).toEqual(mockHistory); expect(result.current.lastRouteReq).toEqual(mockRoute); }); diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index d0e83ed..dc4a8d9 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -45,8 +45,8 @@ "help": { "description": "Print list of commands" }, - "intro": { - "description": "Print intro message", + "welcome": { + "description": "Print welcome message", "welcome": "Hi there, I'm Xav. Welcome to my own little corner of the web!", "site_intro_1": "This site can be browsed by clicking on the tabs above.", "site_intro_2": "Or, for a CLI experience, get started with " diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 2c1f964..d4d6180 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -45,7 +45,7 @@ "help": { "description": "Afficher la liste des commandes" }, - "intro": { + "welcome": { "description": "Afficher le message d'introduction", "welcome": "Salut, je suis Xav. Bienvenue dans mon \"petit coin\" du web !", "site_intro_1": "Ce site peut être parcouru en cliquant sur les onglets ci-dessus.", diff --git a/src/types/__tests__/terminal.test.ts b/src/types/__tests__/terminal.test.ts index deaad19..bef2ffd 100644 --- a/src/types/__tests__/terminal.test.ts +++ b/src/types/__tests__/terminal.test.ts @@ -13,7 +13,7 @@ describe('terminal types', () => { expect(Command.Clear).toBe('clear'); expect(Command.Contact).toBe('contact'); expect(Command.Help).toBe('help'); - expect(Command.Intro).toBe('intro'); + expect(Command.Welcome).toBe('welcome'); expect(Command.Web2work).toBe('web2work'); expect(Command.Web3work).toBe('web3work'); expect(Command.Contribs).toBe('contribs'); diff --git a/src/types/terminal.ts b/src/types/terminal.ts index b58a945..66b0fb8 100644 --- a/src/types/terminal.ts +++ b/src/types/terminal.ts @@ -6,7 +6,7 @@ export enum Command { Clear = 'clear', Contact = 'contact', Help = 'help', - Intro = 'intro', + Welcome = 'welcome', Web2work = 'web2work', Web3work = 'web3work', Contribs = 'contribs', From 3753443c69cf53711841814005837366765f57cd Mon Sep 17 00:00:00 2001 From: Xavier Saliniere Date: Tue, 2 Sep 2025 23:28:38 -0400 Subject: [PATCH 5/6] feat: replace tabs with sidenav and navigation modal --- README.md | 1 + package.json | 2 +- pnpm-lock.yaml | 38 +-- src/app/[locale]/layout.tsx | 11 +- src/app/layout.tsx | 6 +- .../__tests__/LoadSequence.test.tsx | 2 +- src/components/cmd-outputs/HelpOutput.tsx | 2 +- .../cmd-outputs/__tests__/HelpOutput.test.tsx | 2 +- src/components/layout/Header.tsx | 14 +- src/components/layout/MainWrapper.tsx | 26 ++ src/components/layout/Sidenav.tsx | 58 +++++ src/components/layout/TermWindow.tsx | 40 ---- .../layout/__tests__/Header.test.tsx | 67 +++++- .../layout/__tests__/Sidenav.test.tsx | 223 ++++++++++++++++++ .../layout/__tests__/TermWindow.test.tsx | 91 ------- src/components/terminal/CmdLink.tsx | 2 +- src/components/terminal/TerminalEmulator.tsx | 2 +- src/components/terminal/TerminalPrompt.tsx | 2 +- .../terminal/__tests__/CmdLink.test.tsx | 2 +- .../__tests__/TerminalPrompt.test.tsx | 3 +- src/constants/commands.ts | 22 +- src/contexts/AppContext.tsx | 10 + src/contexts/__tests__/AppContext.test.tsx | 43 ++++ src/i18n/messages/en.json | 1 + src/i18n/messages/fr.json | 1 + src/styles/components.module.css | 9 + 26 files changed, 484 insertions(+), 196 deletions(-) create mode 100644 src/components/layout/MainWrapper.tsx create mode 100644 src/components/layout/Sidenav.tsx delete mode 100644 src/components/layout/TermWindow.tsx create mode 100644 src/components/layout/__tests__/Sidenav.test.tsx delete mode 100644 src/components/layout/__tests__/TermWindow.test.tsx diff --git a/README.md b/README.md index 7a4b622..18940ad 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Still a work in progress, but it already lives on the InterPlanetary FileSystem: - [x] responsive design - [x] internationalization - [x] automatic deployment on IPFS +- [ ] UI still needs to be improved - [ ] content is still being written - [ ] pages are still being added & refined diff --git a/package.json b/package.json index 81eb355..eb6acf6 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "postinstall": "lefthook install" }, "dependencies": { - "@acid-info/lsd-react": "0.2.0-beta.4", + "@nipsysdev/lsd-react": "0.2.0-beta.4-dev1", "next": "^15.5.2", "next-intl": "^4.3.5", "postcss": "^8.5.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e9aab01..13a2b58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,9 @@ importers: .: dependencies: - '@acid-info/lsd-react': - specifier: 0.2.0-beta.4 - version: 0.2.0-beta.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@nipsysdev/lsd-react': + specifier: 0.2.0-beta.4-dev1 + version: 0.2.0-beta.4-dev1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) next: specifier: ^15.5.2 version: 15.5.2(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -81,12 +81,6 @@ importers: packages: - '@acid-info/lsd-react@0.2.0-beta.4': - resolution: {integrity: sha512-SkpJmJhrDuGmAgwL04o8mjGPJR0jAo04g/wNAddwhoZ4RhQqvEIBPlK98slyn8XmiWJhddT9rf54YCGIuHc00A==} - peerDependencies: - react: 17.x || 18.x || 19.x - react-dom: 17.x || 18.x || 19.x - '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} @@ -663,6 +657,12 @@ packages: cpu: [x64] os: [win32] + '@nipsysdev/lsd-react@0.2.0-beta.4-dev1': + resolution: {integrity: sha512-bRnxuyKvpS8f9uXRL7W6oukzGdqk907b2FNFw+c3usyGUssaq4lfoKG0Km3+RBhCeeNhefJiHMc4f1MhONA5TA==} + peerDependencies: + react: 17.x || 18.x || 19.x + react-dom: 17.x || 18.x || 19.x + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -2066,16 +2066,6 @@ packages: snapshots: - '@acid-info/lsd-react@0.2.0-beta.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@datepicker-react/hooks': 2.8.4(react@19.1.1) - clsx: 1.2.1 - lodash: 4.17.21 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-hot-toast: 2.6.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react-use: 17.6.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@adobe/css-tools@4.4.4': {} '@alloc/quick-lru@5.2.0': {} @@ -2537,6 +2527,16 @@ snapshots: '@next/swc-win32-x64-msvc@15.5.2': optional: true + '@nipsysdev/lsd-react@0.2.0-beta.4-dev1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@datepicker-react/hooks': 2.8.4(react@19.1.1) + clsx: 1.2.1 + lodash: 4.17.21 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-hot-toast: 2.6.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react-use: 17.6.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@pkgjs/parseargs@0.11.0': optional: true diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index f745929..f728de5 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -1,8 +1,7 @@ import { notFound } from 'next/navigation'; import { hasLocale, NextIntlClientProvider } from 'next-intl'; import { setRequestLocale } from 'next-intl/server'; -import Header from '@/components/layout/Header'; -import TerminalWindow from '@/components/layout/TermWindow'; +import MainWrapper from '@/components/layout/MainWrapper'; import { routing } from '@/i18n/intl'; import type { RouteData } from '@/types/routing'; import { setPageMeta } from '@/utils/metadata-utils'; @@ -29,13 +28,7 @@ export default async function Layout({ return ( -
-
- -
- {children} -
-
+ {children}
); } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2af1061..e9aa6b4 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,11 +1,9 @@ -import { LsdThemeStyles } from '@acid-info/lsd-react/theme'; import { getTranslations } from 'next-intl/server'; import { AppStateProvider } from '@/contexts/AppContext'; import 'tailwindcss/index.css'; -import '@acid-info/lsd-react/css'; - -// Initialize chunk retry manager for IPFS deployment +import '@nipsysdev/lsd-react/css'; import '@/utils/chunk-retry'; +import { LsdThemeStyles } from '@nipsysdev/lsd-react/theme'; export async function generateMetadata() { const tMeta = await getTranslations({ locale: 'en', namespace: 'Metadata' }); diff --git a/src/components/__tests__/LoadSequence.test.tsx b/src/components/__tests__/LoadSequence.test.tsx index e51387a..57aa4ba 100644 --- a/src/components/__tests__/LoadSequence.test.tsx +++ b/src/components/__tests__/LoadSequence.test.tsx @@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import LoadSequence from '../LoadSequence'; // Mock Typography component -vi.mock('@acid-info/lsd-react/client/Typography', () => ({ +vi.mock('@nipsysdev/lsd-react/client/Typography', () => ({ Typography: ({ children, ...props }: { children: React.ReactNode }) => (
{children}
), diff --git a/src/components/cmd-outputs/HelpOutput.tsx b/src/components/cmd-outputs/HelpOutput.tsx index a22b5e2..7b0bddb 100644 --- a/src/components/cmd-outputs/HelpOutput.tsx +++ b/src/components/cmd-outputs/HelpOutput.tsx @@ -1,4 +1,4 @@ -import { Typography } from '@acid-info/lsd-react/client/Typography'; +import { Typography } from '@nipsysdev/lsd-react/client/Typography'; import { Component } from 'react'; import { Commands } from '@/constants/commands'; import type { CommandOutputProps } from '@/types/terminal'; diff --git a/src/components/cmd-outputs/__tests__/HelpOutput.test.tsx b/src/components/cmd-outputs/__tests__/HelpOutput.test.tsx index 78a80ec..1371f9a 100644 --- a/src/components/cmd-outputs/__tests__/HelpOutput.test.tsx +++ b/src/components/cmd-outputs/__tests__/HelpOutput.test.tsx @@ -49,7 +49,7 @@ vi.mock('@/contexts/TerminalContext', () => ({ })); // Mock LSD React Typography component -vi.mock('@acid-info/lsd-react/client/Typography', () => ({ +vi.mock('@nipsysdev/lsd-react/client/Typography', () => ({ Typography: ({ children, variant, diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index c8ba61d..51ec921 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -1,19 +1,25 @@ 'use client'; -import { Button } from '@acid-info/lsd-react/client/Button'; -import { ButtonGroup } from '@acid-info/lsd-react/client/ButtonGroup'; -import { useLocale } from 'next-intl'; +import { Button } from '@nipsysdev/lsd-react/client/Button'; +import { ButtonGroup } from '@nipsysdev/lsd-react/client/ButtonGroup'; +import { useLocale, useTranslations } from 'next-intl'; import { PiGithubLogoFill } from 'react-icons/pi'; import { LangLabels } from '@/constants/lang'; +import { useAppContext } from '@/contexts/AppContext'; import { Link, usePathname } from '@/i18n/intl'; import styles from '@/styles/components.module.css'; import { cx } from '@/utils/helpers'; export default function Header() { + const t = useTranslations('Core'); + const { setIsMenuDisplayed } = useAppContext(); const pathname = usePathname(); const locale = useLocale(); return ( -
+
+
+ +
{Object.entries(LangLabels).map(([lang, label]) => ( + ))} + + ); + + return ( +
+
+ {btnList} +
+ + setIsMenuDisplayed(false)} + className={styles.modal} + > + +
{btnList}
+
+
+
+ ); +} diff --git a/src/components/layout/TermWindow.tsx b/src/components/layout/TermWindow.tsx deleted file mode 100644 index 1cd1d26..0000000 --- a/src/components/layout/TermWindow.tsx +++ /dev/null @@ -1,40 +0,0 @@ -'use client'; -import { TabItem } from '@acid-info/lsd-react/client/TabItem'; -import { Tabs } from '@acid-info/lsd-react/client/Tabs'; -import { useTranslations } from 'next-intl'; -import { Routes } from '@/constants/routes'; -import { Link, usePathname } from '@/i18n/intl'; -import styles from '@/styles/components.module.css'; - -export default function TerminalWindow({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - const t = useTranslations('Pages'); - - const pathname = usePathname(); - const notfound = '404'; - const activeRouteName = - Object.entries(Routes) - .filter(([, routePath]) => routePath === pathname) - .map(([routeName]) => routeName) - .find(Boolean) ?? notfound; - - return ( -
- - {Object.entries(Routes).map(([routeName, routePath]) => ( - - {t(routeName)} - - ))} - - {children} -
- ); -} diff --git a/src/components/layout/__tests__/Header.test.tsx b/src/components/layout/__tests__/Header.test.tsx index 1161d14..f7c55eb 100644 --- a/src/components/layout/__tests__/Header.test.tsx +++ b/src/components/layout/__tests__/Header.test.tsx @@ -1,11 +1,18 @@ -import { render, screen } from '@testing-library/react'; +import { render, screen, within } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; -import { LangLabels } from '@/constants/lang'; -import Header from '../Header'; + +// Mock AppContext +const mockSetIsMenuDisplayed = vi.fn(); +vi.mock('@/contexts/AppContext', () => ({ + useAppContext: () => ({ + setIsMenuDisplayed: mockSetIsMenuDisplayed, + }), +})); // Mock next-intl vi.mock('next-intl', () => ({ useLocale: () => 'en', + useTranslations: () => (key: string) => key, })); // Mock the i18n navigation @@ -27,21 +34,26 @@ vi.mock('@/i18n/intl', () => ({ })); // Mock LSD React components -vi.mock('@acid-info/lsd-react/client/Button', () => ({ +vi.mock('@nipsysdev/lsd-react/client/Button', () => ({ Button: ({ children, + onClick, variant, size, className, }: { children: React.ReactNode; - variant: string; - size: string; + onClick?: () => void; + variant?: string; + size?: string; className?: string; }) => ( + ), +})); + +vi.mock('@nipsysdev/lsd-react/client/Modal', () => ({ + Modal: ({ + isOpen, + onClose, + children, + className, + }: { + isOpen: boolean; + onClose: () => void; + children: React.ReactNode; + className?: string; + }) => + isOpen ? ( +
+ + {children} +
+ ) : null, +})); + +vi.mock('@nipsysdev/lsd-react/client/ModalBody', () => ({ + ModalBody: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), +})); + +describe('Sidenav', () => { + beforeEach(() => { + // Reset mocks and state before each test + vi.clearAllMocks(); + mockIsMenuDisplayed = false; + mockSetIsMenuDisplayed = vi.fn(); + mockUsePathname = () => '/'; + + mockLink = vi.fn( + ({ + children, + href, + onClick, + }: { + children: React.ReactNode; + href: string; + onClick?: () => void; + }) => ( + + {children} + + ), + ); + }); + + it('renders desktop sidebar with navigation links', () => { + render(); + + // The sidebar itself is a div, not a nav role, so we check for a child link + const firstLink = screen.getByRole('link', { + name: Object.keys(Routes)[0], + }); + const sidebar = firstLink.closest('div.flex-col'); + expect(sidebar).toBeInTheDocument(); + expect(sidebar).toHaveClass('hidden'); // It's hidden on small screens by default + + Object.entries(Routes).forEach(([routeName, routePath]) => { + const link = screen.getByRole('link', { name: routeName }); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute('href', routePath); + }); + }); + + it('does not render modal when isMenuDisplayed is false', () => { + render(); + expect(screen.queryByTestId('modal')).not.toBeInTheDocument(); + }); + + it('renders modal when isMenuDisplayed is true', () => { + mockIsMenuDisplayed = true; + render(); + + const modal = screen.getByTestId('modal'); + expect(modal).toBeInTheDocument(); + expect(screen.getByTestId('modal-body')).toBeInTheDocument(); + + // Scope the link checks to within the modal + Object.entries(Routes).forEach(([routeName, routePath]) => { + const link = within(modal).getByRole('link', { name: routeName }); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute('href', routePath); + }); + }); + + it('calls setIsMenuDisplayed(false) when modal close button is clicked', () => { + mockIsMenuDisplayed = true; + render(); + + const closeButton = screen.getByTestId('modal-close'); + fireEvent.click(closeButton); + + expect(mockSetIsMenuDisplayed).toHaveBeenCalledTimes(1); + expect(mockSetIsMenuDisplayed).toHaveBeenCalledWith(false); + }); + + it('calls setIsMenuDisplayed(false) when a link inside the modal is clicked', () => { + mockIsMenuDisplayed = true; + render(); + + const modal = screen.getByTestId('modal'); + const firstRouteName = Object.keys(Routes)[0]; + // Scope the link query to within the modal + const firstLink = within(modal).getByRole('link', { name: firstRouteName }); + fireEvent.click(firstLink); + + expect(mockSetIsMenuDisplayed).toHaveBeenCalledTimes(1); + expect(mockSetIsMenuDisplayed).toHaveBeenCalledWith(false); + }); + + it('highlights the active route in the desktop sidebar', () => { + const activeRoute = 'whoami'; // Example active route + const activePath = Routes[activeRoute as keyof typeof Routes]; + mockUsePathname = () => activePath; + + render(); + + const activeButton = screen.getByTestId('button-filled'); + expect(activeButton).toBeInTheDocument(); + // Check that the link inside the active button has the correct name + expect(activeButton).toHaveTextContent(activeRoute); + + // Check that other buttons are 'outlined' + Object.keys(Routes).forEach((routeName) => { + if (routeName !== activeRoute) { + // We need to be more specific to get the correct button + const link = screen.getByRole('link', { name: routeName }); + const button = link.closest('button'); + expect(button).toHaveAttribute('data-testid', 'button-outlined'); + } + }); + }); + + it('highlights the active route in the modal', () => { + const activeRoute = 'web3work'; // Example active route + const activePath = Routes[activeRoute as keyof typeof Routes]; + mockUsePathname = () => activePath; + mockIsMenuDisplayed = true; + + render(); + + const modal = screen.getByTestId('modal'); + // Scope the button query to within the modal + const activeButton = within(modal).getByTestId('button-filled'); + expect(activeButton).toBeInTheDocument(); + expect(activeButton).toHaveTextContent(activeRoute); + }); +}); diff --git a/src/components/layout/__tests__/TermWindow.test.tsx b/src/components/layout/__tests__/TermWindow.test.tsx deleted file mode 100644 index 864fda8..0000000 --- a/src/components/layout/__tests__/TermWindow.test.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import { describe, expect, it, vi } from 'vitest'; -import { Routes } from '@/constants/routes'; -import TerminalWindow from '../TermWindow'; - -// Mock next-intl -vi.mock('next-intl', () => ({ - useTranslations: () => (key: string) => key, -})); - -// Mock the i18n navigation -vi.mock('@/i18n/intl', () => ({ - Link: ({ children, href }: { children: React.ReactNode; href: string }) => ( - {children} - ), - usePathname: () => '/web2work', -})); - -// Mock LSD React components -vi.mock('@acid-info/lsd-react/client/Tabs', () => ({ - Tabs: ({ - children, - className, - activeTab, - fullWidth, - }: { - children: React.ReactNode; - className?: string; - activeTab: string; - fullWidth?: boolean; - }) => ( -
- {children} -
- ), -})); - -vi.mock('@acid-info/lsd-react/client/TabItem', () => ({ - TabItem: ({ - children, - name, - className, - }: { - children: React.ReactNode; - name: string; - className?: string; - }) => ( -
- {children} -
- ), -})); - -describe('TerminalWindow', () => { - const mockChildren =
Test content
; - - it('sets correct active tab based on pathname', () => { - render({mockChildren}); - - const tabs = screen.getByTestId('tabs'); - expect(tabs).toHaveAttribute('data-active-tab', 'web2work'); - }); - - it('renders tab items for all routes', () => { - render({mockChildren}); - - Object.entries(Routes).forEach(([routeName, routePath]) => { - const tabItem = screen.getByTestId(`tab-item-${routeName}`); - expect(tabItem).toBeInTheDocument(); - expect(tabItem).toHaveAttribute('data-name', routeName); - - // Check the link inside the tab - const link = screen.getByRole('link', { name: routeName }); - expect(link).toHaveAttribute('href', routePath); - }); - }); - - it('renders children content', () => { - render({mockChildren}); - expect(screen.getByTestId('mock-children')).toBeInTheDocument(); - }); -}); diff --git a/src/components/terminal/CmdLink.tsx b/src/components/terminal/CmdLink.tsx index f8bb10e..99a53de 100644 --- a/src/components/terminal/CmdLink.tsx +++ b/src/components/terminal/CmdLink.tsx @@ -1,4 +1,4 @@ -import { Button } from '@acid-info/lsd-react/client/Button'; +import { Button } from '@nipsysdev/lsd-react/client/Button'; import type { JSX } from 'react'; import { useTerminalContext } from '@/contexts/TerminalContext'; import type { CommandArgument, CommandInfo } from '@/types/terminal'; diff --git a/src/components/terminal/TerminalEmulator.tsx b/src/components/terminal/TerminalEmulator.tsx index 6150111..23c8d8a 100644 --- a/src/components/terminal/TerminalEmulator.tsx +++ b/src/components/terminal/TerminalEmulator.tsx @@ -79,7 +79,7 @@ export default function TerminalEmulator() { return ( hasWindow && ( -
+
{/** biome-ignore lint/a11y/useSemanticElements: terminal container needs to be clickable and listen to inputs while still displaying as a div */}
({ })); // Mock the LSD Button component -vi.mock('@acid-info/lsd-react/client/Button', () => ({ +vi.mock('@nipsysdev/lsd-react/client/Button', () => ({ Button: ({ children, onClick, ...props }: React.ComponentProps<'button'>) => ( + +