diff --git a/dev/dev.env.example b/dev/dev.env.example index 6439921b..b50fc353 100644 --- a/dev/dev.env.example +++ b/dev/dev.env.example @@ -1,7 +1,7 @@ # environment file to configure docker containers # postgres -POSTGRES_NAME= +POSTGRES_DB= POSTGRES_USER=postgres POSTGRES_PASSWORD= diff --git a/docker-compose.yml b/docker-compose.yml index 4cd01664..98514d9f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ services: volumes: - postgres_data:/lib/postgresql/data ports: - - "5434:5434" + - "5432:5432" healthcheck: test: [ "CMD-SHELL", "pg_isready" ] interval: 10s @@ -58,6 +58,9 @@ services: - action: sync path: ./frontend target: /usr/src/app + volumes: + - ./frontend:/usr/src/app + - /usr/src/app/node_modules volumes: postgres_data: {} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5e501b42..415eb361 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,6 +15,7 @@ "clsx": "^2.1.1", "react-hook-form": "^7.46.1", "react-popper": "^2.3.0", + "tailwind-merge": "^3.2.0", "vite-tsconfig-paths": "^4.3.1" }, "devDependencies": { @@ -9819,6 +9820,15 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tailwind-merge": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.2.0.tgz", + "integrity": "sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", @@ -17929,6 +17939,11 @@ "tslib": "^2.6.2" } }, + "tailwind-merge": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.2.0.tgz", + "integrity": "sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==" + }, "tailwindcss": { "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", diff --git a/frontend/package.json b/frontend/package.json index 5d0d6237..93939ffe 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -73,6 +73,7 @@ "clsx": "^2.1.1", "react-hook-form": "^7.46.1", "react-popper": "^2.3.0", + "tailwind-merge": "^3.2.0", "vite-tsconfig-paths": "^4.3.1" } } diff --git a/frontend/src/api_data/copData.ts b/frontend/src/api_data/copData.ts index 39be2bc5..e44635aa 100644 --- a/frontend/src/api_data/copData.ts +++ b/frontend/src/api_data/copData.ts @@ -9,6 +9,7 @@ import { interface copDatum { id: number; title: string; + subtitle: string; icon: React.ElementType; description: string; roles: string[]; @@ -18,6 +19,7 @@ const copData: copDatum[] = [ { id: 0, // will be replaced by useID when upgrade to React18+; see #200 title: "UI/UX", + subtitle: "UX Design and Writing", icon: CopIconUiux, description: "The User Interface/User Experience (UI/UX) Community of Practice (CoP) is a space for UI and UX designers and research professionals to share effective practices, and give and receive mentorship, set design and research standards, and to create guides for new projects. Recent meeting topics include how to create a professional online portfolio, a meet and greet with a Hack for LA alum who landed a job at Google, how to effectively network, and training in Figma.", @@ -31,6 +33,7 @@ const copData: copDatum[] = [ { id: 1, title: "Engineering", + subtitle: "Frontend and Backend", icon: CopIconEngineering, description: "The Engineering Community of Practice (CoP) is a space for developers to share effective practices and set development standards and give and receive mentorship. Recent meeting topics include career advancement strategy workshops and “tech talks” with discussions on architecture paradigms, testing, and new technology.", @@ -44,6 +47,7 @@ const copData: copDatum[] = [ { id: 2, title: "Data Science", + subtitle: "", icon: CopIconData, description: "The Data Science Community of Practice (CoP) is a space for data science professionals to discuss the current state of the field, share effective practices, give and receive mentorship, and to workshop projects. Recent meeting topics include reviewing popular tools for data analysis, using data science to improve Hack for LA workflows, and presenting research results to peers and leadership for feedback and mentoring.", @@ -57,6 +61,7 @@ const copData: copDatum[] = [ { id: 3, title: "Project/Product Management", + subtitle: "Planning and Organization", icon: CopIconProduct, description: "The Product Managers (PM) Community of Practice (CoP) is a space for product management professionals to share effective practices, and give and receive mentorship, set product management standards, and to create guides and templates for new projects. Recent meeting topics include a project management focused book club, discussing how to best manage knowledge and issues, and brainstorming solutions to various PM issues.", @@ -72,6 +77,7 @@ const copData: copDatum[] = [ { id: 4, title: "DevOps", + subtitle: "Dev and IT Operations", icon: CopIconOps, description: "The Operations (Ops) Community of Practice (CoP) is a space for operations professionals to discuss all areas of dev-ops, coordinate infrastructure improvement, and share effective practices, and give and receive mentorship. Recent meeting topics include improving AWS hosting, password vaults, and multi-tenant product architecture.", diff --git a/frontend/src/components/Buttons/_Button.scss b/frontend/src/components/Buttons/_Button.scss index 34908d93..ecdeb7d0 100644 --- a/frontend/src/components/Buttons/_Button.scss +++ b/frontend/src/components/Buttons/_Button.scss @@ -102,18 +102,18 @@ $theme-values: ( ), ), "primary-dark": ( - "background-color": $color-blue, - "color": $color-white, + "background-color": $color-white, + "color": $color-blue-dark, "focus": ( - "background-color": $color-blue-focused, + "background-color": $color-blue-dark-focused, ), "hover": ( - "background-color": $color-white, + "background-color": $color-blue-dark-hover, "box-shadow": 0 4px 4px rgb(0 0 0 / 20%), - "color": $color-charcoal, + "color": $color-white, ), "active": ( - "background-color": $color-blue-focused, + "background-color": $color-blue-dark-focused, ), ), ); diff --git a/frontend/src/components/Dialog/_Dialog.scss b/frontend/src/components/Dialog/_Dialog.scss deleted file mode 100644 index 4f2f0eab..00000000 --- a/frontend/src/components/Dialog/_Dialog.scss +++ /dev/null @@ -1,42 +0,0 @@ -// External Imports -@use "sass:map"; - -// Internal Imports -@use "../Basics" as *; - -/*********************************** -*** Dialog class for all dialogs *** -***********************************/ - -.dialog-backdrop { - background-color: rgb(0 0 0 / 40%); - height: 100%; - left: 0; - overflow: auto; - position: fixed; - top: 0; - width: 100%; - z-index: $z-dialog; -} - -.dialog-enter { - transform: translateY(-50%); - opacity: 0; -} - -.dialog-enter-active { - transform: translateY(0%); - opacity: 1; - transition-duration: 400ms; -} - -.dialog-exit { - transform: translateY(0%); - opacity: 1; -} - -.dialog-exit-active { - transform: translateY(50%); - opacity: 0; - transition-duration: 400ms; -} diff --git a/frontend/src/components/Navigation/ProgressBar.tsx b/frontend/src/components/Navigation/ProgressBar.tsx deleted file mode 100644 index 8bb75b81..00000000 --- a/frontend/src/components/Navigation/ProgressBar.tsx +++ /dev/null @@ -1,60 +0,0 @@ -// External Imports -import React, { Fragment, useId } from "react"; - -// Internal Imports -import { combineClasses, range } from "components/Utility/utils"; - -// Type declaration for props -interface ProgressBarProps { - addClass?: string; - label: string; - labelHidden?: boolean; - max?: 1 | 2 | 3 | 4; - value?: number; // Can only be 1, 2, 3, or 4 -} - -function ProgressBar({ - labelHidden = true, - max = 2, - value = 1, - ...props -}: ProgressBarProps) { - const ariaLabelledBy = useId(); - - return ( - - -
- {range(1, max).map((num, index) => { - return ( -
- ); - })} -
-
- ); -} - -export { ProgressBar }; diff --git a/frontend/src/components/Navigation/_ProgressBar.scss b/frontend/src/components/Navigation/_ProgressBar.scss deleted file mode 100644 index c43aa171..00000000 --- a/frontend/src/components/Navigation/_ProgressBar.scss +++ /dev/null @@ -1,26 +0,0 @@ -@use "../Basics" as *; - -$bars: 1, 2, 3, 4; -$bars-height: 12px; - -.progress-bar { - height: $bars-height; - width: 100%; -} - -@mixin progress-bar($num) { - background-color: $color-grey; - border-radius: 8px; - height: $bars-height; - width: calc(100% / #{$num}); - - &.active { - background-color: $color-blue-dark; - } -} - -@each $bar in $bars { - .progress-bar-#{$bar} { - @include progress-bar($bar); - } -} diff --git a/frontend/src/components/Navigation/_index.scss b/frontend/src/components/Navigation/_index.scss deleted file mode 100644 index 14058e4a..00000000 --- a/frontend/src/components/Navigation/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@forward "ProgressBar"; diff --git a/frontend/src/components/components.tsx b/frontend/src/components/components.tsx index 020180e0..9c3a9231 100644 --- a/frontend/src/components/components.tsx +++ b/frontend/src/components/components.tsx @@ -1,26 +1,21 @@ import { Button } from "./Buttons/Button"; import { IconButton } from "./Buttons/IconButton"; import { ScrollCarousel } from "./Carousel/ScrollCarousel"; -import { Dialog } from "./Dialog/Dialog"; import { Calendar } from "./Inputs/Calendar"; import { Checkbox } from "./Inputs/Checkbox"; import { Chip } from "./Inputs/Chip"; import { Dropdown, DropdownOption } from "./Inputs/Dropdown"; import { TextField } from "./Inputs/Textfield"; -import { ProgressBar } from "./Navigation/ProgressBar"; import { Notification } from "./Notification/Notification"; import { TransitionWrapper } from "./Transition/Wrapper"; import { ChevronScroll } from "./Scroll/ChevronScroll"; export { - // Buttons Button, IconButton, - // Carousel ScrollCarousel, ChevronScroll, // Dialog - Dialog, // Inputs Calendar, Checkbox, @@ -28,10 +23,6 @@ export { Dropdown, DropdownOption, TextField, - // Navigation - ProgressBar, - // Notification Notification, - // Transition TransitionWrapper, }; diff --git a/frontend/src/context/QualifiersContext.tsx b/frontend/src/context/QualifiersContext.tsx index b0b32b20..6056a6a8 100644 --- a/frontend/src/context/QualifiersContext.tsx +++ b/frontend/src/context/QualifiersContext.tsx @@ -6,6 +6,8 @@ type QualifiersType = { COPs: { [copName: string]: string[]; }; + selectedCOP?: string; + skills_matrix?: { [skill: string]: string }; // availabilityTimeSlots: string[]; }; @@ -53,6 +55,7 @@ export const QualifiersProvider: React.FC<{ children: ReactNode }> = ({ const [qualifiers, setQualifiers] = useState(initialState); const updateQualifiers = (newQualifiers: QualifiersType) => { + console.log("Updated Qualifiers:", newQualifiers); // Log the updated qualifiers TO DELETE setQualifiers(newQualifiers); }; diff --git a/frontend/src/index.scss b/frontend/src/index.scss index bcaa8896..cd4abad9 100644 --- a/frontend/src/index.scss +++ b/frontend/src/index.scss @@ -7,9 +7,7 @@ // Import component styles @use "@/components/Buttons" as *; @use "@/components/Carousel/ScrollCarousel"; -@use "@/components/Dialog/Dialog"; @use "@/components/Inputs" as *; -@use "@/components/Navigation" as *; @use "@/components/Notification/Notification"; @use "@/components/Transition/Wrapper"; @use "@/components/Scroll/ChevronScroll"; @@ -18,7 +16,6 @@ @use "@/pages/Demo/Demo"; @use "@/pages/NotFoundPage/NotFoundPage"; @use "@/pages/LandingPage" as *; -@use "@/pages/QualifierPage" as *; body { font-family: Roboto, Tahoma, Verdana, sans-serif; diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts new file mode 100644 index 00000000..c6a523ab --- /dev/null +++ b/frontend/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/frontend/src/pages/Authentication/page.tsx b/frontend/src/pages/Authentication/page.tsx index 04a5e5cc..bb0209d9 100644 --- a/frontend/src/pages/Authentication/page.tsx +++ b/frontend/src/pages/Authentication/page.tsx @@ -45,7 +45,7 @@ export default function AuthenticationPage() {
-
+
{pathname === "/login" && } {pathname === "/signup" && }
diff --git a/frontend/src/pages/LandingPage/LandingPageCop.tsx b/frontend/src/pages/LandingPage/LandingPageCop.tsx index 78d1ba1b..797966f6 100644 --- a/frontend/src/pages/LandingPage/LandingPageCop.tsx +++ b/frontend/src/pages/LandingPage/LandingPageCop.tsx @@ -2,15 +2,14 @@ import React, { useState, useEffect } from "react"; // Internal Imports -import { Button, Dialog } from "components/components"; +import { Button } from "components/components"; +import { Dialog, CircleCard, Typography } from "tw-components/index"; import { CopCard, InnerCopCard, InnerCopNavCard } from "./LandingPageCopCards"; import { copDatum, fetchAllCopData, fetchCopDataById, } from "../../api_data/copData"; -import { CircleCard } from "tw-components/CircleCard"; -import Typography from "tw-components/Typography"; function LandingPageCop() { const [isDialogOpen, setIsDialogOpen] = useState(false); @@ -69,7 +68,7 @@ function LandingPageCop() { onClose={() => { setIsDialogOpen(false); }} - addClass="flex-container justify-center align-center" + className="flex flex-col items-center justify-center" ariaLabel="Communities of Practice (COP)" > -

{title}

-

{children}

- - ); -} - -interface QualifierNavProps { - addClass?: string; - children?: React.ReactNode; -} - -function QualifierNav({ addClass, children }: QualifierNavProps) { - return ( -
- {children} -
- ); -} - -export { QualifierTitle, QualifierNav }; diff --git a/frontend/src/pages/QualifierPage/QualifierPageCalendar.tsx b/frontend/src/pages/QualifierPage/QualifierPageCalendar.tsx index d4001690..1575907c 100644 --- a/frontend/src/pages/QualifierPage/QualifierPageCalendar.tsx +++ b/frontend/src/pages/QualifierPage/QualifierPageCalendar.tsx @@ -1,8 +1,9 @@ // External Imports -import React, { Fragment, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; // Internal Imports +import Typography from "tw-components/Typography"; import { Dropdown, DropdownOption, @@ -10,7 +11,7 @@ import { IconButton, Button, } from "components/components"; -import { QualifierNav, QualifierTitle } from "./QualifierComponents"; +import { QualifierNav } from "./components/QualifierNav"; import { timezones } from "../../api_data/timezoneData"; import { iconArrowLeft } from "assets/images/images"; // import { useQualifiersContext } from "context/QualifiersContext"; @@ -21,12 +22,15 @@ function QualifierPageCalendar() { const navigate = useNavigate(); return ( - - +
+ + What is your weekly availability? + + Drag to select.   - +  = available - + - + navigate("../1", { relative: "path" })} + onClick={() => navigate("../2", { relative: "path" })} /> - +
); } @@ -61,7 +65,7 @@ function TimeZoneDropDown() { } }, []); return ( -
+
{ // console.log("Old Qualifiers:", qualifiers); const newQualifiers = { ...qualifiers, COPs: updatedCopQualifiers }; - console.log("New Qualifiers:", newQualifiers); + // console.log("New Qualifiers:", newQualifiers); updateQualifiers(newQualifiers); // Update qualifiers }; return ( - - + <> + + What type of Practice Area are you looking for? + + Select as many roles as you'd like to find opportunities in. - + +
{copData.map((cop, index) => { const cleanCopName = cop.title.replace(/\s+/g, "_"); @@ -167,13 +172,13 @@ const QualifierPageRoles: React.FC = () => {
{index < copData.length - 1 && ( -
+
)} ); })}
- + -
+ ); }; diff --git a/frontend/src/pages/QualifierPage/_QualifierComponents.scss b/frontend/src/pages/QualifierPage/_QualifierComponents.scss deleted file mode 100644 index aa5f529c..00000000 --- a/frontend/src/pages/QualifierPage/_QualifierComponents.scss +++ /dev/null @@ -1,15 +0,0 @@ -@use "@/components/Basics" as *; - -.qualifier-nav { - background-color: $color-white; - box-sizing: content-box; - bottom: 0; - position: sticky; - height: 100px; - opacity: 0.8; - width: 100%; - - &:hover { - opacity: 1; - } -} diff --git a/frontend/src/pages/QualifierPage/_QualifierPage.scss b/frontend/src/pages/QualifierPage/_QualifierPage.scss deleted file mode 100644 index be33262a..00000000 --- a/frontend/src/pages/QualifierPage/_QualifierPage.scss +++ /dev/null @@ -1,26 +0,0 @@ -@use "@/components/Basics" as *; - -// Main Page -.qualifier-content { - flex-wrap: nowrap; - width: fit-content; -} - -// Roles -.qroles-border { - color: $color-grey; -} - -// Calendar -.qcalendar-green-square { - background-color: $color-green; - border-radius: 2px; - display: inline-block; - height: 24px; - line-height: 30px; - width: 24px; -} - -.qcalendar-dropdown { - width: 100%; -} diff --git a/frontend/src/pages/QualifierPage/_index.scss b/frontend/src/pages/QualifierPage/_index.scss deleted file mode 100644 index a47dc78d..00000000 --- a/frontend/src/pages/QualifierPage/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@forward "QualifierComponents"; -@forward "QualifierPage"; diff --git a/frontend/src/pages/QualifierPage/components/ChipsSelection.tsx b/frontend/src/pages/QualifierPage/components/ChipsSelection.tsx new file mode 100644 index 00000000..45ea1ef3 --- /dev/null +++ b/frontend/src/pages/QualifierPage/components/ChipsSelection.tsx @@ -0,0 +1,40 @@ +import React from "react"; + +import Typography from "tw-components/Typography"; +import { Chip } from "components/components"; + +const tools: string[] = [ + "Figma", + "Adobe XD", + "Miro", + "Figjam", + "Github", + "UserTesting.com", + "Tailwind CSS", + "HTML", + "Optimal Workshop", + "JavaScript", + "CSS", + "Photoshop", + "ARIA", + "Illustrator", + "Lyssna", + "Web Content Accessibility (WCAG)", +]; + +function ChipsSelection() { + return ( +
+ + Technical Skills and Tools + +
+ {tools.map((tool) => { + return ; + })} +
+
+ ); +} + +export { ChipsSelection }; diff --git a/frontend/src/pages/QualifierPage/components/ProgressIndicator.tsx b/frontend/src/pages/QualifierPage/components/ProgressIndicator.tsx new file mode 100644 index 00000000..34f4e024 --- /dev/null +++ b/frontend/src/pages/QualifierPage/components/ProgressIndicator.tsx @@ -0,0 +1,60 @@ +import React from "react"; + +interface ProgressIndicatorProps { + currentPart: number; + totalParts: number; + title: string; + progressPercentage: number; // New prop +} + +export const ProgressIndicator: React.FC = ({ + currentPart, + totalParts, + title, + progressPercentage, +}) => { + // Ensure progressPercentage is clamped between 0 and 100 + const validProgressPercentage = Math.min( + Math.max(progressPercentage, 0), + 100, + ); + const strokeDashoffset = 62.8 - (62.8 * validProgressPercentage) / 100; + + return ( +
+ + + + +
+ + Part {currentPart} of {totalParts} + + {title} +
+
+ ); +}; diff --git a/frontend/src/pages/QualifierPage/components/QualifierNav.tsx b/frontend/src/pages/QualifierPage/components/QualifierNav.tsx new file mode 100644 index 00000000..9095b919 --- /dev/null +++ b/frontend/src/pages/QualifierPage/components/QualifierNav.tsx @@ -0,0 +1,25 @@ +// External Imports +import React from "react"; +import clsx from "clsx"; + +interface QualifierNavProps { + className?: string; + children?: React.ReactNode; +} + +function QualifierNav({ className, children }: QualifierNavProps) { + return ( +
+
+ {children} +
+
+ ); +} + +export { QualifierNav }; diff --git a/frontend/src/pages/QualifierPage/components/RadioButtonForm.tsx b/frontend/src/pages/QualifierPage/components/RadioButtonForm.tsx new file mode 100644 index 00000000..79c15eb2 --- /dev/null +++ b/frontend/src/pages/QualifierPage/components/RadioButtonForm.tsx @@ -0,0 +1,144 @@ +import React from "react"; + +// Internal Imports +import Typography from "tw-components/Typography"; + +interface RadioButtonFormProps { + onSkillSelect: (skill: string, level: string) => void; + selectedSkillsLevel: Record; +} + +const skills = [ + { + name: "User Research Methods", + description: "Interviews, surveys, and usability testing", + }, + { + name: "User Personas & Journey Mapping", + description: + "Developing representative user profiles and mapping user journeys", + }, + { + name: "Information Architecture", + description: + "E.g., creating site maps, navigation flows, or using card sorting", + }, + { + name: "Wireframing & Sketching", + description: "low-fidelity layouts to visualize structure and flow", + }, +]; + +function RadioButtonForm({ + onSkillSelect, + selectedSkillsLevel, +}: RadioButtonFormProps) { + return ( + + + + + + + + + + + + + + + {skills.map((skill) => ( + + ))} + +
+ UX Research and Strategy + + Experience Level +
+ 0-2 yrs + + 2-4 yrs + + 4+ yrs +
+ ); +} + +interface SkillRowProps { + skillName: string; + description: string; + onSkillSelect: (skill: string, level: string) => void; + selectedLevel?: string; +} + +function SkillRow({ + skillName, + description, + onSkillSelect, + selectedLevel, +}: SkillRowProps) { + return ( + + + {skillName} + + {description} + + + + onSkillSelect(skillName, "0-2yrs")} + /> + + + onSkillSelect(skillName, "2-4yrs")} + /> + + + onSkillSelect(skillName, "4+yrs")} + /> + + + ); +} + +interface RadioButtonProps { + value: string; + name: string; + checked?: boolean; + onChange: () => void; +} + +function RadioButton({ value, name, checked, onChange }: RadioButtonProps) { + return ( + + ); +} + +export { RadioButtonForm }; diff --git a/frontend/src/pages/QualifierPage/components/Stepper.tsx b/frontend/src/pages/QualifierPage/components/Stepper.tsx new file mode 100644 index 00000000..7138851a --- /dev/null +++ b/frontend/src/pages/QualifierPage/components/Stepper.tsx @@ -0,0 +1,91 @@ +// External Imports +import React from "react"; +import { useParams } from "react-router-dom"; + +// Internal Imports +import Typography from "tw-components/Typography"; +import { IconCheckMark } from "assets/images/images"; + +function Stepper() { + return ( +
+ Practice Area + Individual Skill Evaluation + Availability +
+ ); +} + +interface StepProps extends React.PropsWithChildren { + step: "1" | "2" | "3"; +} + +function Step({ children, step }: StepProps) { + let { page } = useParams(); + if (!page) page = "1"; + + const stepStatus = + step < page ? "complete" : page === step ? "active" : "pending"; + + return ( +
+ + {children} + +
+ {renderSwitch(stepStatus)} +
+
+ ); +} + +type Status = "complete" | "active" | "pending"; + +function renderSwitch(stepStatus: Status) { + switch (stepStatus) { + case "complete": + return ; + case "active": + return ; + case "pending": + return ; + default: + return <>; + } +} + +function CompleteStep() { + return ( + <> +
+
+ +
+
+ + ); +} + +function ActiveStep() { + return ( + <> +
+
+
+ + ); +} + +function PendingStep() { + return ( + <> +
+
+
+ + ); +} + +export { Stepper }; diff --git a/frontend/src/pages/QualifierPage/QualifierPage.tsx b/frontend/src/pages/QualifierPage/index.tsx similarity index 57% rename from frontend/src/pages/QualifierPage/QualifierPage.tsx rename to frontend/src/pages/QualifierPage/index.tsx index d8cda37e..63dda816 100644 --- a/frontend/src/pages/QualifierPage/QualifierPage.tsx +++ b/frontend/src/pages/QualifierPage/index.tsx @@ -3,16 +3,20 @@ import React from "react"; import { useParams } from "react-router-dom"; // Internal Imports -import { ProgressBar } from "components/components"; +import { Stepper } from "./components/Stepper"; import { QualifiersProvider } from "context/QualifiersContext"; -import { QualifierPageRoles } from "./QualifierPageRoles"; import { QualifierPageCalendar } from "./QualifierPageCalendar"; +import { QualifierPage1 } from "./pages/QualifierPage1"; +import { QualifierPage2 } from "./pages/QualifierPage2"; + function Content({ page }: { page: string }) { switch (page) { case "1": - return ; + return ; case "2": + return ; + case "3": return ; default: throw new Error("Page not found"); @@ -27,18 +31,12 @@ function QualifierPage() { return ( -
- -
-
- -
+
+ +
+
-
+
); } diff --git a/frontend/src/pages/QualifierPage/pages/QualifierPage1.tsx b/frontend/src/pages/QualifierPage/pages/QualifierPage1.tsx new file mode 100644 index 00000000..f9eca4e6 --- /dev/null +++ b/frontend/src/pages/QualifierPage/pages/QualifierPage1.tsx @@ -0,0 +1,114 @@ +// External Imports +import React, { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import clsx from "clsx"; + +// Internal Imports +import { copDatum, fetchAllCopData } from "api_data/copData"; +import Typography from "tw-components/Typography"; +import { Button } from "tw-components/Buttons"; +import { QualifierNav } from "../components/QualifierNav"; +import { IconCheckMark } from "assets/images/images"; +import { useQualifiersContext } from "context/QualifiersContext"; + +function QualifierPage1() { + const navigate = useNavigate(); + const { copData, qualifiers, updateQualifiers } = useQualifiersContext(); + + useEffect(() => { + fetchAllCopData(); + }, []); + + const handleSelectCOP = ( + e: React.MouseEvent, + cop: copDatum, + ) => { + e.stopPropagation(); + + const newQualifiers = { + ...qualifiers, + selectedCOP: cop.title, + }; + updateQualifiers(newQualifiers); + }; + + return ( + <> +
+ + What type of Practice Area are you looking for? + + + Select one practice area + + + {/* COP Cards */} +
+
+ {copData.map((cop) => { + const isSelected = qualifiers.selectedCOP === cop.title; + + return ( +
handleSelectCOP(e, cop)} + role="button" + tabIndex={0} + > +
+
+ + {cop.title} + + + {cop.subtitle} + +
+ ); + })} +
+ +
+
+
+ + Practice Area: Complete + +
+ +
+
+
+ + ); +} + +export { QualifierPage1 }; diff --git a/frontend/src/pages/QualifierPage/pages/QualifierPage2.tsx b/frontend/src/pages/QualifierPage/pages/QualifierPage2.tsx new file mode 100644 index 00000000..dae77188 --- /dev/null +++ b/frontend/src/pages/QualifierPage/pages/QualifierPage2.tsx @@ -0,0 +1,76 @@ +// External Imports +import React from "react"; +import { useNavigate } from "react-router-dom"; + +// Internal Imports +import Typography from "tw-components/Typography"; +import { Button } from "tw-components/Buttons"; +import { QualifierNav } from "../components/QualifierNav"; +import { RadioButtonForm } from "../components/RadioButtonForm"; +import { ProgressIndicator } from "../components/ProgressIndicator"; +import { useQualifiersContext } from "context/QualifiersContext"; + +function QualifierPage2() { + const navigate = useNavigate(); + const { qualifiers, updateQualifiers } = useQualifiersContext(); + + const handleSkillSelect = (skill: string, level: string) => { + const newSkillsMatrix = { + ...qualifiers.skills_matrix, + [skill]: level, + }; + + const newQualifiers = { + ...qualifiers, + skills_matrix: newSkillsMatrix, + }; + + updateQualifiers(newQualifiers); + }; + + return ( + <> +
+ + Skill Evaluation + + + Evaluate each skill based on your experience + + +
+
+ + +
+ + +
+
+
+ + ); +} + +export { QualifierPage2 }; diff --git a/frontend/src/router/Router.tsx b/frontend/src/router/Router.tsx index 83e3e259..94575677 100644 --- a/frontend/src/router/Router.tsx +++ b/frontend/src/router/Router.tsx @@ -13,9 +13,7 @@ import HomeLayout from "layouts/HomeLayout"; import DefaultNavLayout from "layouts/DefaultNavLayout"; import PrivacyPolicyPage from "pages/PrivacyPolicyPage/PrivacyPolicyPage"; -const QualifierPage = lazy( - () => import("../pages/QualifierPage/QualifierPage"), -); +const QualifierPage = lazy(() => import("../pages/QualifierPage")); const router = createBrowserRouter([ { diff --git a/frontend/src/tw-components/AuthNav.tsx b/frontend/src/tw-components/AuthNav.tsx index fef818e4..60681a05 100644 --- a/frontend/src/tw-components/AuthNav.tsx +++ b/frontend/src/tw-components/AuthNav.tsx @@ -13,7 +13,7 @@ function AuthNav() { return ( Civic Tech Jobs - Home diff --git a/frontend/src/tw-components/Buttons.tsx b/frontend/src/tw-components/Buttons.tsx index 8d0d3fc5..4f30901f 100644 --- a/frontend/src/tw-components/Buttons.tsx +++ b/frontend/src/tw-components/Buttons.tsx @@ -1,6 +1,6 @@ import React from "react"; -import clsx from "clsx"; import Typography from "tw-components/Typography"; +import { cn } from "lib/utils"; const buttonSizes = { small: "px-[24px] h-[32px]", @@ -14,41 +14,76 @@ const buttonSizes = { type ButtonSize = keyof typeof buttonSizes; +type ButtonVariant = "default" | "primary-dark"; + type BaseButtonProps = { size?: ButtonSize; disabled?: boolean; className?: string; + variant?: ButtonVariant; children?: React.ReactNode; onClick?: () => void; + href?: string; }; -// Shared styles -const baseButtonStyles = - "transition-all duration-200 flex items-center justify-center rounded-[64px] bg-blue-dark text-white hover:bg-blue-dark-hover focus:bg-blue-dark-focused focus:outline-none active:bg-blue-dark-focused disabled:bg-grey disabled:text-white disabled:cursor-not-allowed"; - -// Dark mode styles for enabled buttons -const enabledDarkModeStyles = - "dark:bg-white dark:text-blue-dark dark:hover:bg-grey-light dark:focus:bg-[#D9DBDF] dark:active:bg-[#D9DBDF] dark:disabled:bg-grey dark:disabled:text-grey-light"; +const variantStyles: Record = { + default: ` + bg-blue-dark + text-white + hover:bg-blue-dark-hover + focus:bg-blue-dark-focused + active:bg-blue-dark-focused + disabled:bg-grey + disabled:text-white + dark:bg-white + dark:text-blue-dark + dark:hover:bg-grey-light + dark:focus:bg-[#D9DBDF] + dark:active:bg-[#D9DBDF] + dark:disabled:bg-grey + dark:disabled:text-grey-light + `, + "primary-dark": ` + bg-white + text-blue-dark + hover:bg-grey-light + focus:bg-grey-light + active:bg-grey-light + `, +}; -// Base Button, no text, just styling const BaseButton: React.FC = ({ size = "medium", disabled = false, className, + variant = "default", children, onClick, + href, }) => { + const buttonClasses = cn( + "transition-all duration-200 flex items-center justify-center rounded-[64px] focus:outline-none disabled:cursor-not-allowed", + buttonSizes[size], + variantStyles[variant], + className, + ); + + if (href) { + return ( + + {children} + + ); + } + return ( - ); diff --git a/frontend/src/tw-components/CircleCard.tsx b/frontend/src/tw-components/CircleCard.tsx index 2d6f83ef..7700fe60 100644 --- a/frontend/src/tw-components/CircleCard.tsx +++ b/frontend/src/tw-components/CircleCard.tsx @@ -1,7 +1,6 @@ import React from "react"; -import clsx from "clsx"; - import { onKey } from "components/Utility/utils"; +import { cn } from "lib/utils"; interface CircleCardProps extends React.PropsWithChildren { className?: string; @@ -18,7 +17,7 @@ const cardSizes = { function CircleCard({ size = "sm", ...props }: CircleCardProps) { return (
void; open: boolean; @@ -14,7 +11,6 @@ interface DialogProps extends React.PropsWithChildren { function Dialog({ open = false, ...props }: DialogProps) { const [isBackdropOpen, setIsBackdropOpen] = useState(false); - const windowRef = useRef(null); const nodeRef = useRef(null); @@ -33,7 +29,11 @@ function Dialog({ open = false, ...props }: DialogProps) { }, [isBackdropOpen]); useEffect(() => { - if (open) setIsBackdropOpen(true); + if (open) { + setIsBackdropOpen(true); + } else { + setIsBackdropOpen(false); + } }, [open]); function handleClose(e: React.MouseEvent) { @@ -44,38 +44,31 @@ function Dialog({ open = false, ...props }: DialogProps) { return (
- setIsBackdropOpen(true)} - onExited={() => { - setIsBackdropOpen(false); - }} - nodeRef={nodeRef} +
-
- {props.children} -
- + {props.children} +
); } -export { Dialog }; +export default Dialog; diff --git a/frontend/src/tw-components/FooterNav.tsx b/frontend/src/tw-components/FooterNav.tsx index 3199f062..d6ea0252 100644 --- a/frontend/src/tw-components/FooterNav.tsx +++ b/frontend/src/tw-components/FooterNav.tsx @@ -3,7 +3,7 @@ import React, { Fragment } from "react"; import { Link } from "react-router-dom"; // Internal imports -import { Button } from "components/components"; +import { Button } from "tw-components/Buttons"; import { logoHorizontalOnDark, logoStackedOnDark } from "assets/images/images"; interface menuObject { @@ -55,9 +55,9 @@ function FooterNav() {
diff --git a/frontend/src/tw-components/HeaderNav.tsx b/frontend/src/tw-components/HeaderNav.tsx index 46ce641c..41019ee6 100644 --- a/frontend/src/tw-components/HeaderNav.tsx +++ b/frontend/src/tw-components/HeaderNav.tsx @@ -1,11 +1,11 @@ // External Imports import React from "react"; +import { Link } from "react-router-dom"; // Internal Imports import { logoHorizontal } from "assets/images/images"; import { IconHamburgerMenu } from "assets/images/images"; import { Button } from "../components/components"; -import { Link } from "react-router-dom"; interface menuObject { name?: string; @@ -23,13 +23,13 @@ const menuItems: menuObject[] = [ const Logo = () => { return ( - + Civic Tech Jobs - Home - + ); }; diff --git a/frontend/src/tw-components/StandardCard.tsx b/frontend/src/tw-components/StandardCard.tsx index 85b59b8d..acc24107 100644 --- a/frontend/src/tw-components/StandardCard.tsx +++ b/frontend/src/tw-components/StandardCard.tsx @@ -1,6 +1,6 @@ // External Imports import React from "react"; -import clsx from "clsx"; +import { cn } from "lib/utils"; interface CardProps extends React.PropsWithChildren { className?: string; @@ -9,8 +9,8 @@ interface CardProps extends React.PropsWithChildren { function Card({ ...props }: CardProps) { return (
diff --git a/frontend/src/tw-components/Typography.tsx b/frontend/src/tw-components/Typography.tsx index 0f2e3bad..e197e104 100644 --- a/frontend/src/tw-components/Typography.tsx +++ b/frontend/src/tw-components/Typography.tsx @@ -1,5 +1,5 @@ import React from "react"; -import clsx from "clsx"; +import { cn } from "lib/utils"; const baseStyles = "font-sans leading-[137%]"; @@ -35,7 +35,7 @@ const createTypography = ( size: (typeof TypographyStyles)[keyof typeof TypographyStyles], ) => { const Component = ({ children, className, ...props }: TypographyProps) => ( - + {children} ); @@ -61,7 +61,7 @@ const Typography = { HyperlinkBold: ({ children, className, href, ...props }: HyperlinkProps) => ( {children} @@ -70,7 +70,7 @@ const Typography = { Hyperlink: ({ children, className, href, ...props }: HyperlinkProps) => ( {children} diff --git a/frontend/src/tw-components/index.tsx b/frontend/src/tw-components/index.tsx index 3e11e226..f1325038 100644 --- a/frontend/src/tw-components/index.tsx +++ b/frontend/src/tw-components/index.tsx @@ -3,3 +3,6 @@ export { default as CookieBanner } from "./CookieBanner"; export { default as FooterNav } from "./FooterNav"; export { default as HeaderNav } from "./HeaderNav"; export { default as TextField } from "./TextField"; +export { default as Dialog } from "./Dialog"; +export { default as Typography } from "./Typography"; +export { CircleCard } from "./CircleCard"; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index c4b7b5ad..181b664f 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -13,6 +13,7 @@ module.exports = { "./src/pages/LandingPage/*.tsx", "./src/pages/PrivacyPolicyPage/*", "./src/pages/CreditsPage/*.tsx", + "./src/pages/QualifierPage/**/*.tsx", ], // Will change to "./src/**/*.{js,jsx,tsx}", "./templates/index.html" theme: { screens: { @@ -23,6 +24,7 @@ module.exports = { xl: "1201px", }, colors: { + transparent: "transparent", // Primary Colors "blue-dark": "#3450a1", "blue-darker": "#323d69", @@ -83,6 +85,20 @@ module.exports = { p10: "80px", }, }, + animation: { + "slide-in-top": "slide-in-top 400ms ease-in-out", + "slide-out-bottom": "slide-out-bottom 400ms ease-in-out", + }, + keyframes: { + "slide-in-top": { + "0%": { transform: "translateY(-100%)", opacity: "0" }, + "100%": { transform: "translateY(0)", opacity: "1" }, + }, + "slide-out-bottom": { + "0%": { transform: "translateY(0)", opacity: "1" }, + "100%": { transform: "translateY(100%)", opacity: "0" }, + }, + }, }, plugins: [], // // Temporarily disables preflight for all components diff --git a/frontend/tests/components/ProgressBar.test.tsx b/frontend/tests/components/ProgressBar.test.tsx deleted file mode 100644 index b05740db..00000000 --- a/frontend/tests/components/ProgressBar.test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -// External imports -import React from "react"; -import { render, screen } from "@testing-library/react"; -import "@testing-library/jest-dom"; - -// Internal imports -import { ProgressBar } from "components/components"; - -describe("ProgressBar", () => { - test("ProgressBar component", () => { - render(); - expect(screen.getByRole("progressbar")).toBeInTheDocument(); - }); - - test("ProgressBar accessibility", () => { - render(); - expect(screen.getByText("page number:")).toBeInTheDocument(); - expect(screen.getByRole("progressbar")).toHaveAttribute( - "aria-valuemin", - "1", - ); - expect(screen.getByRole("progressbar")).toHaveAttribute( - "aria-valuemax", - "4", - ); - expect(screen.getByRole("progressbar")).toHaveAttribute( - "aria-valuenow", - "2", - ); - expect(screen.getByRole("progressbar")).not.toHaveAttribute( - "aria-valuenow", - "1", - ); - }); -}); diff --git a/mkdocs/docs/developer/devops.md b/mkdocs/docs/developer/devops.md index 1ea17704..7bf20c9a 100644 --- a/mkdocs/docs/developer/devops.md +++ b/mkdocs/docs/developer/devops.md @@ -117,6 +117,8 @@ Of note, however, is the `dev.env.example` file. This file is only a sample, but #### Staging +You can view the staging environment with the following URL: [https://stage.civictechjobs.org/](https://stage.civictechjobs.org/) + The main stage file is `docker-compose.stage.yml`. This docker compose file sets up a staging environment in our local machine. The following is a brief overview of how the stage environment is set up: diff --git a/mkdocs/docs/developer/eslint-guide.md b/mkdocs/docs/developer/eslint-guide.md new file mode 100644 index 00000000..783bd8ec --- /dev/null +++ b/mkdocs/docs/developer/eslint-guide.md @@ -0,0 +1,94 @@ +# ESLint Guide - CTJ + +The purpose of this page is to document how our ESLint setup is configured. ESLint is responsible for linting the frontend portion of our application. + +## ESLint Configuration Documentation for Frontend Developers + +Our ESLint configuration is tailored to help us maintain a clean, consistent codebase with special attention to React, TypeScript, Tailwind CSS, and accessibility standards. Below is an overview of the main components and rules in the configuration and how they function. + +### Plugins and Extensions +This configuration uses several plugins to enhance linting capabilities: + +1. **@eslint/js** - Provides basic JavaScript linting rules. +2. **typescript-eslint** - Adds TypeScript support, integrating rules to enforce TypeScript-specific syntax and best practices. +3. **eslint-plugin-react** - Adds React-specific linting rules to ensure best practices with JSX. +4. **eslint-plugin-prettier** - Enforces code formatting consistency, using Prettier's rules. +5. **eslint-plugin-tailwindcss** - Adds rules specific to Tailwind CSS, helping with class management and preventing misconfigured or conflicting classes. +6. **eslint-plugin-react-hooks** - Enforces React Hooks rules, including hook dependency checking. +7. **eslint-plugin-jsx-a11y** - Adds accessibility rules for JSX, ensuring the markup adheres to accessibility standards. + +### Key Configuration Options + +1. **File Matching** + The configuration applies to all JavaScript, JSX, TypeScript, and TSX files in the project, matching the following patterns: + - `**/*.js` + - `**/*.jsx` + - `**/*.ts` + - `**/*.tsx` + +2. **Global Environment** + Sets up global variables specific to browser environments to avoid undefined variable errors. + +3. **React Settings** + Automatically detects the version of React being used, which optimizes the linting experience. + +4. **Rules** + + - **General Rules:** + - `no-unused-vars`: Warns about variables defined but not used. + - `no-console`: Warns when `console` statements are used in production code. + - `indent`: Enforces a 2-space indentation style for code consistency. + - `no-irregular-whitespace`: Prevents errors caused by unexpected whitespace. + + - **Prettier Integration**: + - `prettier/prettier`: Enforces Prettier's formatting rules for a consistent code style. + + - **React-Specific Rules:** + - `react/no-unescaped-entities`: Disabled globally. To bypass, use `/* eslint-disable react/no-unescaped-entities */` at the top of a file when necessary. + - `react-hooks/rules-of-hooks`: Ensures hooks are only used within functional components and custom hooks. + - `react-hooks/exhaustive-deps`: Warns about missing dependencies in effect hooks. + + - **TypeScript Rules:** + - `@typescript-eslint/no-unused-vars`: Flags unused variables in TypeScript code as errors. + + - **Tailwind CSS Rules:** + - `tailwindcss/no-contradicting-classname`: Prevents usage of conflicting Tailwind CSS classes. + - `tailwindcss/no-unnecessary-arbitrary-value`: Warns about arbitrary values in Tailwind that could be simplified. + - `tailwindcss/classnames-order`: Enforces consistent order of Tailwind CSS classes. + + - **Accessibility Rules (JSX A11y)**: + - `jsx-a11y/alt-text`: Ensures all `img` elements have an `alt` attribute for accessibility. + +5. **Ignored Files and Folders** + - Certain files and folders are ignored to avoid unnecessary linting errors, such as `node_modules/`, config files (`*.config.js`), and mock data in `tests/__mocks__`. + +### Notes on Using ESLint in Development + +- **Disabling Rules Temporarily**: If you encounter specific rule warnings or errors that are intentional or irrelevant to your case, you can disable rules at the file or line level using `// eslint-disable` comments. +- **Testing New Plugins or Rules**: When new plugins or rules are added, test them in a few sample files to ensure compatibility and expected behavior. + +### Running Linter and Formatter +To help maintain consistent code quality and style across the project, we’ve set up commands for both linting and formatting. Here’s how to use them: + +### Linting: + +Run the linter using `npm run lint`. This command will analyze all files in the project for potential linting issues. It will attempt to auto-fix any issues it can and will display whether the code passed or failed the check. +If there are any issues that cannot be auto-fixed, the output will provide details so they can be manually reviewed and addressed. + +### Formatting: + +Run the formatter using `npm run format`. This command will format all JavaScript, TypeScript, and JSON files in the project, skipping any files specified in .gitignore. +These steps help ensure a consistent coding style across the project, minimizing style-related issues and making code easier to read and maintain. + +This ESLint setup ensures our codebase is both clean and accessible, while supporting best practices in React, TypeScript, and Tailwind CSS usage. For any adjustments to the rules or extensions, reach out to the team for further guidance. + +### Recommended Extensions for VS Code +To ensure consistent code quality and style across the team, please install the following extensions in Visual Studio Code: + +- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) +- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + +## Additional Resources + +[Documentation - ESLint - Pluggable JavaScript Linter](https://eslint.org/docs/latest/) +[What is Prettier? · Prettier](https://prettier.io/docs/) \ No newline at end of file diff --git a/mkdocs/docs/developer/frontend.md b/mkdocs/docs/developer/frontend.md index d330b62d..ab2e274f 100644 --- a/mkdocs/docs/developer/frontend.md +++ b/mkdocs/docs/developer/frontend.md @@ -231,94 +231,6 @@ Note: `jest-environment-jsdom` is a library that is absolutely required to link In addition to testing the functioning of our components, we also test the accessability of it via the library, [@axe-core/react](https://github.com/dequelabs/axe-core-npm/tree/develop/packages/react). This library prints out accessibility issues onto the browser console, providing accessibiltiy testing once the HTML has fully rendered. That said, the library is known to give both false positives and false negatives. As always reading the official documentation is best when it comes to resolving these errors. -## ESLint Configuration Documentation for Frontend Developers - -Our ESLint configuration is tailored to help us maintain a clean, consistent codebase with special attention to React, TypeScript, Tailwind CSS, and accessibility standards. Below is an overview of the main components and rules in the configuration and how they function. - -### Plugins and Extensions -This configuration uses several plugins to enhance linting capabilities: - -1. **@eslint/js** - Provides basic JavaScript linting rules. -2. **typescript-eslint** - Adds TypeScript support, integrating rules to enforce TypeScript-specific syntax and best practices. -3. **eslint-plugin-react** - Adds React-specific linting rules to ensure best practices with JSX. -4. **eslint-plugin-prettier** - Enforces code formatting consistency, using Prettier's rules. -5. **eslint-plugin-tailwindcss** - Adds rules specific to Tailwind CSS, helping with class management and preventing misconfigured or conflicting classes. -6. **eslint-plugin-react-hooks** - Enforces React Hooks rules, including hook dependency checking. -7. **eslint-plugin-jsx-a11y** - Adds accessibility rules for JSX, ensuring the markup adheres to accessibility standards. - -### Key Configuration Options - -1. **File Matching** - The configuration applies to all JavaScript, JSX, TypeScript, and TSX files in the project, matching the following patterns: - - `**/*.js` - - `**/*.jsx` - - `**/*.ts` - - `**/*.tsx` - -2. **Global Environment** - Sets up global variables specific to browser environments to avoid undefined variable errors. - -3. **React Settings** - Automatically detects the version of React being used, which optimizes the linting experience. - -4. **Rules** - - - **General Rules:** - - `no-unused-vars`: Warns about variables defined but not used. - - `no-console`: Warns when `console` statements are used in production code. - - `indent`: Enforces a 2-space indentation style for code consistency. - - `no-irregular-whitespace`: Prevents errors caused by unexpected whitespace. - - - **Prettier Integration**: - - `prettier/prettier`: Enforces Prettier's formatting rules for a consistent code style. - - - **React-Specific Rules:** - - `react/no-unescaped-entities`: Disabled globally. To bypass, use `/* eslint-disable react/no-unescaped-entities */` at the top of a file when necessary. - - `react-hooks/rules-of-hooks`: Ensures hooks are only used within functional components and custom hooks. - - `react-hooks/exhaustive-deps`: Warns about missing dependencies in effect hooks. - - - **TypeScript Rules:** - - `@typescript-eslint/no-unused-vars`: Flags unused variables in TypeScript code as errors. - - - **Tailwind CSS Rules:** - - `tailwindcss/no-contradicting-classname`: Prevents usage of conflicting Tailwind CSS classes. - - `tailwindcss/no-unnecessary-arbitrary-value`: Warns about arbitrary values in Tailwind that could be simplified. - - `tailwindcss/classnames-order`: Enforces consistent order of Tailwind CSS classes. - - - **Accessibility Rules (JSX A11y)**: - - `jsx-a11y/alt-text`: Ensures all `img` elements have an `alt` attribute for accessibility. - -5. **Ignored Files and Folders** - - Certain files and folders are ignored to avoid unnecessary linting errors, such as `node_modules/`, config files (`*.config.js`), and mock data in `tests/__mocks__`. - -### Notes on Using ESLint in Development - -- **Disabling Rules Temporarily**: If you encounter specific rule warnings or errors that are intentional or irrelevant to your case, you can disable rules at the file or line level using `// eslint-disable` comments. -- **Testing New Plugins or Rules**: When new plugins or rules are added, test them in a few sample files to ensure compatibility and expected behavior. - -### Running Linter and Formatter -To help maintain consistent code quality and style across the project, we’ve set up commands for both linting and formatting. Here’s how to use them: - -### Linting: - -Run the linter using `npm run lint`. This command will analyze all files in the project for potential linting issues. It will attempt to auto-fix any issues it can and will display whether the code passed or failed the check. -If there are any issues that cannot be auto-fixed, the output will provide details so they can be manually reviewed and addressed. - -### Formatting: - -Run the formatter using `npm run format`. This command will format all JavaScript, TypeScript, and JSON files in the project, skipping any files specified in .gitignore. -These steps help ensure a consistent coding style across the project, minimizing style-related issues and making code easier to read and maintain. - -This ESLint setup ensures our codebase is both clean and accessible, while supporting best practices in React, TypeScript, and Tailwind CSS usage. For any adjustments to the rules or extensions, reach out to the team for further guidance. - -### Required Extensions for VS Code -To ensure consistent code quality and style across the team, please install the following extensions in Visual Studio Code: - -Prettier: https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode - -ESLint: https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint - - ## Additional Resources [Sass Documentation](https://sass-lang.com/documentation)
diff --git a/mkdocs/docs/developer/installation.md b/mkdocs/docs/developer/installation.md index 1ef1f274..414ee17d 100644 --- a/mkdocs/docs/developer/installation.md +++ b/mkdocs/docs/developer/installation.md @@ -126,7 +126,8 @@ This will utilize superuser permissions to change the user and group ownership o [Git Documentation](https://git-scm.com/doc)
[Docker Documentation](https://docs.docker.com/)
-[Frontend Architecture](https://hackforla.github.io/CivicTechJobs/developer/frontend/)
-[Backend Architecture](https://hackforla.github.io/CivicTechJobs/developer/backend/)
-[DevOps Architecture](https://hackforla.github.io/CivicTechJobs/developer/devops/)
-[GitHub Architecture](https://hackforla.github.io/CivicTechJobs/developer/backend/)
+[Quickstart Guide](developer/quickstart-guide)
+[Frontend Architecture](developer/frontend/)
+[Backend Architecture](developer/backend/)
+[DevOps Architecture](developer/devops/)
+[GitHub Architecture](developer/backend/)
diff --git a/mkdocs/docs/developer/quickstart-guide.md b/mkdocs/docs/developer/quickstart-guide.md new file mode 100644 index 00000000..6dd9b2ee --- /dev/null +++ b/mkdocs/docs/developer/quickstart-guide.md @@ -0,0 +1,120 @@ +# Quickstart Guide + +This is a quickstart guide with instructions for how to get the various environments of the application up and running on your machine. + +Before doing any of these, make sure to first follow the [Installation Instructions](developer/installation/). + +## How to run the app in development mode + +First, make sure that `dev.env` is in the `dev` folder with the correct variables. + +Start the application in development mode: +```sh +docker compose up --watch +``` + +Test client with this URL: +``` +http://localhost:5175 +``` + +Test server with these URLs: +``` +http://localhost:8000/api/opportunities/ + +http://localhost:8000/api/healthcheck +``` +- If you request a non-existent api endpoint, eg. `localhost:8000/api/asdf`, it should return an API error response JSON: + - `{"error": "API endpoint not found", "status_code": 404, "message": "The requested API endpoint does not exist"}` + +## Run the app in dev mode without Docker + +Make sure you have python, poetry, node.js, and npm installed on your machine. + +Steps to run the **backend** without docker: + +1. In a terminal, navigate to the backend folder with `cd backend`. +2. Inside this folder, run the backend dev server start command below. +3. The server should be running on `localhost:8000` now. + +Backend dev server start command: +```sh +poetry run python manage.py runserver localhost:8000 +``` + +
+ +Steps to run the **frontend** without docker: + +1. In a separate terminal, navigate to the frontend folder with `cd frontend`. +2. Inside this folder, run the frontend dev server start command below. +3. The client dev server should be running on `localhost:5175` now. + +Frontend dev server start command: +```sh +npm run dev +``` + +## Local Django ADMIN url and credentials + +Use the below to log in to your local django server admin portal: + +``` +http://localhost:8000/admin/ +``` +- This should work with and without docker + +Create an admin user with: +``` +python manage.py createsuperuser +``` +- It will prompt you to enter a username, email, and password to create an admin user. Make sure to write these down +- You can now use these credentials to log into your local server's admin portal. +- These credentials are saved into your local postgres instance database, so if this data ever gets deleted or reset, you will have to create an admin user again. +- Reference: [Writing your first Django app, part 2 | Django documentation | Django](https://docs.djangoproject.com/en/5.1/intro/tutorial02/#introducing-the-django-admin) + + +## Mkdocs + +Start mkdocs development server: + +```sh +docker compose -f docker-compose.docs.yml up --watch +``` + +- Test with: `http://localhost:8005/CivicTechJobs/` + + +## Backend - Linting script + +Run these commands inside the `/backend` folder, in the following order: + +``` +poetry install +poetry run isort . +poetry run black . +poetry run flake8 +``` + +- These should ideally be run before making a PR +- `isort`: sorts the import statements +- `black`: automatically formats python code +- `flake8`: lints python code + + +## Staging environment + +You can view the staging deployment with the following URL: [https://stage.civictechjobs.org/](https://stage.civictechjobs.org/) + +How to run the stage environment locally: + +1. Make sure you have a `stage.env` file inside the `/stage` folder of your local repository. It should be configured with the correct variables. +2. Run the staging environment in your local machine: + +``` +docker compose -f docker-compose.stage.yml up +``` + +Notes: +- [Feature: Set up stage environment in docker by LoTerence · Pull Request #613 · hackforla/CivicTechJobs · GitHub](https://github.com/hackforla/CivicTechJobs/pull/613) + diff --git a/mkdocs/docs/index.md b/mkdocs/docs/index.md index a257179f..ae2e653d 100644 --- a/mkdocs/docs/index.md +++ b/mkdocs/docs/index.md @@ -20,7 +20,7 @@ CTJ will tackle key challenges in project recruitment and organizational sustain Yes, there are recruitment issues on project boards. However, the process is cumbersome and involves multiple steps to match candidates with the right roles. -Read more about what lead up to us developing this project, at our [History](History) page. +Read more about what lead up to us developing this project, at our [History](misc/history) page. ### So how is this different? diff --git a/mkdocs/mkdocs.yml b/mkdocs/mkdocs.yml index e8fce130..17b0cd90 100644 --- a/mkdocs/mkdocs.yml +++ b/mkdocs/mkdocs.yml @@ -26,10 +26,12 @@ nav: - Design System Helper: developer/design-system.md - DevOps Architecture: developer/devops.md - Development Culture: developer/development-culture.md + - ESLint Guide: developer/eslint-guide.md - Frontend Architecture: developer/frontend.md - GitHub Architecture: developer/github.md - Git branch Structure: developer/git-branch-structure.md - Installation Instructions: developer/installation.md + - Quickstart Guide: developer/quickstart-guide.md - MkDocs Architecture: developer/mkdocs-architecture.md - MkDocs - Documentation Guide: developer/mkdocs-guide.md - MkDocs - How to Edit: developer/mkdocs-edit-instructions.md