diff --git a/.gitignore b/.gitignore index d2d1714d..19df7797 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,7 @@ public/build # Local Netlify folder .netlify +deno.lock # Next.js .next diff --git a/.storybook/main.ts b/.storybook/main.ts index 193b9585..0c234450 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -13,6 +13,7 @@ export default { '../libs/shared/ui/**/*.@(mdx|stories.@(js|jsx|ts|tsx))', '../libs/website/feature/**/*.@(mdx|stories.@(js|jsx|ts|tsx))', '../libs/website/shared/ui/**/*.@(mdx|stories.@(js|jsx|ts|tsx))', + '../libs/website/ui/**/*.@(mdx|stories.@(js|jsx|ts|tsx))', ], staticDirs: [ // './public', diff --git a/apps/docs/content/docs/concepts/bullet-proof-react.mdx b/apps/docs/content/docs/concepts/bullet-proof-react.mdx new file mode 100644 index 00000000..32e411a8 --- /dev/null +++ b/apps/docs/content/docs/concepts/bullet-proof-react.mdx @@ -0,0 +1,46 @@ +--- +title: Bullet-Proof React +description: WIP +icon: ShieldCheck +--- + +import { File, Folder, Files } from 'fumadocs-ui/components/files'; +import { TbBrandTypescript } from "react-icons/tb"; + +## [Bullet-Proof React](https://reacthandbook.dev/project-standards) + + +Bullet-Proof React is a methodology for building scalable and maintainable front-end application, however, we extend it for our entire codebase. +We use Bullet-Proof React and [Feature-Sliced Design](/concepts/feature-sliced-design) to create a scalable and maintainable codebase. + +In our use case, the Bullet-Proof React is how we organize our [Feature Slices](https://feature-sliced.design/docs/reference/slices-segments) + +### Example structure + + + + + + + + + + + + + + + + + + + + }/> + + + diff --git a/apps/docs/content/docs/concepts/feature-sliced-design.mdx b/apps/docs/content/docs/concepts/feature-sliced-design.mdx new file mode 100644 index 00000000..1aca6270 --- /dev/null +++ b/apps/docs/content/docs/concepts/feature-sliced-design.mdx @@ -0,0 +1,107 @@ +--- +title: Feature-Sliced Design +description: WIP +icon: Eclipse +--- + +import { File, Folder, Files } from 'fumadocs-ui/components/files'; +import {Atom as TSX} from 'lucide-react' +import { DiJavascript1 } from "react-icons/di"; +import { TbBrandTypescript } from "react-icons/tb"; +import { BsFiletypeJson } from "react-icons/bs"; +import { DiCss3 } from "react-icons/di"; + +We follow [Feature-Sliced Design](https://feature-sliced.design/docs) for our project structure. + + + +Feature-Sliced Design is a methodology for building scalable and maintainable front-end applications, but we extend it for our entire codebase. +It is based on the idea of splitting the application into features, each of which is a separate entity with its own logic, UI, and data. +All content shared within an app is placed into the shared folder. +This approach allows for better organization of the codebase, easier collaboration between team members, and faster development cycles. + +It is highly recommended that you follow the [Feature-Sliced Design Tutorial](https://feature-sliced.design/docs/get-started/tutorial) to practice using the methodology. + +### Example structure + + + + + + + + + + + + }/> + }/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + }/> + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/docs/content/docs/concepts/meta.json b/apps/docs/content/docs/concepts/meta.json index 1c535d5d..45c2d52b 100644 --- a/apps/docs/content/docs/concepts/meta.json +++ b/apps/docs/content/docs/concepts/meta.json @@ -11,6 +11,8 @@ "design-systems", "extreme-programming", "self-hosted-infrastructure", - "keyboard-driven" + "keyboard-driven", + "feature-sliced-design", + "bullet-proof-react" ] } diff --git a/apps/docs/content/docs/contribution-guidelines/coding-standards/front-end.mdx b/apps/docs/content/docs/contribution-guidelines/coding-standards/front-end.mdx index 1a669f8d..2cc56d60 100644 --- a/apps/docs/content/docs/contribution-guidelines/coding-standards/front-end.mdx +++ b/apps/docs/content/docs/contribution-guidelines/coding-standards/front-end.mdx @@ -4,8 +4,21 @@ description: WIP icon: Atom --- +## [Presenter, Container Pattern](https://www.patterns.dev/react/presentational-container-pattern/) + +TLDR: Presenter components answer **how** UI is rendered. Container components answer **what** UI is rendered. + +## tsconfig [tsconfig.json with comments and explanations](https://gist.github.com/er-ant/e8c2f8c47ad871a4685c2a17800c86ae) +## Folder structure +Please refer to the [Feature-Sliced Design](/concepts/feature-sliced-design) and the [Bullet-Proof React](/concepts/bullet-proof-react) for the folder structure. + ## Scaffolding Shadcn Ecosystem Library - Find desired library from [Awesome Shadcn](https://github.com/birobirobiro/awesome-shadcn-ui) or elsewhere. @@ -27,4 +40,4 @@ icon: Atom "@shadcn/*": ["libs/external/shadcn/*"], "@website/*": ["libs/website/*"] }, -``` +``` \ No newline at end of file diff --git a/apps/docs/content/docs/contribution-guidelines/coding-standards/meta.json b/apps/docs/content/docs/contribution-guidelines/coding-standards/meta.json index 838dac31..81aed7c1 100644 --- a/apps/docs/content/docs/contribution-guidelines/coding-standards/meta.json +++ b/apps/docs/content/docs/contribution-guidelines/coding-standards/meta.json @@ -4,6 +4,7 @@ "unit-testing", "front-end", "back-end", - "tooling" + "tooling", + "style-guide" ] } diff --git a/apps/docs/content/docs/contribution-guidelines/coding-standards/style-guide.mdx b/apps/docs/content/docs/contribution-guidelines/coding-standards/style-guide.mdx new file mode 100644 index 00000000..809b1eac --- /dev/null +++ b/apps/docs/content/docs/contribution-guidelines/coding-standards/style-guide.mdx @@ -0,0 +1,28 @@ +--- +title: Style Guide +description: WIP +icon: PenTool +--- + +## Inspiration +Our style guide is an extension of the [Angular Style Guide](https://angular.dev/style-guide). + + +## Overview + +### File Naming Convention +We follow the Angular style guide for naming files. The file name should be in kebab-case, not capitalized, and should describe the content of the file. For example, a component file should be named `event-card.component.ts`. + +**Note:** Presenter and component files must be prefixed with a `.presenter` or a `.container` and do not need a `.component` suffix + +Examples: +- `event-card.component.tsx` +- `event-card.container.tsx` +- `event-card.presenter.tsx` +- `event.service.tsx` +- `event.helper.tsx` diff --git a/apps/website-e2e/playwright.config.ts b/apps/website-e2e/playwright.config.ts index d079ecde..701c01dd 100644 --- a/apps/website-e2e/playwright.config.ts +++ b/apps/website-e2e/playwright.config.ts @@ -40,30 +40,33 @@ export default defineConfig({ // reporter: [['html']], projects: [ { - name: 'chromium', + name: 'chromium (desktop)', use: { ...devices['Desktop Chrome'] }, }, - - // { - // name: 'firefox', - // use: { ...devices['Desktop Firefox'] }, - // }, - - // { - // name: 'webkit', - // use: { ...devices['Desktop Safari'] }, - // }, - - // Uncomment for mobile browsers support - /* { - name: 'Mobile Chrome', + { + name: 'firefox (desktop)', + use: { ...devices['Desktop Firefox'] }, + testIgnore: ['./src/lighthouse.spec.ts'], + }, + { + name: 'webkit (desktop)', + use: { ...devices['Desktop Safari'] }, + testIgnore: ['./src/lighthouse.spec.ts'], + }, + { + name: 'webkit (tablet)', + use: { ...devices['iPad Mini'] }, + testIgnore: ['./src/lighthouse.spec.ts'], + }, + { + name: 'chromium (mobile)', use: { ...devices['Pixel 5'] }, }, { - name: 'Mobile Safari', + name: 'webkit (mobile)', use: { ...devices['iPhone 12'] }, - }, */ - + testIgnore: ['./src/lighthouse.spec.ts'], + }, // Uncomment for branded browsers /* { name: 'Microsoft Edge', @@ -74,4 +77,10 @@ export default defineConfig({ use: { ...devices['Desktop Chrome'], channel: 'chrome' }, } */ ], + + // Ignore Chromium projects in CI to speed up runs + ignore: process.env.CI + ? ['chromium (desktop)', 'chromium (mobile)'] + : [], + }) diff --git a/apps/website-e2e/src/lighthouse.spec.ts b/apps/website-e2e/src/lighthouse.spec.ts new file mode 100644 index 00000000..2051472a --- /dev/null +++ b/apps/website-e2e/src/lighthouse.spec.ts @@ -0,0 +1,3 @@ +import { doLighthouseTest } from '@website-e2e/features/lighthouse/lighthouse' + +doLighthouseTest() diff --git a/apps/website-e2e/src/pages/home.spec.ts b/apps/website-e2e/src/pages/home.spec.ts new file mode 100644 index 00000000..b45714e7 --- /dev/null +++ b/apps/website-e2e/src/pages/home.spec.ts @@ -0,0 +1,13 @@ +// import type { Browser } from 'playwright' +// import { chromium, expect, test } from '@playwright/test' +import { footerMobileTabletDesktop } from '@website-e2e/ui/footer/footer' +import { navbarMobile, navbarMobileTabletDesktop, navbarTabletDesktop } from '@website-e2e/ui/navbar/navbar' +import { sponsorsMobileTabletDesktop } from '@website-e2e/ui/sponsorship/sponsorship' +import { welcomeMobileTabletDesktop } from '@website-e2e/ui/welcome/welcome' + +navbarMobileTabletDesktop() +navbarTabletDesktop() +navbarMobile() +welcomeMobileTabletDesktop() +footerMobileTabletDesktop() +sponsorsMobileTabletDesktop() diff --git a/apps/website-e2e/src/pom.ts b/apps/website-e2e/src/pom.ts new file mode 100644 index 00000000..c8fe67f2 --- /dev/null +++ b/apps/website-e2e/src/pom.ts @@ -0,0 +1,123 @@ +import type { Locator, Page } from '@playwright/test' + +// TODO: Refactor link locators once we have a CMS +export class WebsiteLayout { + // Page object + readonly page: Page + + // Website Sections + readonly welcomeSection: Locator + readonly aboutSection: Locator + readonly eventsSection: Locator + readonly sponsorsSection: Locator + readonly faqSection: Locator + readonly navbarSection: Locator + readonly footerSection: Locator + + // Mobile + Tablet + Desktop Elements + readonly cuHackingLogoIconNav: Locator + readonly cuHackingLogoFooter: Locator + readonly sponsorshipPackage: Locator + + // Mobile + Tablet + Desktop Platform Links + readonly discordLinkNavbar: Locator + readonly instagramLinkNavbar: Locator + readonly linkedinLinkNavbar: Locator + readonly linktreeLinkNavbar: Locator + readonly figmaLinkNavbar: Locator + readonly githubLinkNavbar: Locator + readonly emailLinkNavbar: Locator + readonly docsLinkNavbar: Locator + + readonly discordLinkWelcome: Locator + readonly instagramLinkWelcome: Locator + readonly linkedinLinkWelcome: Locator + readonly linktreeLinkWelcome: Locator + readonly figmaLinkWelcome: Locator + readonly githubLinkWelcome: Locator + readonly emailLinkWelcome: Locator + readonly docsLinkWelcome: Locator + + readonly discordLinkFooter: Locator + readonly instagramLinkFooter: Locator + readonly linkedinLinkFooter: Locator + readonly linktreeLinkFooter: Locator + readonly figmaLinkFooter: Locator + readonly githubLinkFooter: Locator + readonly emailLinkFooter: Locator + readonly docsLinkFooter: Locator + + // Mobile + Tablet + Desktop Navigation Links + readonly aboutLink: Locator + readonly eventsLink: Locator + readonly sponsorsLink: Locator + readonly faqLink: Locator + + // Mobile + Tablet + readonly hamburgerIcon: Locator + + // Tablet + Desktop + readonly searchBar: Locator + readonly searchModal: Locator + readonly themeToggle: Locator + readonly sideBarToggle: Locator + readonly overviewSidebar: Locator + + constructor(page: Page) { + this.page = page + + this.welcomeSection = page.locator('#welcome') + this.aboutSection = page.locator('#about') + this.eventsSection = page.locator('#events') + this.sponsorsSection = page.locator('#sponsors') + this.faqSection = page.locator('#faq') + this.navbarSection = page.locator('#navbar') + this.footerSection = page.locator('footer') + + this.cuHackingLogoIconNav = page.getByRole('link', { name: 'Return to homepage' }).first() + this.cuHackingLogoFooter = page.getByRole('img', { name: 'cuHacking logo' }).last() + this.sponsorshipPackage = page.getByRole('link', { name: 'Sponsorship Package' }) + + this.hamburgerIcon = page.getByRole('button', { name: 'Open Navigation Drawer' }) + + const navbar = page.locator('#navbar-social-media-links') + this.discordLinkNavbar = navbar.getByRole('link', { name: 'Discord' }) + this.instagramLinkNavbar = navbar.getByRole('link', { name: 'Instagram' }) + this.linkedinLinkNavbar = navbar.getByRole('link', { name: 'LinkedIn' }) + this.linktreeLinkNavbar = navbar.getByRole('link', { name: 'Linktree' }) + this.figmaLinkNavbar = navbar.getByRole('link', { name: 'Figma' }) + this.githubLinkNavbar = navbar.getByRole('link', { name: 'GitHub' }) + this.emailLinkNavbar = navbar.getByRole('link', { name: 'Email' }) + this.docsLinkNavbar = navbar.getByRole('link', { name: 'Docs' }) + + const welcome = page.locator('#welcome') + this.discordLinkWelcome = welcome.getByRole('link', { name: 'Discord' }) + this.instagramLinkWelcome = welcome.getByRole('link', { name: 'Instagram' }) + this.linkedinLinkWelcome = welcome.getByRole('link', { name: 'LinkedIn' }) + this.linktreeLinkWelcome = welcome.getByRole('link', { name: 'Linktree' }) + this.figmaLinkWelcome = welcome.getByRole('link', { name: 'Figma' }) + this.githubLinkWelcome = welcome.getByRole('link', { name: 'GitHub' }) + this.emailLinkWelcome = welcome.getByRole('link', { name: 'Email' }) + this.docsLinkWelcome = welcome.getByRole('link', { name: 'Docs' }) + + const footer = page.locator('footer') + this.discordLinkFooter = footer.getByRole('link', { name: 'Discord' }) + this.instagramLinkFooter = footer.getByRole('link', { name: 'Instagram' }) + this.linkedinLinkFooter = footer.getByRole('link', { name: 'LinkedIn' }) + this.linktreeLinkFooter = footer.getByRole('link', { name: 'Linktree' }) + this.figmaLinkFooter = footer.getByRole('link', { name: 'Figma' }) + this.githubLinkFooter = footer.getByRole('link', { name: 'GitHub' }) + this.emailLinkFooter = footer.getByRole('link', { name: 'Email' }) + this.docsLinkFooter = footer.getByRole('link', { name: 'Docs' }) + + this.aboutLink = page.getByRole('link', { name: 'About' }) + this.eventsLink = page.getByRole('link', { name: 'Events' }) + this.sponsorsLink = page.getByRole('link', { name: 'Sponsors' }).first() + this.faqLink = page.getByRole('link', { name: 'FAQ' }) + } + + async goto() { + await this.page + .goto('http://localhost:3000') + } +} diff --git a/apps/website-e2e/src/website.spec.ts b/apps/website-e2e/src/website.spec.ts deleted file mode 100644 index 4cc88489..00000000 --- a/apps/website-e2e/src/website.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -// import type { Browser } from 'playwright' -// import { chromium, expect, test } from '@playwright/test' -import { expect, test } from '@playwright/test' -// import getPort from 'get-port' -// import { playAudit } from 'playwright-lighthouse' - -// const lighthouseTest = test.extend<{ Page }, { port: number, browser: Browser }>({ -// port: [ -// async (use) => { -// // Assign a unique port for each playwright worker to support parallel tests -// const port = await getPort() -// await use(port) -// }, -// { scope: 'worker' }, -// ], - -// browser: [ -// async ({ port }, use) => { -// const browser = await chromium.launch({ -// args: [`--remote-debugging-port=${port}`], -// }) -// await use(browser) -// }, -// { scope: 'worker' }, -// ], -// }) - -// const thresholdsConfig = { -// 'performance': 90, -// 'accessibility': 90, -// 'best-practices': 90, -// 'seo': 90, -// // 'pwa': 50, -// } - -// TODO: re-activate after website refactor (three.js/spline) -// lighthouseTest('should pass lighthouse audits', async ({ page, port }) => { -// await page.goto('/') - -// await playAudit({ -// page, -// port, -// thresholds: thresholdsConfig, -// reports: { -// formats: { -// // json: true, // defaults to false -// html: true, // defaults to false -// // csv: true, // defaults to false -// }, -// name: `latest-report`, // defaults to `lighthouse-${new Date().getTime()}` -// directory: `${process.cwd()}../../../lighthouse-report`, // defaults to `${process.cwd()}/lighthouse` -// }, -// }) -// }) - -test('has title', async ({ page }) => { - await page.goto('/') - - // Expect h1 to contain a substring. - expect(await page.locator('h1').textContent()).toContain('Sponsorship') -}) diff --git a/apps/website/app/root.tsx b/apps/website/app/root.tsx index af97fe19..4b08efb3 100644 --- a/apps/website/app/root.tsx +++ b/apps/website/app/root.tsx @@ -1,5 +1,4 @@ -import type { LinksFunction, MetaFunction } from '@remix-run/node' - +import type { MetaFunction } from '@remix-run/node' import { Links, Meta, @@ -16,24 +15,11 @@ export const meta: MetaFunction = () => [ }, ] -export const links: LinksFunction = () => [ - { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, - { - rel: 'preconnect', - href: 'https://fonts.gstatic.com', - crossOrigin: 'anonymous', - }, - { - rel: 'stylesheet', - href: 'https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap', - }, -] - export function Layout({ children }: { children: React.ReactNode }) { return ( diff --git a/apps/website/app/routes/_index.tsx b/apps/website/app/routes/_index.tsx index c162c84b..48ece799 100644 --- a/apps/website/app/routes/_index.tsx +++ b/apps/website/app/routes/_index.tsx @@ -1,5 +1,5 @@ import { Home } from '@website/pages/home' -// import { Icon } from '@cuhacking/shared/ui/src/cuHacking/components/icon/icon' +// import { Icon } from '@cuhacking/shared/ui/icon/icon' export default function Index() { return ( diff --git a/apps/website/vite.config.ts b/apps/website/vite.config.ts index 60161eee..0d680f50 100644 --- a/apps/website/vite.config.ts +++ b/apps/website/vite.config.ts @@ -30,7 +30,7 @@ export default defineConfig({ server: { port: 3000, fs: { - allow: ['..'], + allow: ['../../libs/shared/', '../../libs/', '..'], }, }, }) diff --git a/libs/external/shadcn/tailwind.config.ts b/libs/external/shadcn/tailwind.config.ts index db8edc8a..4f4cd2e4 100644 --- a/libs/external/shadcn/tailwind.config.ts +++ b/libs/external/shadcn/tailwind.config.ts @@ -18,7 +18,7 @@ export function buildConfig( theme: { extend: { fontFamily: { - sans: ['JetBrains Mono', 'sans-serif'], + mono: ['JetBrains Mono'], }, backgroundImage: { 'greendiant': 'linear-gradient(200deg, hsl(var(--secondary)) 10%, hsl(var(--primary)) 90%)', diff --git a/libs/shared/assets/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf b/libs/shared/assets/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf new file mode 100644 index 00000000..994761f2 Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf b/libs/shared/assets/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf new file mode 100644 index 00000000..1b3d7f27 Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/OFL.txt b/libs/shared/assets/fonts/JetBrains_Mono/OFL.txt new file mode 100644 index 00000000..3f348475 --- /dev/null +++ b/libs/shared/assets/fonts/JetBrains_Mono/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/libs/shared/assets/fonts/JetBrains_Mono/README.txt b/libs/shared/assets/fonts/JetBrains_Mono/README.txt new file mode 100644 index 00000000..0a8510da --- /dev/null +++ b/libs/shared/assets/fonts/JetBrains_Mono/README.txt @@ -0,0 +1,79 @@ +JetBrains Mono Variable Font +============================ + +This download contains JetBrains Mono as both variable fonts and static fonts. + +JetBrains Mono is a variable font with this axis: + wght + +This means all the styles are contained in these files: + JetBrainsMono-VariableFont_wght.ttf + JetBrainsMono-Italic-VariableFont_wght.ttf + +If your app fully supports variable fonts, you can now pick intermediate styles +that aren’t available as static fonts. Not all apps support variable fonts, and +in those cases you can use the static font files for JetBrains Mono: + static/JetBrainsMono-Thin.ttf + static/JetBrainsMono-ExtraLight.ttf + static/JetBrainsMono-Light.ttf + static/JetBrainsMono-Regular.ttf + static/JetBrainsMono-Medium.ttf + static/JetBrainsMono-SemiBold.ttf + static/JetBrainsMono-Bold.ttf + static/JetBrainsMono-ExtraBold.ttf + static/JetBrainsMono-ThinItalic.ttf + static/JetBrainsMono-ExtraLightItalic.ttf + static/JetBrainsMono-LightItalic.ttf + static/JetBrainsMono-Italic.ttf + static/JetBrainsMono-MediumItalic.ttf + static/JetBrainsMono-SemiBoldItalic.ttf + static/JetBrainsMono-BoldItalic.ttf + static/JetBrainsMono-ExtraBoldItalic.ttf + +Get started +----------- + +1. Install the font files you want to use + +2. Use your app's font picker to view the font family and all the +available styles + +Learn more about variable fonts +------------------------------- + + https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts + https://variablefonts.typenetwork.com + https://medium.com/variable-fonts + +In desktop apps + + https://theblog.adobe.com/can-variable-fonts-illustrator-cc + https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts + +Online + + https://developers.google.com/fonts/docs/getting_started + https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide + https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts + +Installing fonts + + MacOS: https://support.apple.com/en-us/HT201749 + Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux + Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows + +Android Apps + + https://developers.google.com/fonts/docs/android + https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts + +License +------- +Please read the full license text (OFL.txt) to understand the permissions, +restrictions and requirements for usage, redistribution, and modification. + +You can use them in your products & projects – print or digital, +commercial or otherwise. + +This isn't legal advice, please consider consulting a lawyer and see the full +license for all details. diff --git a/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Bold.ttf b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Bold.ttf new file mode 100644 index 00000000..1926c804 Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Bold.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-BoldItalic.ttf b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-BoldItalic.ttf new file mode 100644 index 00000000..a4477513 Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-BoldItalic.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraBold.ttf b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraBold.ttf new file mode 100644 index 00000000..0c5f863f Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraBold.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraBoldItalic.ttf b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraBoldItalic.ttf new file mode 100644 index 00000000..d62fc3c3 Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraBoldItalic.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraLight.ttf b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraLight.ttf new file mode 100644 index 00000000..605f79d0 Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraLight.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraLightItalic.ttf b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraLightItalic.ttf new file mode 100644 index 00000000..befe9d20 Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-ExtraLightItalic.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Italic.ttf b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Italic.ttf new file mode 100644 index 00000000..8cf794ab Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Italic.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Light.ttf b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Light.ttf new file mode 100644 index 00000000..9d5d8a5d Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Light.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-LightItalic.ttf b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-LightItalic.ttf new file mode 100644 index 00000000..4c91d3e0 Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-LightItalic.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Medium.ttf b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Medium.ttf new file mode 100644 index 00000000..ad71d92b Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Medium.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-MediumItalic.ttf b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-MediumItalic.ttf new file mode 100644 index 00000000..4c96cc57 Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-MediumItalic.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Regular.ttf b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Regular.ttf new file mode 100644 index 00000000..436c982f Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Regular.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-SemiBold.ttf b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-SemiBold.ttf new file mode 100644 index 00000000..b00a6485 Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-SemiBold.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-SemiBoldItalic.ttf b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-SemiBoldItalic.ttf new file mode 100644 index 00000000..5b6c9a8f Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-SemiBoldItalic.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Thin.ttf b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Thin.ttf new file mode 100644 index 00000000..322705a9 Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-Thin.ttf differ diff --git a/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-ThinItalic.ttf b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-ThinItalic.ttf new file mode 100644 index 00000000..58b40854 Binary files /dev/null and b/libs/shared/assets/fonts/JetBrains_Mono/static/JetBrainsMono-ThinItalic.ttf differ diff --git a/libs/shared/assets/icons/general/cross-1.svg b/libs/shared/assets/icons/general/cross-1.svg new file mode 100644 index 00000000..35169627 --- /dev/null +++ b/libs/shared/assets/icons/general/cross-1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/libs/shared/assets/icons/socials/linkedin-white-1.svg b/libs/shared/assets/icons/socials/linkedin-white-1.svg index 3c437f8b..2f2e31ae 100644 --- a/libs/shared/assets/icons/socials/linkedin-white-1.svg +++ b/libs/shared/assets/icons/socials/linkedin-white-1.svg @@ -1,12 +1,6 @@ - - - - - - - - - - + + + + diff --git a/libs/shared/config/tailwind/shadcn.ts b/libs/shared/config/tailwind/shadcn.ts index fe0022f1..bbc298b2 100644 --- a/libs/shared/config/tailwind/shadcn.ts +++ b/libs/shared/config/tailwind/shadcn.ts @@ -3,7 +3,7 @@ import { join } from 'node:path' import { createGlobPatternsForDependencies } from '@nx/react/tailwind' import TailwindAnimate from 'tailwindcss-animate' - +// TODO: Refactor to not add custom styles to shadcn tailwind, needed for now, DO NOT CHANGE export function buildConfig( appDir: string, ): Config { @@ -17,6 +17,19 @@ export function buildConfig( ], theme: { extend: { + backgroundImage: { + 'greendiant': 'linear-gradient(200deg, hsl(var(--secondary)) 10%, hsl(var(--primary)) 90%)', + 'g-keyboardBlack': 'linear-gradient(300deg , hsl(var(--g-keyboardblack-start)) -10%, hsl(var(--background)) 100%)', + 'g-nav-drawer-background': ` + linear-gradient( + 90deg, + hsl(var(--background)) 0%, + hsl(var(--light-black)) 25%, + hsl(var(--light-black)) 75%, + hsl(var(--background)) 100% + ) + `, + }, colors: { border: 'hsl(var(--border))', input: 'hsl(var(--input))', @@ -88,6 +101,12 @@ export function buildConfig( '100%': { 'background-position': '200%' }, }, }, + boxShadow: { + dropShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)', + innerShadow: 'inset 0 4px 4px rgba(0, 0, 0, 0.25)', + buttonKeyboard: '0.5px 0.5px 0px 0.6px rgba(0, 0, 0, 0.70), 0.35px 0.35px 0.2px 0.75px rgba(255, 255, 255, 0.15) inset, 4px 2px 4px -1px rgba(0, 0, 0, 0.25)', + buttonKeyboardHover: '0.25px 0.25px 0px 0.5px #0A0A0A, 0.2px 0.2px 0.2px 0.35px rgba(255, 255, 255, 0.25) inset, 0.2px 0.2px 0px 0.75px rgba(137, 237, 16, 0.25), 0px 0px 10px -4px rgba(137, 237, 16, 0.60), 4px 2px 4px -1px rgba(0, 0, 0, 0.25)', + }, animation: { 'accordion-down': 'accordion-down 0.2s ease-out', 'accordion-up': 'accordion-up 0.2s ease-out', diff --git a/libs/shared/ui/src/cuHacking/components/accordion/accordion.stories.tsx b/libs/shared/ui/accordion/accordion.stories.tsx similarity index 98% rename from libs/shared/ui/src/cuHacking/components/accordion/accordion.stories.tsx rename to libs/shared/ui/accordion/accordion.stories.tsx index 9427d978..ce364256 100644 --- a/libs/shared/ui/src/cuHacking/components/accordion/accordion.stories.tsx +++ b/libs/shared/ui/accordion/accordion.stories.tsx @@ -1,5 +1,4 @@ import type { Meta, StoryObj } from '@storybook/react' -import React from 'react' import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from './accordion' const meta = { diff --git a/libs/shared/ui/src/cuHacking/components/accordion/accordion.tsx b/libs/shared/ui/accordion/accordion.tsx similarity index 93% rename from libs/shared/ui/src/cuHacking/components/accordion/accordion.tsx rename to libs/shared/ui/accordion/accordion.tsx index 618d8195..7a312826 100644 --- a/libs/shared/ui/src/cuHacking/components/accordion/accordion.tsx +++ b/libs/shared/ui/accordion/accordion.tsx @@ -1,5 +1,5 @@ +import { cn } from '@cuhacking/shared/utils/cn' import * as AccordionPrimitive from '@radix-ui/react-accordion' -import { cn } from '@shadcn/lib/utils' import { ChevronDown } from 'lucide-react' import * as React from 'react' @@ -10,11 +10,7 @@ const AccordionItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )) AccordionItem.displayName = 'AccordionItem' diff --git a/libs/shared/ui/src/cuHacking/components/accordion/index.ts b/libs/shared/ui/accordion/index.ts similarity index 100% rename from libs/shared/ui/src/cuHacking/components/accordion/index.ts rename to libs/shared/ui/accordion/index.ts diff --git a/libs/shared/ui/src/cuHacking/components/button/button.stories.tsx b/libs/shared/ui/button/button.stories.tsx similarity index 97% rename from libs/shared/ui/src/cuHacking/components/button/button.stories.tsx rename to libs/shared/ui/button/button.stories.tsx index 6172a467..572a1e40 100644 --- a/libs/shared/ui/src/cuHacking/components/button/button.stories.tsx +++ b/libs/shared/ui/button/button.stories.tsx @@ -7,7 +7,7 @@ import { import { fn } from '@storybook/test' import React from 'react' import { Button } from './button' - +// HALP: REANME WEBSITE-LIB TO LIBS/WEBSITE const meta = { title: 'cuHacking Design System/Button', component: Button, diff --git a/libs/shared/ui/src/cuHacking/components/button/button.tsx b/libs/shared/ui/button/button.tsx similarity index 91% rename from libs/shared/ui/src/cuHacking/components/button/button.tsx rename to libs/shared/ui/button/button.tsx index d501add2..d1dc89dd 100644 --- a/libs/shared/ui/src/cuHacking/components/button/button.tsx +++ b/libs/shared/ui/button/button.tsx @@ -1,5 +1,5 @@ +import { cn } from '@cuhacking/shared/utils/cn' import { Slot } from '@radix-ui/react-slot' -import { cn } from '@shadcn/lib/utils' import { cva, type VariantProps } from 'class-variance-authority' import * as React from 'react' @@ -9,7 +9,8 @@ const buttonVariants = cva( { variants: { variant: { - default: 'shadow-buttonKeyboard bg-g-keyboardBlack text-primary hover:bg-primary/90 hover:shadow-buttonKeyboardHover', + default: + 'shadow-buttonKeyboard bg-g-keyboardBlack text-primary hover:bg-primary/90 hover:shadow-buttonKeyboardHover', destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', outline: diff --git a/libs/shared/ui/src/cuHacking/components/button/index.ts b/libs/shared/ui/button/index.ts similarity index 100% rename from libs/shared/ui/src/cuHacking/components/button/index.ts rename to libs/shared/ui/button/index.ts diff --git a/libs/shared/ui/src/cuHacking/components/drawer/drawer.stories.tsx b/libs/shared/ui/drawer/drawer.stories.tsx similarity index 100% rename from libs/shared/ui/src/cuHacking/components/drawer/drawer.stories.tsx rename to libs/shared/ui/drawer/drawer.stories.tsx diff --git a/libs/shared/ui/src/cuHacking/components/drawer/drawer.tsx b/libs/shared/ui/drawer/drawer.tsx similarity index 100% rename from libs/shared/ui/src/cuHacking/components/drawer/drawer.tsx rename to libs/shared/ui/drawer/drawer.tsx diff --git a/libs/shared/ui/src/cuHacking/components/drawer/index.ts b/libs/shared/ui/drawer/index.ts similarity index 100% rename from libs/shared/ui/src/cuHacking/components/drawer/index.ts rename to libs/shared/ui/drawer/index.ts diff --git a/libs/shared/ui/src/cuHacking/components/glassmorphic-card/glassmorphic-card.stories.tsx b/libs/shared/ui/glassmorphic-card/glassmorphic-card.stories.tsx similarity index 100% rename from libs/shared/ui/src/cuHacking/components/glassmorphic-card/glassmorphic-card.stories.tsx rename to libs/shared/ui/glassmorphic-card/glassmorphic-card.stories.tsx diff --git a/libs/shared/ui/src/cuHacking/components/glassmorphic-card/glassmorphic-card.tsx b/libs/shared/ui/glassmorphic-card/glassmorphic-card.tsx similarity index 84% rename from libs/shared/ui/src/cuHacking/components/glassmorphic-card/glassmorphic-card.tsx rename to libs/shared/ui/glassmorphic-card/glassmorphic-card.tsx index 3056cd6c..e773e806 100644 --- a/libs/shared/ui/src/cuHacking/components/glassmorphic-card/glassmorphic-card.tsx +++ b/libs/shared/ui/glassmorphic-card/glassmorphic-card.tsx @@ -1,8 +1,7 @@ import type { ReactNode } from 'react' import InfoIcon from '@cuhacking/shared/assets/icons/general/info-1.svg' -import { cn } from '@shadcn/lib/utils' +import { cn } from '@cuhacking/shared/utils/cn' import { cva } from 'class-variance-authority' -import React from 'react' interface GlassmorphicCardProps { children: ReactNode @@ -26,7 +25,11 @@ const glassmorphicCardVariants = cva( }, ) -export function GlassmorphicCard({ children, variant = 'default', className }: GlassmorphicCardProps) { +export function GlassmorphicCard({ + children, + variant = 'default', + className, +}: GlassmorphicCardProps) { return (
{variant === 'info' diff --git a/libs/shared/ui/src/cuHacking/components/glassmorphic-card/index.ts b/libs/shared/ui/glassmorphic-card/index.ts similarity index 100% rename from libs/shared/ui/src/cuHacking/components/glassmorphic-card/index.ts rename to libs/shared/ui/glassmorphic-card/index.ts diff --git a/libs/shared/ui/global.css b/libs/shared/ui/global.css index 7f87a761..5dc292e5 100644 --- a/libs/shared/ui/global.css +++ b/libs/shared/ui/global.css @@ -2,10 +2,17 @@ * Don't forget to update your APPs global.css to include this file! */ +/* DO NOT IMPORT FONT FROM GOOGLE CDN */ +/* @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap'); */ + @tailwind base; @tailwind components; @tailwind utilities; -@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap'); + +@font-face { + font-family: 'JetBrains Mono'; + src: url('../../shared/assets/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf'); +} @layer base { :root { @@ -30,6 +37,8 @@ --input: 0 0% 100%; --ring: 0 0% 100% / 15%; --radius: 0.5rem; + --greendiant-start: 78 100% 42%; + --greendiant-end: 136 98% 45%; --g-keyboardblack-start: 0 0% 5%; --light-black: 0 0% 0% / 5%; --color-1: 0 100% 63%; @@ -38,7 +47,6 @@ --color-4: 195 100% 63%; --color-5: 90 100% 63%; } - .dark { --background: 0 0% 0%; --foreground: 0 0% 100%; @@ -73,6 +81,7 @@ @apply border-border; } body { + font-family: 'JetBrains Mono', monospace; @apply bg-background text-foreground; font-feature-settings: 'rlig' 1, diff --git a/libs/shared/ui/src/cuHacking/components/icon/icon.stories.tsx b/libs/shared/ui/icon/icon.stories.tsx similarity index 93% rename from libs/shared/ui/src/cuHacking/components/icon/icon.stories.tsx rename to libs/shared/ui/icon/icon.stories.tsx index 412eeada..a14b179e 100644 --- a/libs/shared/ui/src/cuHacking/components/icon/icon.stories.tsx +++ b/libs/shared/ui/icon/icon.stories.tsx @@ -24,10 +24,25 @@ import linkedin_green from '@cuhacking/shared/assets/icons/socials/linkedin-gree import linkedin_white from '@cuhacking/shared/assets/icons/socials/linkedin-white-1.svg' import linktree_green from '@cuhacking/shared/assets/icons/socials/linktree-green-1.svg' import linktree_white from '@cuhacking/shared/assets/icons/socials/linktree-white-1.svg' +import { createRemixStub } from '@remix-run/testing' import { Icon } from './icon' const meta = { title: 'cuHacking Design System/Icon', + decorators: [ + (story) => { + const remixStub = createRemixStub([ + { + path: '/*', + action: () => ({ redirect: '/' }), + loader: () => ({ redirect: '/' }), + Component: () => story(), + }, + ]) + + return remixStub({ initialEntries: ['/'] }) + }, + ], component: Icon, tags: ['autodocs'], parameters: { diff --git a/libs/shared/ui/src/cuHacking/components/icon/icon.tsx b/libs/shared/ui/icon/icon.tsx similarity index 88% rename from libs/shared/ui/src/cuHacking/components/icon/icon.tsx rename to libs/shared/ui/icon/icon.tsx index ff184d2b..6c01c2ba 100644 --- a/libs/shared/ui/src/cuHacking/components/icon/icon.tsx +++ b/libs/shared/ui/icon/icon.tsx @@ -1,5 +1,5 @@ import type Media from '@cuhacking/types/media' -import { cn } from '@shadcn/lib/utils' +import { cn } from '@cuhacking/shared/utils/cn' import React from 'react' interface IconProps { @@ -11,7 +11,7 @@ interface IconProps { export function Icon({ media, className }: IconProps) { return ( <> - { media + {media ? ( - ) } diff --git a/libs/shared/ui/src/cuHacking/components/icon/index.ts b/libs/shared/ui/icon/index.ts similarity index 100% rename from libs/shared/ui/src/cuHacking/components/icon/index.ts rename to libs/shared/ui/icon/index.ts diff --git a/libs/shared/ui/src/cuHacking/components/navigation-menu/index.ts b/libs/shared/ui/navigation-menu/index.ts similarity index 100% rename from libs/shared/ui/src/cuHacking/components/navigation-menu/index.ts rename to libs/shared/ui/navigation-menu/index.ts diff --git a/libs/shared/ui/src/cuHacking/components/navigation-menu/navigation-menu.stories.tsx b/libs/shared/ui/navigation-menu/navigation-menu.stories.tsx similarity index 100% rename from libs/shared/ui/src/cuHacking/components/navigation-menu/navigation-menu.stories.tsx rename to libs/shared/ui/navigation-menu/navigation-menu.stories.tsx diff --git a/libs/shared/ui/src/cuHacking/components/navigation-menu/navigation-menu.tsx b/libs/shared/ui/navigation-menu/navigation-menu.tsx similarity index 99% rename from libs/shared/ui/src/cuHacking/components/navigation-menu/navigation-menu.tsx rename to libs/shared/ui/navigation-menu/navigation-menu.tsx index 0a3ecd58..bd6e8144 100644 --- a/libs/shared/ui/src/cuHacking/components/navigation-menu/navigation-menu.tsx +++ b/libs/shared/ui/navigation-menu/navigation-menu.tsx @@ -1,5 +1,5 @@ +import { cn } from '@cuhacking/shared/utils/cn' import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu' -import { cn } from '@shadcn/lib/utils' import { cva } from 'class-variance-authority' import { ChevronDown } from 'lucide-react' import * as React from 'react' diff --git a/libs/shared/ui/src/cuHacking/components/separator/index.ts b/libs/shared/ui/separator/index.ts similarity index 100% rename from libs/shared/ui/src/cuHacking/components/separator/index.ts rename to libs/shared/ui/separator/index.ts diff --git a/libs/shared/ui/src/cuHacking/components/separator/separator.tsx b/libs/shared/ui/separator/separator.tsx similarity index 93% rename from libs/shared/ui/src/cuHacking/components/separator/separator.tsx rename to libs/shared/ui/separator/separator.tsx index bc0b3249..0b51544e 100644 --- a/libs/shared/ui/src/cuHacking/components/separator/separator.tsx +++ b/libs/shared/ui/separator/separator.tsx @@ -1,5 +1,5 @@ +import { cn } from '@cuhacking/shared/utils/cn' import * as SeparatorPrimitive from '@radix-ui/react-separator' -import { cn } from '@shadcn/lib/utils' import * as React from 'react' export const Separator = React.forwardRef< diff --git a/libs/shared/ui/src/cuHacking/components/terminal-text/terminal-text.tsx b/libs/shared/ui/src/cuHacking/components/terminal-text/terminal-text.tsx deleted file mode 100644 index 37d63056..00000000 --- a/libs/shared/ui/src/cuHacking/components/terminal-text/terminal-text.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type Media from '@cuhacking/types/media' -import type { ReactNode } from 'react' -import { cn } from '@shadcn/lib/utils' -import { cva } from 'class-variance-authority' -import React from 'react' -import { Icon } from '../icon/icon' - -interface TerminalTextProps { - children: ReactNode - icon?: Media - className?: string - callToAction?: boolean -} - -const callToActionVariation = cva( - '', - { - variants: { - callToAction: { - true: 'bg-greendiant bg-clip-text text-transparent', - false: '', - }, - }, - }, -) - -const terminalTextVariation = cva( - 'flex flex-row gap-x-3 font-sans items-center', -) - -export function TerminalText({ icon, children, className = '', callToAction }: TerminalTextProps) { - return ( -
- { !icon - ? ( -
- ~ -
- ) - : } -
- {children} -
-
- ) -} diff --git a/libs/shared/ui/src/cuHacking/components/typography/typgoraphy.tsx b/libs/shared/ui/src/cuHacking/components/typography/typgoraphy.tsx new file mode 100644 index 00000000..8e5bb18c --- /dev/null +++ b/libs/shared/ui/src/cuHacking/components/typography/typgoraphy.tsx @@ -0,0 +1,42 @@ +import { cn } from '@cuhacking/shared/utils/cn' +import { cva } from 'class-variance-authority' +import React from 'react' + +interface TypographyProps { + variant: + | 'h1' + | 'h2' + | 'h3' + | 'h4' + | 'h5' + | 'h6' + | 'paragraph-sm' + | 'paragraph-base' + | 'paragraph-xs' + children: React.ReactNode + className: string +} + +const typographyVariants = cva('', { + variants: { + variant: { + 'h1': 'text-6xl font-bold font-sans', + 'h2': 'text-5xl font-normal font-sans', + 'h3': 'font-sans font-medium leading-10 font-sans text-4xl tracking-normal uppercase no-underline', + 'h4': 'text-2xl font-normal font-sans', + 'h5': 'text-lg font-normal font-sans', + 'h6': 'font-sans font-medium leading-5 text-sm tracking-normal uppercase no-underline', + 'paragraph-base': 'text-base font-normal font-sans', + 'paragraph-sm': 'text-sm font-normal font-sans', + 'paragraph-xs': 'text-xs font-normal font-sans', + }, + }, +}) + +export function Typography({ variant, className, children }: TypographyProps) { + return ( +
+ {children} +
+ ) +} diff --git a/libs/shared/ui/src/cuHacking/components/typography/typography.stories.tsx b/libs/shared/ui/src/cuHacking/components/typography/typography.stories.tsx new file mode 100644 index 00000000..00135931 --- /dev/null +++ b/libs/shared/ui/src/cuHacking/components/typography/typography.stories.tsx @@ -0,0 +1,73 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { Typography } from './typgoraphy' + +const meta: Meta = { + title: 'cuHacking Design System/Typography', + component: Typography, + tags: ['autodocs'], + parameters: { + layout: 'centered', + }, + args: { + variant: 'h1', + children: 'This is a headline', + className: '', + }, + argTypes: { + variant: { + control: { + type: 'select', + options: [ + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'paragraph-sm', + 'paragraph-base', + 'paragraph-xs', + ], + }, + table: { + type: { summary: 'Typography variant' }, + defaultValue: { summary: 'h1' }, + }, + }, + children: { + control: { type: 'text' }, + table: { + type: { summary: 'React.ReactNode' }, + }, + }, + className: { + control: { type: 'text' }, + table: { + type: { summary: 'string' }, + }, + }, + }, +} + +export default meta +type Story = StoryObj + +function createVariantStory(variant: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'paragraph-base' | 'paragraph-sm' | 'paragraph-xs'): Story { + return { + args: { + variant, + children: `This is ${variant} typography`, + className: '', + }, + } +} + +export const H1: Story = createVariantStory('h1') +export const H2: Story = createVariantStory('h2') +export const H3: Story = createVariantStory('h3') +export const H4: Story = createVariantStory('h4') +export const H5: Story = createVariantStory('h5') +export const H6: Story = createVariantStory('h6') +export const ParagraphBase: Story = createVariantStory('paragraph-base') +export const ParagraphSm: Story = createVariantStory('paragraph-sm') +export const ParagraphXs: Story = createVariantStory('paragraph-xs') diff --git a/libs/shared/ui/src/cuHacking/components/terminal-text/index.ts b/libs/shared/ui/terminal-text/index.ts similarity index 100% rename from libs/shared/ui/src/cuHacking/components/terminal-text/index.ts rename to libs/shared/ui/terminal-text/index.ts diff --git a/libs/shared/ui/src/cuHacking/components/terminal-text/terminal-text.stories.tsx b/libs/shared/ui/terminal-text/terminal-text.stories.tsx similarity index 98% rename from libs/shared/ui/src/cuHacking/components/terminal-text/terminal-text.stories.tsx rename to libs/shared/ui/terminal-text/terminal-text.stories.tsx index 7badc0b8..4135223a 100644 --- a/libs/shared/ui/src/cuHacking/components/terminal-text/terminal-text.stories.tsx +++ b/libs/shared/ui/terminal-text/terminal-text.stories.tsx @@ -5,7 +5,6 @@ import chevron_down from '@cuhacking/shared/assets/icons/general/chevron-down-1. import chevron_up from '@cuhacking/shared/assets/icons/general/chevron-up-1.svg' import link from '@cuhacking/shared/assets/icons/general/link-1.svg' import phone from '@cuhacking/shared/assets/icons/general/phone-1.svg' -import React from 'react' import { TerminalText } from './terminal-text' const meta = { diff --git a/libs/shared/ui/terminal-text/terminal-text.tsx b/libs/shared/ui/terminal-text/terminal-text.tsx new file mode 100644 index 00000000..2f2c41bd --- /dev/null +++ b/libs/shared/ui/terminal-text/terminal-text.tsx @@ -0,0 +1,41 @@ +import type Media from '@cuhacking/types/media' +import type { ReactNode } from 'react' +import { cn } from '@cuhacking/shared/utils/cn' +import { cva } from 'class-variance-authority' +import { Icon } from '../icon/icon' + +interface TerminalTextProps { + children: ReactNode + icon?: Media + className?: string + callToAction?: boolean +} + +const callToActionVariation = cva('', { + variants: { + callToAction: { + true: 'bg-greendiant bg-clip-text text-transparent', + false: '', + }, + }, +}) + +const terminalTextVariation = cva( + 'flex flex-row gap-x-3 items-start', +) + +export function TerminalText({ + icon, + children, + className = '', + callToAction, +}: TerminalTextProps) { + return ( +
+ {!icon ?
~
: } +
+ {children} +
+
+ ) +} diff --git a/libs/shared/ui/typography/typgoraphy.tsx b/libs/shared/ui/typography/typgoraphy.tsx new file mode 100644 index 00000000..0325672b --- /dev/null +++ b/libs/shared/ui/typography/typgoraphy.tsx @@ -0,0 +1,42 @@ +import { cn } from '@cuhacking/shared/utils/cn' +import { cva } from 'class-variance-authority' +import React from 'react' + +interface TypographyProps { + variant: + | 'h1' + | 'h2' + | 'h3' + | 'h4' + | 'h5' + | 'h6' + | 'paragraph-sm' + | 'paragraph-base' + | 'paragraph-xs' + children: React.ReactNode + className: string +} + +const typographyVariants = cva('', { + variants: { + variant: { + 'h1': 'text-6xl font-bold', + 'h2': 'text-5xl font-normal', + 'h3': 'font-medium leading-10 text-4xl tracking-normal uppercase no-underline', + 'h4': 'text-2xl font-normal', + 'h5': 'text-lg font-normal', + 'h6': 'font-medium leading-5 text-sm tracking-normal uppercase no-underline', + 'paragraph-base': 'text-base font-normal', + 'paragraph-sm': 'text-sm font-normal', + 'paragraph-xs': 'text-xs font-normal', + }, + }, +}) + +export function Typography({ variant, className, children }: TypographyProps) { + return ( +
+ {children} +
+ ) +} diff --git a/libs/shared/ui/typography/typography.stories.tsx b/libs/shared/ui/typography/typography.stories.tsx new file mode 100644 index 00000000..00135931 --- /dev/null +++ b/libs/shared/ui/typography/typography.stories.tsx @@ -0,0 +1,73 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { Typography } from './typgoraphy' + +const meta: Meta = { + title: 'cuHacking Design System/Typography', + component: Typography, + tags: ['autodocs'], + parameters: { + layout: 'centered', + }, + args: { + variant: 'h1', + children: 'This is a headline', + className: '', + }, + argTypes: { + variant: { + control: { + type: 'select', + options: [ + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'paragraph-sm', + 'paragraph-base', + 'paragraph-xs', + ], + }, + table: { + type: { summary: 'Typography variant' }, + defaultValue: { summary: 'h1' }, + }, + }, + children: { + control: { type: 'text' }, + table: { + type: { summary: 'React.ReactNode' }, + }, + }, + className: { + control: { type: 'text' }, + table: { + type: { summary: 'string' }, + }, + }, + }, +} + +export default meta +type Story = StoryObj + +function createVariantStory(variant: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'paragraph-base' | 'paragraph-sm' | 'paragraph-xs'): Story { + return { + args: { + variant, + children: `This is ${variant} typography`, + className: '', + }, + } +} + +export const H1: Story = createVariantStory('h1') +export const H2: Story = createVariantStory('h2') +export const H3: Story = createVariantStory('h3') +export const H4: Story = createVariantStory('h4') +export const H5: Story = createVariantStory('h5') +export const H6: Story = createVariantStory('h6') +export const ParagraphBase: Story = createVariantStory('paragraph-base') +export const ParagraphSm: Story = createVariantStory('paragraph-sm') +export const ParagraphXs: Story = createVariantStory('paragraph-xs') diff --git a/libs/shared/utils/ErrorBoundary.tsx b/libs/shared/utils/ErrorBoundary.tsx new file mode 100644 index 00000000..9c288b13 --- /dev/null +++ b/libs/shared/utils/ErrorBoundary.tsx @@ -0,0 +1,36 @@ +import type { ReactNode } from 'react' +import React from 'react' + +interface ErrorBoundaryProps { + children: ReactNode +} + +interface ErrorBoundaryState { + hasError: boolean +} + +export class ErrorBoundary extends React.Component< + ErrorBoundaryProps, + ErrorBoundaryState +> { + constructor(props: ErrorBoundaryProps) { + super(props) + this.state = { hasError: false } + } + + static getDerivedStateFromError(): ErrorBoundaryState { + return { hasError: true } + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { + console.error('ErrorBoundary caught an error', error, errorInfo) + } + + render() { + if (this.state.hasError) { + return
Something went wrong. Please try again later.
+ } + + return this.props.children + } +} diff --git a/libs/website-e2e/features/lighthouse/lighthouse.ts b/libs/website-e2e/features/lighthouse/lighthouse.ts new file mode 100644 index 00000000..1c7a194b --- /dev/null +++ b/libs/website-e2e/features/lighthouse/lighthouse.ts @@ -0,0 +1,66 @@ +import type { Browser } from 'playwright' +import { chromium, test } from '@playwright/test' +import getPort from 'get-port' +import { playAudit } from 'playwright-lighthouse' + +const lighthouseTest = test.extend<{ Page }, { port: number, browser: Browser }>({ + port: [ + // eslint-disable-next-line no-empty-pattern + async ({ }, use) => { + // Assign a unique port for each playwright worker to support parallel tests + const port = await getPort() + await use(port) + }, + { scope: 'worker' }, + ], + + browser: [ + async ({ port }, use) => { + const browser = await chromium.launch({ + args: [`--remote-debugging-port=${port}`], + }) + await use(browser) + }, + { scope: 'worker' }, + ], +}) + +const thresholdsConfig = { + // 'performance': 90, + 'performance': 50, // temporarily lowered to 50 until Spline components are removed + 'accessibility': 90, + 'best-practices': 90, + 'seo': 90, + // 'pwa': 50, +} + +export function doLighthouseTest() { + lighthouseTest('should pass lighthouse audits', async ({ page, port }, testInfo) => { + // Skip Lighthouse tests for specific projects + if ( + ['firefox (desktop)', 'webkit (desktop)', 'webkit (tablet)', 'webkit (mobile)'].includes( + testInfo.project.name, + ) + ) { + test.skip('Lighthouse is not supported for this project') + } + + await page.goto('/') + + await playAudit({ + page, + port, + thresholds: thresholdsConfig, + reports: { + formats: { + // json: true, // defaults to false + html: true, // defaults to false + // csv: true, // defaults to false + }, + name: `latest-report`, // defaults to `lighthouse-${new Date().getTime()}` + // eslint-disable-next-line node/prefer-global/process + directory: `${process.cwd()}../../../lighthouse-report`, // defaults to `${process.cwd()}/lighthouse` + }, + }) + }) +} diff --git a/libs/website-e2e/project.json b/libs/website-e2e/project.json new file mode 100644 index 00000000..a3cf6743 --- /dev/null +++ b/libs/website-e2e/project.json @@ -0,0 +1,8 @@ +{ + "name": "libs/website-e2e", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/website-e2e/", + "projectType": "library", + "tags": [], + "targets": {} +} diff --git a/libs/website-e2e/shared/constants.ts b/libs/website-e2e/shared/constants.ts new file mode 100644 index 00000000..cab2104b --- /dev/null +++ b/libs/website-e2e/shared/constants.ts @@ -0,0 +1,11 @@ +export const CUHACKING_2025_WEBSITE_URL = 'http://localhost:3000' + +// TODO: Add LinkedIn and Instagram links once auth w/Playwright is implemented +// export const CUHACKING_2025_LINKED_IN_URL = 'https://www.linkedin.com/company/cuhacking/' +// export const CUHACKING_2025_INSTAGRAM_URL = 'https://www.instagram.com/cuhacking/' +export const CUHACKING_2025_GITHUB_REPOSITORY_URL = 'https://github.com/cuhacking/2025' +export const CUHACKING_2025_LINKTREE_URL = 'https://linktr.ee/cuhacking_' +export const CUHACKING_2025_DISCORD_URL = 'https://discord.com/invite/h2cQqF9aZf' +export const CUHACKING_2025_FIGMA_URL = 'https://www.figma.com/design/wc1JOWR48tBNkjcjwY3AzB/%E2%8C%A8%EF%B8%8F-cuHacking-Design-System' +export const CUHACKING_2025_EMAIL_URL = 'mailto:info@cuhacking.ca' +export const CUHACKING_2025_DOCS_URL = 'https://docs.cuhacking.ca/' diff --git a/libs/website-e2e/shared/utils/click-and-go-to-page.ts b/libs/website-e2e/shared/utils/click-and-go-to-page.ts new file mode 100644 index 00000000..c9f8e22c --- /dev/null +++ b/libs/website-e2e/shared/utils/click-and-go-to-page.ts @@ -0,0 +1,10 @@ +import type { Locator, Page } from '@playwright/test' + +export async function clickAndGoToPage( + layout: { page: Page }, + button: Locator, +) { + const pagePromise = layout.page.context().waitForEvent('page') + await button.click() + return pagePromise +} diff --git a/libs/website-e2e/shared/utils/gotoURL.ts b/libs/website-e2e/shared/utils/gotoURL.ts new file mode 100644 index 00000000..ea6e1993 --- /dev/null +++ b/libs/website-e2e/shared/utils/gotoURL.ts @@ -0,0 +1,4 @@ +export async function gotoURL(url: string) { + await this.page + .goto(url) +} diff --git a/libs/website-e2e/tsconfig.json b/libs/website-e2e/tsconfig.json new file mode 100644 index 00000000..57ff8557 --- /dev/null +++ b/libs/website-e2e/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc" + }, + "include": [ + "**/*.ts", + "**/*.js", + "playwright.config.ts", + "src/**/*.spec.ts", + "src/**/*.spec.js", + "src/**/*.test.ts", + "src/**/*.test.js", + "src/**/*.d.ts" + ] +} diff --git a/libs/website-e2e/ui/footer/footer.ts b/libs/website-e2e/ui/footer/footer.ts new file mode 100644 index 00000000..574d1624 --- /dev/null +++ b/libs/website-e2e/ui/footer/footer.ts @@ -0,0 +1,105 @@ +import { test as base, expect } from '@playwright/test' +import { CUHACKING_2025_DISCORD_URL, CUHACKING_2025_DOCS_URL, CUHACKING_2025_EMAIL_URL, CUHACKING_2025_FIGMA_URL, CUHACKING_2025_GITHUB_REPOSITORY_URL, CUHACKING_2025_LINKTREE_URL, CUHACKING_2025_WEBSITE_URL } from '@website-e2e/shared/constants' +import { clickAndGoToPage } from '@website-e2e/shared/utils/click-and-go-to-page' +import { FooterLayout } from './pom' + +const test = base.extend<{ footerLayout: FooterLayout }>({ + footerLayout: async ({ page }, use) => { + const footerLayout = new FooterLayout(page) + await footerLayout.goto() + // eslint-disable-next-line react-hooks/rules-of-hooks + await use(footerLayout) + }, +}) + +export function footerMobileTabletDesktop() { + test.describe('FOOTER - Common Mobile, Tablet, and Desktop layout elements', () => { + test('should contain footer', async ({ footerLayout }) => { + await expect(footerLayout.footerSection).toBeVisible() + }) + test('should contain cuHacking logo in footer', async ({ footerLayout }) => { + await expect(footerLayout.cuHackingLogoFooter).toBeVisible() + }) + + test('should return to home page when cuHacking logo in footer is clicked', async ({ footerLayout }) => { + footerLayout.cuHackingLogoFooter.click() + await expect(footerLayout.page).toHaveURL(CUHACKING_2025_WEBSITE_URL) + }) + }) + + test.describe('FOOTER - Common Mobile, Tablet, and Desktop links', () => { + test('should contain Discord link in footer', async ({ footerLayout }) => { + await expect(footerLayout.discordLinkFooter).toBeVisible() + }) + + test('should take user to Discord page from footer', async ({ footerLayout }) => { + const res = await clickAndGoToPage(footerLayout, footerLayout.discordLinkFooter) + await expect(res).toHaveURL(CUHACKING_2025_DISCORD_URL) + }) + + test('should contain Instagram link in footer', async ({ footerLayout }) => { + await expect(footerLayout.instagramLinkFooter).toBeVisible() + }) + + // TODO: Instagram requires auth + // test('should take user to Instagram page from footer', async ({ footerLayout }) => { + // const res = await clickAndGoToPage(footerLayout, footerLayout.instagramLinkFooter) + // await expect(res).toHaveURL(CUHACKING_2025_INSTAGRAM_URL) + // }) + + test('should contain LinkedIn link in footer', async ({ footerLayout }) => { + await expect(footerLayout.linkedinLinkFooter).toBeVisible() + }) + + // TODO: Update when auth w/Playwright is implemented + // test('should take user to LinkedIn page from footer', async ({ footerLayout }) => { + // const res = await clickAndGoToPage(footerLayout, footerLayout.linkedinLinkFooter) + // await expect(res).toHaveURL(CUHACKING_2025_LINKED_IN_URL) + // }) + + test('should contain Linktree link in footer', async ({ footerLayout }) => { + await expect(footerLayout.linktreeLinkFooter).toBeVisible() + }) + + test('should take user to Linktree page from footer', async ({ footerLayout }) => { + const res = await clickAndGoToPage(footerLayout, footerLayout.linktreeLinkFooter) + await expect(res).toHaveURL(CUHACKING_2025_LINKTREE_URL) + }) + + test('should contain Figma link in footer', async ({ footerLayout }) => { + await expect(footerLayout.figmaLinkFooter).toBeVisible() + }) + + test('should take user to Figma page from footer', async ({ footerLayout }) => { + const res = await clickAndGoToPage(footerLayout, footerLayout.figmaLinkFooter) + expect(res.url()).toMatch(new RegExp(`^${CUHACKING_2025_FIGMA_URL}`)) + }) + + test('should contain GitHub link in footer', async ({ footerLayout }) => { + await expect(footerLayout.githubLinkFooter).toBeVisible() + }) + + test('should take user to GitHub page from footer', async ({ footerLayout }) => { + const res = await clickAndGoToPage(footerLayout, footerLayout.githubLinkFooter) + await expect(res).toHaveURL(CUHACKING_2025_GITHUB_REPOSITORY_URL) + }) + + test('should contain Email link in footer', async ({ footerLayout }) => { + await expect(footerLayout.emailLinkFooter).toBeVisible() + }) + + test('should take user to Email page from footer', async ({ footerLayout }) => { + const href = footerLayout.emailLinkFooter + await expect(href).toHaveAttribute('href', CUHACKING_2025_EMAIL_URL) + }) + + test('should contain Docs link in footer', async ({ footerLayout }) => { + await expect(footerLayout.docsLinkFooter).toBeVisible() + }) + + test('should take user to Docs page from footer', async ({ footerLayout }) => { + const res = await clickAndGoToPage(footerLayout, footerLayout.docsLinkFooter) + await expect(res).toHaveURL(CUHACKING_2025_DOCS_URL) + }) + }) +} diff --git a/libs/website-e2e/ui/footer/pom.ts b/libs/website-e2e/ui/footer/pom.ts new file mode 100644 index 00000000..498e3ac8 --- /dev/null +++ b/libs/website-e2e/ui/footer/pom.ts @@ -0,0 +1,38 @@ +import type { Locator, Page } from '@playwright/test' +import { CUHACKING_2025_WEBSITE_URL } from '@website-e2e/shared/constants' + +export class FooterLayout { + readonly page: Page + readonly cuHackingLogoFooter: Locator + readonly footerSection: Locator + + // --------LINKS-------- + readonly discordLinkFooter: Locator + readonly instagramLinkFooter: Locator + readonly linkedinLinkFooter: Locator + readonly linktreeLinkFooter: Locator + readonly figmaLinkFooter: Locator + readonly githubLinkFooter: Locator + readonly emailLinkFooter: Locator + readonly docsLinkFooter: Locator + + constructor(page: Page) { + this.page = page + // --------MOBILE + TABLET + DESKTOP-------- + this.footerSection = page.locator('footer') + + this.cuHackingLogoFooter = page.getByRole('img', { name: 'cuHacking logo' }).last() + this.discordLinkFooter = this.footerSection.getByRole('link', { name: 'Discord' }) + this.instagramLinkFooter = this.footerSection.getByRole('link', { name: 'Instagram' }) + this.linkedinLinkFooter = this.footerSection.getByRole('link', { name: 'LinkedIn' }) + this.linktreeLinkFooter = this.footerSection.getByRole('link', { name: 'Linktree' }) + this.figmaLinkFooter = this.footerSection.getByRole('link', { name: 'Figma' }) + this.githubLinkFooter = this.footerSection.getByRole('link', { name: 'GitHub' }) + this.emailLinkFooter = this.footerSection.getByRole('link', { name: 'Email' }) + this.docsLinkFooter = this.footerSection.getByRole('link', { name: 'Docs' }) + } + + async goto() { + await this.page.goto(CUHACKING_2025_WEBSITE_URL) + } +} diff --git a/libs/website-e2e/ui/navbar/constants.ts b/libs/website-e2e/ui/navbar/constants.ts new file mode 100644 index 00000000..60d5c22a --- /dev/null +++ b/libs/website-e2e/ui/navbar/constants.ts @@ -0,0 +1,6 @@ +import { CUHACKING_2025_WEBSITE_URL } from '@website-e2e/shared/constants' + +export const CUHACKING_WEBSITE_ABOUT_URL = `${CUHACKING_2025_WEBSITE_URL}/#about` +export const CUHACKING_WEBSITE_EVENTS_URL = `${CUHACKING_2025_WEBSITE_URL}/#events` +export const CUHACKING_WEBSITE_SPONSORS_URL = `${CUHACKING_2025_WEBSITE_URL}/#sponsors` +export const CUHACKING_WEBSITE_FAQ_URL = `${CUHACKING_2025_WEBSITE_URL}/#faq` diff --git a/libs/website-e2e/ui/navbar/navbar.ts b/libs/website-e2e/ui/navbar/navbar.ts new file mode 100644 index 00000000..faea2ff4 --- /dev/null +++ b/libs/website-e2e/ui/navbar/navbar.ts @@ -0,0 +1,182 @@ +import { test as base, expect } from '@playwright/test' +import { CUHACKING_2025_DISCORD_URL, CUHACKING_2025_DOCS_URL, CUHACKING_2025_EMAIL_URL, CUHACKING_2025_FIGMA_URL, CUHACKING_2025_GITHUB_REPOSITORY_URL, CUHACKING_2025_LINKTREE_URL, CUHACKING_2025_WEBSITE_URL } from '@website-e2e/shared/constants' +import { clickAndGoToPage } from '@website-e2e/shared/utils/click-and-go-to-page' +import { CUHACKING_WEBSITE_ABOUT_URL, CUHACKING_WEBSITE_EVENTS_URL, CUHACKING_WEBSITE_FAQ_URL, CUHACKING_WEBSITE_SPONSORS_URL } from './constants' +import { NavbarLayout } from './pom' + +// TODO: Split tests into separate files +// TODO: Add tests for navigation through website on mobile navbar + +const test = base.extend<{ navbarLayoutPage: NavbarLayout }>({ + navbarLayoutPage: async ({ page }, use) => { + const navbarLayoutPage = new NavbarLayout(page) + await navbarLayoutPage.goto() + // eslint-disable-next-line react-hooks/rules-of-hooks + await use(navbarLayoutPage) + }, +}) + +export function navbarMobileTabletDesktop() { + test.describe('NAVBAR - Common Mobile, Table, and Desktop layout elements', { + tag: '@smoke', + }, () => { + test('should return to home page when cuHacking logo on navbar is clicked', async ({ navbarLayoutPage }) => { + navbarLayoutPage.cuHackingLogoIconNav.click() + await expect(navbarLayoutPage.page).toHaveURL(CUHACKING_2025_WEBSITE_URL) + }) + test('should contain navbar', async ({ navbarLayoutPage }) => { + await expect(navbarLayoutPage.navbarSection).toBeVisible() + }) + }) +} + +export function navbarTabletDesktop() { + test.describe('NAVBAR - Common Tablet and Desktop links', { + tag: '@smoke', + }, () => { + test.beforeEach(async () => { + const device = test.info().project.name + if (device.includes('mobile')) { + test.skip() + } + }) + + test('should contain About link in navbar', async ({ navbarLayoutPage }) => { + await expect(navbarLayoutPage.aboutLink).toBeVisible() + }) + + test('should take user to About section from navbar', async ({ navbarLayoutPage }) => { + await navbarLayoutPage.aboutLink.click() + await expect(navbarLayoutPage.page).toHaveURL(CUHACKING_WEBSITE_ABOUT_URL) + }) + + test('should contain Events link in navbar', async ({ navbarLayoutPage }) => { + await expect(navbarLayoutPage.eventsLink).toBeVisible() + }) + + test('should take user to Events section from navbar', async ({ navbarLayoutPage }) => { + await navbarLayoutPage.eventsLink.click() + await expect(navbarLayoutPage.page).toHaveURL(CUHACKING_WEBSITE_EVENTS_URL) + }) + + test('should contain Sponsors link in navbar', async ({ navbarLayoutPage }) => { + await expect(navbarLayoutPage.sponsorsLink).toBeVisible() + }) + + test('should take user to Sponsors section from navbar', async ({ navbarLayoutPage }) => { + await navbarLayoutPage.sponsorsLink.click() + await expect(navbarLayoutPage.page).toHaveURL(CUHACKING_WEBSITE_SPONSORS_URL) + }) + + test('should contain FAQ link in navbar', async ({ navbarLayoutPage }) => { + await expect(navbarLayoutPage.faqLink).toBeVisible() + }) + + test('should take user to FAQ section from navbar', async ({ navbarLayoutPage }) => { + await navbarLayoutPage.faqLink.click() + await expect(navbarLayoutPage.page).toHaveURL(CUHACKING_WEBSITE_FAQ_URL) + }) + }) +} + +export function navbarMobile() { + test.describe('NAVBAR - Mobile links', { + tag: '@smoke', + }, () => { + test.beforeEach(async ({ navbarLayoutPage }) => { + const device = test.info().project.name + if (device.includes('desktop') || device.includes('tablet')) { + test.skip() + return + } + await navbarLayoutPage.hamburgerIcon.click() + }) + + test('should contain Discord link in navbar', async ({ navbarLayoutPage }) => { + await expect(navbarLayoutPage.discordLinkNavbar).toBeVisible() + }) + + test('should take user to Discord page from navbar', async ({ navbarLayoutPage }) => { + const res = await clickAndGoToPage(navbarLayoutPage, navbarLayoutPage.discordLinkNavbar) + await expect(res).toHaveURL(CUHACKING_2025_DISCORD_URL) + }) + + test('should contain Instagram link in navbar', async ({ navbarLayoutPage }) => { + await expect(navbarLayoutPage.instagramLinkNavbar).toBeVisible() + }) + + // TODO: Instagram requires auth + // test('should take user to Instagram page from Navbar', async ({ navbarLayoutPage }) => { + // const res = await clickAndGoToPage(navbarLayoutPage, navbarLayoutPage.instagramLinkNavbar) + // await expect(res).toHaveURL(CUHACKING_2025_INSTAGRAM_URL) + // }) + + test('should contain LinkedIn link in navbar', async ({ navbarLayoutPage }) => { + await expect(navbarLayoutPage.linkedinLinkNavbar).toBeVisible() + }) + + // TODO: Update when auth w/Playwright is implemented + // test('should take user to LinkedIn page from Navbar', async ({ navbarLayoutPage }) => { + // const res = await clickAndGoToPage(navbarLayoutPage, navbarLayoutPage.linkedinLinkNavbar) + // await expect(res).toHaveURL(CUHACKING_2025_LINKED_IN_URL) + // }) + + test('should contain Linktree link in navbar', async ({ navbarLayoutPage }) => { + await expect(navbarLayoutPage.linktreeLinkNavbar).toBeVisible() + }) + + test('should take user to Linktree page from navbar', async ({ navbarLayoutPage }) => { + const res = await clickAndGoToPage(navbarLayoutPage, navbarLayoutPage.linktreeLinkNavbar) + await expect(res).toHaveURL(CUHACKING_2025_LINKTREE_URL) + }) + + test('should contain Figma link in navbar', async ({ navbarLayoutPage }) => { + await expect(navbarLayoutPage.figmaLinkNavbar).toBeVisible() + }) + + test('should take user to Figma page from navbar', async ({ navbarLayoutPage }) => { + const res = await clickAndGoToPage(navbarLayoutPage, navbarLayoutPage.figmaLinkNavbar) + expect(res.url()).toMatch(new RegExp(`^${CUHACKING_2025_FIGMA_URL}`)) + }) + + test('should contain GitHub link in navbar', async ({ navbarLayoutPage }) => { + await expect(navbarLayoutPage.githubLinkNavbar).toBeVisible() + }) + + test('should take user to GitHub page from navbar', async ({ navbarLayoutPage }) => { + const res = await clickAndGoToPage(navbarLayoutPage, navbarLayoutPage.githubLinkNavbar) + await expect(res).toHaveURL(CUHACKING_2025_GITHUB_REPOSITORY_URL) + }) + + test('should contain Email link in navbar', async ({ navbarLayoutPage }) => { + await expect(navbarLayoutPage.emailLinkNavbar).toBeVisible() + }) + + test('should take user to Email page from navbar', async ({ navbarLayoutPage }) => { + const href = navbarLayoutPage.emailLinkNavbar + await expect(href).toHaveAttribute('href', CUHACKING_2025_EMAIL_URL) + }) + + test('should contain Docs link in navbar', async ({ navbarLayoutPage }) => { + await expect(navbarLayoutPage.docsLinkNavbar).toBeVisible() + }) + + test('should take user to Docs page from navbar', async ({ navbarLayoutPage }) => { + const res = await clickAndGoToPage(navbarLayoutPage, navbarLayoutPage.docsLinkNavbar) + await expect(res).toHaveURL(CUHACKING_2025_DOCS_URL) + }) + }) + test.describe('NAVBAR - Mobile elements', { + tag: '@smoke', + }, () => { + test.beforeEach(async () => { + const device = test.info().project.name + if (device.includes('desktop') || device.includes('tablet')) { + test.skip() + } + }) + test('should contain hamburger icon in navbar', async ({ navbarLayoutPage }) => { + await expect(navbarLayoutPage.hamburgerIcon).toBeVisible() + }) + }) +} diff --git a/libs/website-e2e/ui/navbar/pom.ts b/libs/website-e2e/ui/navbar/pom.ts new file mode 100644 index 00000000..b95dbf81 --- /dev/null +++ b/libs/website-e2e/ui/navbar/pom.ts @@ -0,0 +1,54 @@ +import type { Locator, Page } from '@playwright/test' +import { CUHACKING_2025_WEBSITE_URL } from '@website-e2e/shared/constants' + +export class NavbarLayout { + readonly page: Page + + // --------MOBLE-------- + readonly discordLinkNavbar: Locator + readonly instagramLinkNavbar: Locator + readonly linkedinLinkNavbar: Locator + readonly linktreeLinkNavbar: Locator + readonly figmaLinkNavbar: Locator + readonly githubLinkNavbar: Locator + readonly emailLinkNavbar: Locator + readonly docsLinkNavbar: Locator + readonly cuHackingLogoIconNav: Locator + readonly navbarSection: Locator + + // -------- MOBILE + TABLET + DESKTOP -------- + readonly aboutLink: Locator + readonly eventsLink: Locator + readonly sponsorsLink: Locator + readonly faqLink: Locator + readonly hamburgerIcon: Locator + + constructor(page: Page) { + this.page = page + + // --------MOBILE + TABLET + DESKTOP-------- + const navbar = page.locator('#navbar-social-media-links') + this.discordLinkNavbar = navbar.getByRole('link', { name: 'Discord' }) + this.instagramLinkNavbar = navbar.getByRole('link', { name: 'Instagram' }) + this.linkedinLinkNavbar = navbar.getByRole('link', { name: 'LinkedIn' }) + this.linktreeLinkNavbar = navbar.getByRole('link', { name: 'Linktree' }) + this.figmaLinkNavbar = navbar.getByRole('link', { name: 'Figma' }) + this.githubLinkNavbar = navbar.getByRole('link', { name: 'GitHub' }) + this.emailLinkNavbar = navbar.getByRole('link', { name: 'Email' }) + this.docsLinkNavbar = navbar.getByRole('link', { name: 'Docs' }) + this.cuHackingLogoIconNav = page.getByRole('link', { name: 'Return to homepage' }).first() + this.navbarSection = page.locator('#navbar') + + // ------MOBILE------- + this.aboutLink = page.getByRole('link', { name: 'About' }) + this.eventsLink = page.getByRole('link', { name: 'Events' }) + this.sponsorsLink = page.getByRole('link', { name: 'Sponsors' }).first() + this.faqLink = page.getByRole('link', { name: 'FAQ' }) + this.hamburgerIcon = page.getByRole('button', { name: 'Open Navigation Drawer' }) + } + + async goto() { + await this.page + .goto(CUHACKING_2025_WEBSITE_URL) + } +} diff --git a/libs/website-e2e/ui/sponsorship/constants.ts b/libs/website-e2e/ui/sponsorship/constants.ts new file mode 100644 index 00000000..f6ddc2c3 --- /dev/null +++ b/libs/website-e2e/ui/sponsorship/constants.ts @@ -0,0 +1 @@ +export const CUHACKING_2025_SPONSORSHIP_PACKAGE = 'https://drive.google.com/file/d/1mchFDm7D8lqmVO7Y8H3WOvE2yYwNWUOm/view' diff --git a/libs/website-e2e/ui/sponsorship/pom.ts b/libs/website-e2e/ui/sponsorship/pom.ts new file mode 100644 index 00000000..43b51a7b --- /dev/null +++ b/libs/website-e2e/ui/sponsorship/pom.ts @@ -0,0 +1,19 @@ +import type { Locator, Page } from '@playwright/test' +import { CUHACKING_2025_WEBSITE_URL } from '@website-e2e/shared/constants' + +export class SponsorshipSectionLayout { + readonly sponsorshipPackage: Locator + readonly sponsorsSection: Locator + + readonly page: Page + constructor(page: Page) { + this.page = page + this.sponsorsSection = page.locator('#sponsors') + this.sponsorshipPackage = page.getByRole('link', { name: 'Sponsorship Package' }) + } + + async goto() { + await this.page + .goto(CUHACKING_2025_WEBSITE_URL) + } +} diff --git a/libs/website-e2e/ui/sponsorship/sponsorship.ts b/libs/website-e2e/ui/sponsorship/sponsorship.ts new file mode 100644 index 00000000..6d4790b3 --- /dev/null +++ b/libs/website-e2e/ui/sponsorship/sponsorship.ts @@ -0,0 +1,33 @@ +import { test as base, expect } from '@playwright/test' +import { CUHACKING_2025_SPONSORSHIP_PACKAGE } from './constants' +import { SponsorshipSectionLayout } from './pom' + +const test = base.extend<{ sponsorshipSectionLayout: SponsorshipSectionLayout }>({ + sponsorshipSectionLayout: async ({ page }, use) => { + const sponsorshipSectionLayout = new SponsorshipSectionLayout(page) + sponsorshipSectionLayout.goto() + // eslint-disable-next-line react-hooks/rules-of-hooks + await use(sponsorshipSectionLayout) + }, +}) +export function sponsorsMobileTabletDesktop() { + test.describe('SPONSORS - Common MOBILE, TABLE, and DESKTOP elements', { + tag: '@smoke', + }, () => { + test('should contain sponsors section', async ({ sponsorshipSectionLayout }) => { + await expect(sponsorshipSectionLayout.sponsorsSection).toBeVisible() + }) + test('should contain a sponsorship package', async ({ sponsorshipSectionLayout }) => { + await expect(sponsorshipSectionLayout.sponsorshipPackage).toBeVisible() + }) + }) + + test.describe('SPONSORS - Common MOBILE, TABLET, and DESKTOP links', { + tag: '@smoke', + }, () => { + test('should take user to sponsorship package when Sponsorship Package link is clicked', async ({ sponsorshipSectionLayout }) => { + await sponsorshipSectionLayout.sponsorshipPackage.click() + await expect(sponsorshipSectionLayout.page).toHaveURL(CUHACKING_2025_SPONSORSHIP_PACKAGE) + }) + }) +} diff --git a/libs/website-e2e/ui/welcome/pom.ts b/libs/website-e2e/ui/welcome/pom.ts new file mode 100644 index 00000000..4ed4d298 --- /dev/null +++ b/libs/website-e2e/ui/welcome/pom.ts @@ -0,0 +1,32 @@ +import type { Locator, Page } from '@playwright/test' +import { CUHACKING_2025_WEBSITE_URL } from '@website-e2e/shared/constants' + +export class WelcomeSectionLayout { + readonly page: Page + readonly welcomeSection: Locator + readonly discordLinkWelcome: Locator + readonly instagramLinkWelcome: Locator + readonly linkedinLinkWelcome: Locator + readonly linktreeLinkWelcome: Locator + readonly figmaLinkWelcome: Locator + readonly githubLinkWelcome: Locator + readonly emailLinkWelcome: Locator + readonly docsLinkWelcome: Locator + + constructor(page: Page) { + this.page = page + this.welcomeSection = page.locator('#welcome') + this.discordLinkWelcome = this.welcomeSection.getByRole('link', { name: 'Discord' }) + this.instagramLinkWelcome = this.welcomeSection.getByRole('link', { name: 'Instagram' }) + this.linkedinLinkWelcome = this.welcomeSection.getByRole('link', { name: 'LinkedIn' }) + this.linktreeLinkWelcome = this.welcomeSection.getByRole('link', { name: 'Linktree' }) + this.figmaLinkWelcome = this.welcomeSection.getByRole('link', { name: 'Figma' }) + this.githubLinkWelcome = this.welcomeSection.getByRole('link', { name: 'GitHub' }) + this.emailLinkWelcome = this.welcomeSection.getByRole('link', { name: 'Email' }) + this.docsLinkWelcome = this.welcomeSection.getByRole('link', { name: 'Docs' }) + } + + async goto() { + await this.page.goto(CUHACKING_2025_WEBSITE_URL) + } +} diff --git a/libs/website-e2e/ui/welcome/welcome.ts b/libs/website-e2e/ui/welcome/welcome.ts new file mode 100644 index 00000000..a65437cc --- /dev/null +++ b/libs/website-e2e/ui/welcome/welcome.ts @@ -0,0 +1,99 @@ +import { test as base, expect } from '@playwright/test' +import { CUHACKING_2025_DISCORD_URL, CUHACKING_2025_DOCS_URL, CUHACKING_2025_EMAIL_URL, CUHACKING_2025_FIGMA_URL, CUHACKING_2025_GITHUB_REPOSITORY_URL, CUHACKING_2025_LINKTREE_URL } from '@website-e2e/shared/constants' +import { clickAndGoToPage } from '@website-e2e/shared/utils/click-and-go-to-page' +import { WelcomeSectionLayout } from './pom' + +const test = base.extend<{ welcomeSectionLayout: WelcomeSectionLayout }>({ + welcomeSectionLayout: async ({ page }, use) => { + const welcomeSectionLayout = new WelcomeSectionLayout(page) + await welcomeSectionLayout.goto() + // eslint-disable-next-line react-hooks/rules-of-hooks + await use(welcomeSectionLayout) + }, +}) +export function welcomeMobileTabletDesktop() { + test.describe('WELCOME SECTION - Common Mobile + Tablet + Desktop Layout Elements', { + tag: '@smoke', + }, () => { + test('should contain welcome section', async ({ welcomeSectionLayout }) => { + await expect(welcomeSectionLayout.welcomeSection).toBeVisible() + }) + }) + test.describe('WELCOME SECTION - Common Mobile + Tablet + Desktop Platform Links', { + tag: '@smoke', + }, () => { + test('should contain Discord link in welcome section', async ({ welcomeSectionLayout }) => { + await expect(welcomeSectionLayout.discordLinkWelcome).toBeVisible() + }) + + test('should take user to Discord page from welcome section', async ({ welcomeSectionLayout }) => { + const res = await clickAndGoToPage(welcomeSectionLayout, welcomeSectionLayout.discordLinkWelcome) + await expect(res).toHaveURL(CUHACKING_2025_DISCORD_URL) + }) + + test('should contain Instagram link in welcome section', async ({ welcomeSectionLayout }) => { + await expect(welcomeSectionLayout.instagramLinkWelcome).toBeVisible() + }) + + // TODO: Instagram requires auth + // test('should take user to Instagram page from welcome section', async ({ welcomeSectionLayout }) => { + // const res = await clickAndGoToPage(welcomeSectionLayout, welcomeSectionLayout.instagramLinkWelcome) + // await expect(res).toHaveURL(CUHACKING_2025_INSTAGRAM_URL) + // }) + + test('should contain LinkedIn link in welcome section', async ({ welcomeSectionLayout }) => { + await expect(welcomeSectionLayout.linkedinLinkWelcome).toBeVisible() + }) + + // TODO: LinkedIn requires auth + // test('should take user to LinkedIn page from welcome section', async ({ welcomeSectionLayout }) => { + // const res = await clickAndGoToPage(welcomeSectionLayout, welcomeSectionLayout.linkedinLinkWelcome) + // await expect(res).toHaveURL(CUHACKING_2025_LINKED_IN_URL) + // }) + + test('should contain Linktree link in welcome section', async ({ welcomeSectionLayout }) => { + await expect(welcomeSectionLayout.linktreeLinkWelcome).toBeVisible() + }) + + test('should take user to Linktree page from welcome section', async ({ welcomeSectionLayout }) => { + const res = await clickAndGoToPage(welcomeSectionLayout, welcomeSectionLayout.linktreeLinkWelcome) + await expect(res).toHaveURL(CUHACKING_2025_LINKTREE_URL) + }) + + test('should contain Figma link in welcome section', async ({ welcomeSectionLayout }) => { + await expect(welcomeSectionLayout.figmaLinkWelcome).toBeVisible() + }) + + test('should take user to Figma page from welcome section', async ({ welcomeSectionLayout }) => { + const res = await clickAndGoToPage(welcomeSectionLayout, welcomeSectionLayout.figmaLinkWelcome) + expect(res.url()).toMatch(new RegExp(`^${CUHACKING_2025_FIGMA_URL}`)) + }) + + test('should contain GitHub link in welcome section', async ({ welcomeSectionLayout }) => { + await expect(welcomeSectionLayout.githubLinkWelcome).toBeVisible() + }) + + test('should take user to GitHub page from welcome section', async ({ welcomeSectionLayout }) => { + const res = await clickAndGoToPage(welcomeSectionLayout, welcomeSectionLayout.githubLinkWelcome) + await expect(res).toHaveURL(CUHACKING_2025_GITHUB_REPOSITORY_URL) + }) + + test('should contain Email link in welcome section', async ({ welcomeSectionLayout }) => { + await expect(welcomeSectionLayout.emailLinkWelcome).toBeVisible() + }) + + test('should take user to Email page from welcome section', async ({ welcomeSectionLayout }) => { + const href = welcomeSectionLayout.emailLinkWelcome + await expect(href).toHaveAttribute('href', CUHACKING_2025_EMAIL_URL) + }) + + test('should contain Docs link in welcome section', async ({ welcomeSectionLayout }) => { + await expect(welcomeSectionLayout.docsLinkWelcome).toBeVisible() + }) + + test('should take user to Docs page from welcome section', async ({ welcomeSectionLayout }) => { + const res = await clickAndGoToPage(welcomeSectionLayout, welcomeSectionLayout.docsLinkWelcome) + await expect(res).toHaveURL(CUHACKING_2025_DOCS_URL) + }) + }) +} diff --git a/libs/website/feature/events/ui/event-item/event-item.tsx b/libs/website/feature/events/ui/event-item/event-item.tsx index 0b315da4..c2a78949 100644 --- a/libs/website/feature/events/ui/event-item/event-item.tsx +++ b/libs/website/feature/events/ui/event-item/event-item.tsx @@ -1,11 +1,15 @@ import type { Event } from '../../types/event' -import { Button } from '@cuhacking/shared/ui/src/cuHacking/components/button' -import { GlassmorphicCard } from '@cuhacking/shared/ui/src/cuHacking/components/glassmorphic-card' -import { Separator } from '@cuhacking/shared/ui/src/cuHacking/components/separator' -import { TerminalText } from '@cuhacking/shared/ui/src/cuHacking/components/terminal-text' -import { getDatePostfix, getDayOfMonth, getMonth } from '@cuhacking/shared/utils/date.helpers' +import { Button } from '@cuhacking/shared/ui/button' +import { GlassmorphicCard } from '@cuhacking/shared/ui/glassmorphic-card' +import { Separator } from '@cuhacking/shared/ui/separator' +import { TerminalText } from '@cuhacking/shared/ui/terminal-text' +import { cn } from '@cuhacking/shared/utils/cn' +import { + getDatePostfix, + getDayOfMonth, + getMonth, +} from '@cuhacking/shared/utils/date.helpers' import { Link } from '@remix-run/react' -import { cn } from '@shadcn/lib/utils' import React from 'react' interface EventProps { @@ -17,20 +21,34 @@ export function EventItem({ event, className }: EventProps) { const day = getDayOfMonth(date) const month = getMonth(date) const datePostfix = getDatePostfix(date) - const buttonMessage = event.status === 'upcoming' ? 'REGISTER NOW' : 'VIEW PHOTOS' + const buttonMessage + = event.status === 'upcoming' ? 'REGISTER NOW' : 'VIEW PHOTOS' + const ariaLabel = event.status === 'upcoming' ? `Register for ${event.title}` : `View photos from ${event.title}` return ( - +
- +
- - + +

{event.title}

@@ -41,7 +59,11 @@ export function EventItem({ event, className }: EventProps) { {event.status !== 'in-progress' ? (
- +
) : null} diff --git a/libs/website/feature/events/ui/event-presenter/event-presenter.tsx b/libs/website/feature/events/ui/event-presenter/event-presenter.tsx index ef557f81..d7f65e3a 100644 --- a/libs/website/feature/events/ui/event-presenter/event-presenter.tsx +++ b/libs/website/feature/events/ui/event-presenter/event-presenter.tsx @@ -1,6 +1,5 @@ import type { Event } from '../../types/event' -import { GlassmorphicCard } from '@cuhacking/shared/ui/src/cuHacking/components/glassmorphic-card' -import React from 'react' +import { GlassmorphicCard } from '@cuhacking/shared/ui/glassmorphic-card' import { EventItem } from '../event-item/event-item' interface EventContainerProps { @@ -10,7 +9,7 @@ interface EventContainerProps { export function EventPresenter({ events }: EventContainerProps) { return ( -

+

EVENTS

diff --git a/libs/website/feature/events/ui/event.section.tsx b/libs/website/feature/events/ui/event.section.tsx index 0d6dc6ec..152744d2 100644 --- a/libs/website/feature/events/ui/event.section.tsx +++ b/libs/website/feature/events/ui/event.section.tsx @@ -1,15 +1,19 @@ -import React from 'react' -import { EVENT_CONSTANTS } from '../constants/event.constants.ts' -import { EventPresenter } from './event-presenter/event-presenter' +/* import { SplineComponent } from '@website/shared/ui/spline/spline-component'; */ +import { EVENT_CONSTANTS } from '../constants/event.constants.ts'; +import { EventPresenter } from './event-presenter/event-presenter'; export function EventSection() { - return ( -
-
-
- -
-
-
- ) + return ( +
+
+
+ +
+
+ {/* */} +
+ ); } diff --git a/libs/website/feature/faq/constants/faq.constants.ts b/libs/website/feature/faq/constants/faq.constants.ts deleted file mode 100644 index d3264020..00000000 --- a/libs/website/feature/faq/constants/faq.constants.ts +++ /dev/null @@ -1,26 +0,0 @@ -const questions = [ - { - question: 'What is an FAQ?', - answer: 'FAQ stands for Frequently Asked Questions.', - }, - { - question: 'How do I use Storybook?', - answer: 'Storybook is a development environment for UI components.', - }, - { - question: 'What is React?', - answer: 'React is a JavaScript library for building user interfaces, primarily for single-page applications.', - }, - { - question: 'How do I install dependencies in a project?', - answer: 'You can install dependencies using package managers like npm or yarn, with commands like `npm install` or `yarn install`.', - }, - { - question: 'What is a pull request?', - answer: 'A pull request is a way of submitting changes to a repository, usually after creating a branch with new features or bug fixes.', - }, -] - -export const FAQ_CONSTANTS = { - QUESTIONS: questions, -} diff --git a/libs/website/feature/faq/index.ts b/libs/website/feature/faq/index.ts deleted file mode 100644 index 0defb3de..00000000 --- a/libs/website/feature/faq/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { FAQSection } from './ui/faq.section' -export { FAQPresenter } from './ui/faq-presenter/faq.presenter' diff --git a/libs/website/feature/faq/ui/faq-item/faq-item.tsx b/libs/website/feature/faq/ui/faq-item/faq-item.tsx deleted file mode 100644 index 2702c662..00000000 --- a/libs/website/feature/faq/ui/faq-item/faq-item.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { AccordionContent, AccordionItem, AccordionTrigger } from '@cuhacking/shared/ui/src/cuHacking/components/accordion' -import { TerminalText } from '@cuhacking/shared/ui/src/cuHacking/components/terminal-text' -import React from 'react' - -interface FAQItemProps { - question: string - answer: string -} - -export function FAQItem({ question, answer }: FAQItemProps) { - return ( - - -

- faq( - {question} - ) -

-
- - {answer} - -
- ) -} diff --git a/libs/website/feature/faq/ui/faq-presenter/faq.presenter.stories.tsx b/libs/website/feature/faq/ui/faq-presenter/faq.presenter.stories.tsx deleted file mode 100644 index ab7657bf..00000000 --- a/libs/website/feature/faq/ui/faq-presenter/faq.presenter.stories.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react' -import { FAQPresenter } from './faq.presenter' - -const meta = { - title: 'Website/FAQ Presenter', - component: FAQPresenter, - tags: ['autodocs'], - args: { - questions: [ - { - question: 'What is an FAQ?', - answer: 'FAQ stands for Frequently Asked Questions.', - }, - { - question: 'How do I use Storybook?', - answer: 'Storybook is a development environment for UI components.', - }, - ], - }, - argTypes: { - questions: { - control: { - type: 'object', - }, - }, - }, -} satisfies Meta - -export default meta -type Story = StoryObj - -export const CustomFAQs: Story = { - args: { - questions: [ - { - question: 'What is the purpose of an FAQ?', - answer: 'The purpose of an FAQ is to provide quick answers to common questions.', - }, - { - question: 'How do I contribute to this project?', - answer: 'You can contribute by opening pull requests and submitting issues.', - }, - ], - }, -} - -export const LongFAQs: Story = { - args: { - questions: [ - { - question: 'What are the benefits of using this FAQ system in a project?', - answer: 'This FAQ system allows users to find answers to their most frequently asked questions without needing to contact support or search through documentation.', - }, - { - question: 'How do I use the accordion component in my React project?', - answer: 'You can use the accordion component by importing it from our component library, passing the necessary props such as "type" and "collapsible", and wrapping your FAQ items inside it.', - }, - ], - }, -} diff --git a/libs/website/feature/faq/ui/faq-presenter/faq.presenter.tsx b/libs/website/feature/faq/ui/faq-presenter/faq.presenter.tsx deleted file mode 100644 index 9f551000..00000000 --- a/libs/website/feature/faq/ui/faq-presenter/faq.presenter.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Accordion } from '@cuhacking/shared/ui/src/cuHacking/components/accordion' -import { GlassmorphicCard } from '@cuhacking/shared/ui/src/cuHacking/components/glassmorphic-card' -import React from 'react' -import { FAQItem } from '../faq-item/faq-item' - -interface FAQPresenterProps { - questions: { question: string, answer: string }[] -} - -export function FAQPresenter({ questions }: FAQPresenterProps) { - return ( - -

FAQ

- - {questions.map(({ question, answer }) => ( - - ))} - -
- ) -} diff --git a/libs/website/feature/introduction/index.ts b/libs/website/feature/introduction/index.ts deleted file mode 100644 index f73db0cc..00000000 --- a/libs/website/feature/introduction/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { MissionSection } from './ui/mission/mission.section' -export { WelcomeSection } from './ui/welcome/welcome.section' diff --git a/libs/website/feature/introduction/ui/mission/mission.section.tsx b/libs/website/feature/introduction/ui/mission/mission.section.tsx deleted file mode 100644 index 7d0f2b74..00000000 --- a/libs/website/feature/introduction/ui/mission/mission.section.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' -import { MISSION_CONSTANTS } from '../../constants/mission.constants' -import { Mission } from './mission' -import { SplineMission } from './spline-mission' - -export function MissionSection() { - return ( -
- -
-
- -
-
-
- ) -} diff --git a/libs/website/feature/introduction/ui/mission/mission.stories.tsx b/libs/website/feature/introduction/ui/mission/mission.stories.tsx deleted file mode 100644 index 47e62142..00000000 --- a/libs/website/feature/introduction/ui/mission/mission.stories.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react' -import asciiLogo from '@website/assets/ascii-art/logos/cuhacking-1.svg' -import { Mission } from './mission' - -const meta: Meta = { - title: 'cuHacking Design System/Mission', - component: Mission, - tags: ['autodocs'], - parameters: { - layout: 'centered', - }, - argTypes: { - logo: { - control: 'text', - }, - }, -} - -export default meta - -type Story = StoryObj - -export const DefaultMission: Story = { args: { logo: asciiLogo.src } } diff --git a/libs/website/feature/introduction/ui/mission/spline-mission.tsx b/libs/website/feature/introduction/ui/mission/spline-mission.tsx deleted file mode 100644 index 80004001..00000000 --- a/libs/website/feature/introduction/ui/mission/spline-mission.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React, { Suspense } from 'react' - -const Spline = React.lazy(() => import('@splinetool/react-spline')) - -export function SplineMission() { - return ( - Loading...
}> -
- -
- - ) -} diff --git a/libs/website/feature/introduction/ui/welcome/spline-welcome.tsx b/libs/website/feature/introduction/ui/welcome/spline-welcome.tsx deleted file mode 100644 index 9a43f138..00000000 --- a/libs/website/feature/introduction/ui/welcome/spline-welcome.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React, { Suspense } from 'react' - -const Spline = React.lazy(() => import('@splinetool/react-spline')) - -export function SplineWelcome() { - return ( - Loading...}> - - - ) -} diff --git a/libs/website/feature/introduction/ui/welcome/welcome.section.tsx b/libs/website/feature/introduction/ui/welcome/welcome.section.tsx deleted file mode 100644 index f5ce42bc..00000000 --- a/libs/website/feature/introduction/ui/welcome/welcome.section.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' -import { WELCOME_CONSTATNTS } from '../../constants/welcome.constants' -import { SplineWelcome } from './spline-welcome' -import { Welcome } from './welcome' - -export function WelcomeSection() { - return ( -
- -
-
- -
-
-
- ) -} diff --git a/libs/website/feature/sponsorship/index.ts b/libs/website/feature/sponsorship/index.ts deleted file mode 100644 index 57f019df..00000000 --- a/libs/website/feature/sponsorship/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SponsorshipSection } from './ui/sponsorship-section' diff --git a/libs/website/feature/sponsorship/ui/sponsor-presenter/sponsor-item.tsx b/libs/website/feature/sponsorship/ui/sponsor-presenter/sponsor-item.tsx deleted file mode 100644 index f3c89c52..00000000 --- a/libs/website/feature/sponsorship/ui/sponsor-presenter/sponsor-item.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import type { Sponsor } from '../../types/sponsorship' -import { GlassmorphicCard } from '@cuhacking/shared/ui/src/cuHacking/components/glassmorphic-card' -import { cn } from '@shadcn/lib/utils' -import { cva } from 'class-variance-authority' -import React from 'react' - -interface SponsorPresenterProps { - sponsor: Sponsor - isPresent: boolean -} - -const sponsorPresenterVariation = cva( - 'p-2.5 hover:scale-105 transition-transform', - { - variants: { - isPresent: { - true: 'h-32 min-w-24', - false: 'h-24 min-w-16', - }, - }, - }, -) -export function SponsorItem({ sponsor, isPresent }: SponsorPresenterProps) { - return ( - - - {`${sponsor.name} - - - ) -} diff --git a/libs/website/feature/sponsorship/ui/sponsorship-presenter/sponsorship.presenter.tsx b/libs/website/feature/sponsorship/ui/sponsorship-presenter/sponsorship.presenter.tsx deleted file mode 100644 index 477fdf78..00000000 --- a/libs/website/feature/sponsorship/ui/sponsorship-presenter/sponsorship.presenter.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { Sponsor } from './sponsorship.model' -import { Button } from '@cuhacking/shared/ui/src/cuHacking/components/button' -import { GlassmorphicCard } from '@cuhacking/shared/ui/src/cuHacking/components/glassmorphic-card' -import { TerminalText } from '@cuhacking/shared/ui/src/cuHacking/components/terminal-text' -import React from 'react' -import { SponsorPresenter } from '../sponsor-presenter/sponsor-presenter' - -interface SponsorshipContainerProps { - text: { content: string, isCallToAction: boolean }[] - sponsors: { present: Sponsor[], past: Sponsor[] } -} -export function SponsorshipPresenter({ text, sponsors }: SponsorshipContainerProps) { - return ( - -

Sponsorship

-
- {text.map(({ content, isCallToAction }) => ( -

{content}

- ))} -
-
- - -
-
- { sponsors.present.length ? : null} - { sponsors.past.length ? : null} -
-
- ) -} diff --git a/libs/website/feature/stats/index.ts b/libs/website/feature/stats/index.ts deleted file mode 100644 index cbf6be99..00000000 --- a/libs/website/feature/stats/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { STATS_CONSTANTS } from './constants/stats.constants' -export { StatItem } from './ui/stat-item/stat-item' -export { StatSection } from './ui/stats.section' -export { StatPresenter } from './ui/stats-presenter/stats.presenter' diff --git a/libs/website/layouts/base.tsx b/libs/website/layouts/base.tsx index 4b3a70f8..fa33c242 100644 --- a/libs/website/layouts/base.tsx +++ b/libs/website/layouts/base.tsx @@ -1,23 +1,27 @@ -import { FOOTER_CONSTANTS, FooterPresenter, NAVBAR_CONSTANTS, NavbarPresenter } from '@website/shared/ui/navigation' -import React from 'react' -import { ClientOnly } from 'remix-utils/client-only' - -export function Layout({ children }) { +import { + FOOTER_CONSTANTS, + FooterPresenter, + NAVBAR_CONSTANTS, + NavbarContainer, +} from '@website/shared/ui/navigation' +import { ReactNode } from 'react' +export function Layout({ children }: { children: ReactNode }) { return ( <> - - {() => ( - - )} - +
{children}
- + ) } diff --git a/libs/website/pages/home.tsx b/libs/website/pages/home.tsx index 76a3302a..56baaa36 100644 --- a/libs/website/pages/home.tsx +++ b/libs/website/pages/home.tsx @@ -1,9 +1,8 @@ -import React from 'react' -import { EventSection } from '../feature/events' -import { FAQSection } from '../feature/faq' -import { MissionSection, WelcomeSection } from '../feature/introduction' -import { SponsorshipSection } from '../feature/sponsorship' -import { Layout } from '../layouts/base' +import { EventSection } from '@website/feature/events' +import { Layout } from '@website/layouts/base' +import { FAQSection } from '@website/ui/faq' +import { MissionSection, WelcomeSection } from '@website/ui/introduction' +import { SponsorshipSection } from '@website/ui/sponsorship' export function Home() { return ( diff --git a/libs/website/project.json b/libs/website/project.json index b8371e0e..6e4cee3c 100644 --- a/libs/website/project.json +++ b/libs/website/project.json @@ -1,5 +1,5 @@ { - "name": "website-lib", + "name": "libs/website", "$schema": "../../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "libs/website/", "projectType": "library", diff --git a/libs/website/shared/ui/navigation/footer/constants/footer.constants.ts b/libs/website/shared/ui/navigation/footer/constants/footer.constants.ts index 20a2d82d..2284e7a2 100644 --- a/libs/website/shared/ui/navigation/footer/constants/footer.constants.ts +++ b/libs/website/shared/ui/navigation/footer/constants/footer.constants.ts @@ -1,3 +1,4 @@ +import type { Media } from '@cuhacking/shared/types' import discord_white from '@cuhacking/shared/assets/icons/socials/discord-white-1.svg' import docs_white from '@cuhacking/shared/assets/icons/socials/docs-white-1.svg' import email_white from '@cuhacking/shared/assets/icons/socials/email-white-1.svg' @@ -8,61 +9,71 @@ import linkedin_white from '@cuhacking/shared/assets/icons/socials/linkedin-whit import linktree_white from '@cuhacking/shared/assets/icons/socials/linktree-white-1.svg' import cuHackingLogo from '@cuhacking/shared/assets/logos/cuHacking/cuhacking-logo-1.svg' -const socials = [ +const socials: { link: string, name: string, media: Media }[] = [ { - link: 'https://github.com', + link: 'https://www.instagram.com/cuhacking/', + name: 'Instagram', media: { - src: github_white, - alt: 'GitHub', + src: instagram_white, + alt: 'Instagram', }, }, { - link: 'https://instagram.com', + + link: 'https://github.com/cuhacking/2025', + name: 'Github', media: { - src: instagram_white, - alt: 'Instagram', + src: github_white, + alt: 'GitHub', }, }, { - link: 'https://linkedin.com', + + link: 'https://discord.com/invite/h2cQqF9aZf', + name: 'Discord', media: { - src: linkedin_white, - alt: 'LinkedIn', + src: discord_white, + alt: 'Discord', }, }, { - link: 'https://linktr.ee', + link: 'https://docs.cuhacking.ca/', + name: 'Docs', media: { - src: linktree_white, - alt: 'Linktree', + src: docs_white, + alt: 'Documentation', }, }, { - link: 'mailto:example@email.com', + link: 'https://www.figma.com/design/wc1JOWR48tBNkjcjwY3AzB/%E2%8C%A8%EF%B8%8F-cuHacking-Design-System?node-id=0-1&t=YTR1ET4Qw1wG1cjz-1', + name: 'Figma', media: { - src: email_white, - alt: 'Email', + src: figma_white, + alt: 'Figma', }, }, { - link: 'https://discord.com', + link: 'mailto:info@cuhacking.ca', + name: 'Email', media: { - src: discord_white, - alt: 'Discord', + src: email_white, + alt: 'Email', }, }, { - link: 'https://docs.com', + link: 'https://ca.linkedin.com/company/cuhacking', + name: 'LinkedIn', media: { - src: docs_white, - alt: 'Documentation', + src: linkedin_white, + alt: 'LinkedIn', }, }, { - link: 'https://figma.com', + link: 'https://linktr.ee/cuhacking_', + name: 'Linktree', media: { - src: figma_white, - alt: 'Figma', + src: linktree_white, + alt: 'Linktree', }, }, ] diff --git a/libs/website/shared/ui/navigation/footer/ui/footer.presenter.tsx b/libs/website/shared/ui/navigation/footer/ui/footer.presenter.tsx index ca068280..2becb623 100644 --- a/libs/website/shared/ui/navigation/footer/ui/footer.presenter.tsx +++ b/libs/website/shared/ui/navigation/footer/ui/footer.presenter.tsx @@ -1,4 +1,5 @@ import type { Media } from '@cuhacking/shared/types' +import { Link } from '@remix-run/react' import { Socials } from '@website/shared/ui/socials' import React from 'react' @@ -14,9 +15,13 @@ export function FooterPresenter({ logo, socials }: FooterProps) { return (