├── backend ├── backend │ ├── __init__.py │ ├── views.py │ ├── asgi.py │ ├── wsgi.py │ ├── templates │ │ └── backend │ │ │ └── dev-mode.html │ └── urls.py ├── ctj_api │ ├── __init__.py │ ├── tests │ │ └── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── apps.py │ ├── admin.py │ ├── urls.py │ └── permissions.py ├── .python-version ├── .gitignore ├── .flake8 ├── .dockerignore ├── entrypoint.sh ├── manage.py ├── startServer.sh └── pyproject.toml ├── frontend ├── tests │ ├── __mocks__ │ │ ├── styleMock.js │ │ └── fileMock.js │ ├── components │ │ ├── Button.test.tsx │ │ ├── TextField.test.tsx │ │ ├── Notification.test.tsx │ │ └── Calendar.test.tsx │ └── pages │ │ └── LandingPage.test.tsx ├── .gitignore ├── src │ ├── pages │ │ ├── LandingPage │ │ │ ├── _index.scss │ │ │ ├── _LandingPageCop.scss │ │ │ ├── LandingPage.tsx │ │ │ ├── _LandingPageCopCards.scss │ │ │ ├── LandingPageIntro.tsx │ │ │ └── LandingPageCopCards.tsx │ │ ├── Demo │ │ │ └── _Demo.scss │ │ ├── NotFoundPage │ │ │ ├── _NotFoundPage.scss │ │ │ └── NotFoundPage.tsx │ │ ├── QualifierPage │ │ │ ├── components │ │ │ │ ├── QualifierNav.tsx │ │ │ │ ├── ChipsSelection.tsx │ │ │ │ └── ProgressIndicator.tsx │ │ │ └── index.tsx │ │ ├── CreditsPage │ │ │ ├── TopSvg.tsx │ │ │ └── Card.tsx │ │ └── Authentication │ │ │ ├── page.tsx │ │ │ └── LoginForm.tsx │ ├── components │ │ ├── Basics │ │ │ ├── _z-index.scss │ │ │ ├── _index.scss │ │ │ ├── _colors.scss │ │ │ ├── _utils.scss │ │ │ ├── _alignment.scss │ │ │ ├── _breakpoints.scss │ │ │ ├── _spacing.scss │ │ │ ├── _font.scss │ │ │ ├── _layout.scss │ │ │ └── _typography.scss │ │ ├── Inputs │ │ │ ├── _index.scss │ │ │ ├── calendar_data.ts │ │ │ ├── _Chip.scss │ │ │ ├── _Textfield.scss │ │ │ ├── _ProtoInput.scss │ │ │ ├── _Dropdown.scss │ │ │ ├── ProtoInput.tsx │ │ │ ├── Textfield.tsx │ │ │ └── Chip.tsx │ │ ├── Carousel │ │ │ ├── _ClickCarousel.scss │ │ │ ├── _ScrollCarousel.scss │ │ │ └── ClickCarousel.tsx │ │ ├── Transition │ │ │ ├── _Wrapper.scss │ │ │ └── Wrapper.tsx │ │ ├── Notification │ │ │ ├── _Notification.scss │ │ │ └── Notification.tsx │ │ ├── components.tsx │ │ ├── Scroll │ │ │ └── _ChevronScroll.scss │ │ └── Utility │ │ │ ├── utils.ts │ │ │ └── hooks │ │ │ └── dragToSelectUnselect.ts │ ├── assets │ │ ├── fonts │ │ │ ├── Roboto-Black.ttf │ │ │ ├── Roboto-Bold.ttf │ │ │ ├── Roboto-Light.ttf │ │ │ ├── Roboto-Thin.ttf │ │ │ ├── Roboto-Italic.ttf │ │ │ ├── Roboto-Medium.ttf │ │ │ ├── Roboto-Regular.ttf │ │ │ ├── Roboto-BoldItalic.ttf │ │ │ ├── Roboto-ThinItalic.ttf │ │ │ ├── Roboto-BlackItalic.ttf │ │ │ ├── Roboto-LightItalic.ttf │ │ │ └── Roboto-MediumItalic.ttf │ │ └── images │ │ │ └── svgs │ │ │ ├── icons │ │ │ ├── icon-chevron-left.svg │ │ │ ├── icon-chevron-right.svg │ │ │ ├── icon-search.svg │ │ │ ├── icon-checkmark.svg │ │ │ ├── icon-checkmark-dark.svg │ │ │ ├── icon-checkbox-no.svg │ │ │ ├── icon-dropdown-up.svg │ │ │ ├── icon-arrow-left.svg │ │ │ ├── icon-dropdown-down.svg │ │ │ ├── icon-plus.svg │ │ │ ├── icon-arrow-down.svg │ │ │ ├── icon-hamburger-menu.svg │ │ │ ├── icon-eye-close.svg │ │ │ ├── icon-x.svg │ │ │ ├── icon-eye-open.svg │ │ │ └── icon-checkbox-yes.svg │ │ │ ├── login-tan-bg.svg │ │ │ ├── landing-page-bg.svg │ │ │ ├── logos │ │ │ └── logo-logomark.svg │ │ │ ├── privacy-policy-bg-top.svg │ │ │ ├── credits-page-bg-top.svg │ │ │ └── communities-of-practice │ │ │ └── cop-icon-engineering.svg │ ├── lib │ │ └── utils.ts │ ├── layouts │ │ ├── HomeLayout.tsx │ │ └── DefaultNavLayout.tsx │ ├── vite-env.d.ts │ ├── tw-components │ │ ├── StandardCard.tsx │ │ ├── index.tsx │ │ ├── CircleCard.tsx │ │ ├── AuthNav.tsx │ │ ├── AccordionFaq.tsx │ │ ├── FooterNav.tsx │ │ ├── TextField.tsx │ │ ├── HeaderNav.tsx │ │ ├── Dialog.tsx │ │ └── CookieBanner.tsx │ ├── index.tsx │ ├── index.scss │ ├── App.tsx │ └── router │ │ └── Router.tsx ├── postcss.config.js ├── .dockerignore ├── tsconfig.json ├── index.html ├── jest.config.js ├── vite.config.mts └── eslint.config.mjs ├── .github ├── linters │ ├── .jscpd.json │ ├── .yaml-lint.yml │ ├── .stylelintrc.json │ └── .eslintrc.yml ├── ISSUE_TEMPLATE │ ├── custom.md │ ├── post-an-open-role.md │ ├── update-team-roster.md │ ├── create-agenda.md │ ├── update-content-for-readme-file.md │ ├── wave-chrome-extension--accessibility-review.md │ ├── lighthouse--cross-origin-destinations-are-unsafe.md │ ├── lighthouse--accessibility---links.md │ ├── lighthouse--image-optimization.md │ ├── lighthouse--accessibility---forms.md │ ├── control-what-appears-when-you-paste-your-sites-link-in-social-media-sites.md │ ├── blank-issue.md │ ├── create-project-card-for--project-name-.md │ ├── feature_request.md │ ├── update-project-profile---name-of-project-.md │ ├── proposal-to-change-project-md-file-format.md │ ├── which-accessibility-testing-tool-should-you-use-.md │ ├── add-project-logo-or-image-to-your-main-repository.md │ ├── dev-onboarding.md │ ├── lighthouse--how-to.md │ ├── blank-issue-form.yml │ ├── blank-issue-form-d.yml │ └── component-issue-form.yml ├── workflows │ ├── sortOpenedIssues │ │ └── config.json │ ├── jest-react-test.yml │ ├── mkdocs-build.yml │ ├── deploy-stage.yml │ ├── linter.yml │ └── codeql-analysis.yml └── pull_request_template.md ├── mkdocs ├── docs │ ├── assets │ │ ├── scalable.gif │ │ ├── responsive.gif │ │ ├── ctj-infra-diagram.png │ │ └── scale-and-response.gif │ ├── css │ │ └── extra.css │ ├── misc │ │ ├── our-process.md │ │ ├── glossary.md │ │ ├── research-wiki-template.md │ │ ├── security-updates.md │ │ ├── ada-guide.md │ │ ├── the-team.md │ │ └── history.md │ ├── joining-the-team │ │ ├── other-volunteer.md │ │ ├── data-scientist.md │ │ ├── content-writer.md │ │ ├── uiux-researcher.md │ │ ├── uiux-designer.md │ │ ├── product-manager.md │ │ ├── intro.md │ │ └── web-developer.md │ ├── developer │ │ ├── github.md │ │ ├── mkdocs-edit-instructions.md │ │ └── deployment-infra.md │ └── index.md └── mkdocs.yml ├── dev ├── linter.env.example ├── vite.Dockerfile ├── dev.env.example ├── linter.dockerfile └── django.dockerfile ├── .gitignore ├── docker-compose.docs.yml ├── .dockerignore ├── stage ├── stage.env.example └── Dockerfile ├── Makefile ├── docker-compose.stage.yml ├── aws └── task-definition.json ├── docker-compose.yml └── .pre-commit-config.yaml /backend/backend/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/ctj_api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/ctj_api/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/.python-version: -------------------------------------------------------------------------------- 1 | 3.12.4 2 | -------------------------------------------------------------------------------- /backend/ctj_api/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/tests/__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /.github/linters/.jscpd.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": ["**/*.md"] 3 | } 4 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | .idea 3 | DCIM 4 | data 5 | staticfiles -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/ 3 | 4 | vite.config.*.timestamp-* -------------------------------------------------------------------------------- /frontend/tests/__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = "test-file-stub"; 2 | -------------------------------------------------------------------------------- /frontend/src/pages/LandingPage/_index.scss: -------------------------------------------------------------------------------- 1 | @forward "LandingPageCop"; 2 | @forward "LandingPageCopCards"; 3 | -------------------------------------------------------------------------------- /frontend/src/components/Basics/_z-index.scss: -------------------------------------------------------------------------------- 1 | $z-focus: 1; 2 | $z-popup: 2; 3 | $z-header: 1030; 4 | $z-dialog: 1060; 5 | -------------------------------------------------------------------------------- /mkdocs/docs/assets/scalable.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackforla/CivicTechJobs/HEAD/mkdocs/docs/assets/scalable.gif -------------------------------------------------------------------------------- /mkdocs/docs/assets/responsive.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackforla/CivicTechJobs/HEAD/mkdocs/docs/assets/responsive.gif -------------------------------------------------------------------------------- /.github/linters/.yaml-lint.yml: -------------------------------------------------------------------------------- 1 | rules: 2 | document-start: 3 | present: false 4 | line-length: disable 5 | new-lines: disable 6 | -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackforla/CivicTechJobs/HEAD/frontend/src/assets/fonts/Roboto-Black.ttf -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackforla/CivicTechJobs/HEAD/frontend/src/assets/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackforla/CivicTechJobs/HEAD/frontend/src/assets/fonts/Roboto-Light.ttf -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackforla/CivicTechJobs/HEAD/frontend/src/assets/fonts/Roboto-Thin.ttf -------------------------------------------------------------------------------- /frontend/src/components/Inputs/_index.scss: -------------------------------------------------------------------------------- 1 | @forward "Calendar"; 2 | @forward "Chip"; 3 | @forward "Dropdown"; 4 | @forward "Textfield"; 5 | -------------------------------------------------------------------------------- /mkdocs/docs/assets/ctj-infra-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackforla/CivicTechJobs/HEAD/mkdocs/docs/assets/ctj-infra-diagram.png -------------------------------------------------------------------------------- /mkdocs/docs/assets/scale-and-response.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackforla/CivicTechJobs/HEAD/mkdocs/docs/assets/scale-and-response.gif -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackforla/CivicTechJobs/HEAD/frontend/src/assets/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackforla/CivicTechJobs/HEAD/frontend/src/assets/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackforla/CivicTechJobs/HEAD/frontend/src/assets/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackforla/CivicTechJobs/HEAD/frontend/src/assets/fonts/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackforla/CivicTechJobs/HEAD/frontend/src/assets/fonts/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackforla/CivicTechJobs/HEAD/frontend/src/assets/fonts/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackforla/CivicTechJobs/HEAD/frontend/src/assets/fonts/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackforla/CivicTechJobs/HEAD/frontend/src/assets/fonts/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /backend/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | extend-ignore = E203, W503 4 | exclude = 5 | .git, 6 | __pycache__, 7 | migrations, 8 | venv, 9 | settings.py -------------------------------------------------------------------------------- /backend/ctj_api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CtjApiConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "ctj_api" 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /frontend/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /mkdocs/docs/css/extra.css: -------------------------------------------------------------------------------- 1 | .dropdown-menu { 2 | position: relative; 3 | top: 2.3rem; /* this is important to move up so that the mouse does not trigger leave when touching the padding on the navbar */ 4 | } 5 | -------------------------------------------------------------------------------- /mkdocs/docs/misc/our-process.md: -------------------------------------------------------------------------------- 1 | # Our Process 2 | 3 | _This page has links and details such as links to a spreadsheet with our research, etc. and what order we did things in. But less narrative and mostly links_ 4 | -------------------------------------------------------------------------------- /frontend/src/pages/LandingPage/_LandingPageCop.scss: -------------------------------------------------------------------------------- 1 | @use "@/components/Basics" as *; 2 | 3 | .landing-inner-cop-nav { 4 | row-gap: 32px; 5 | } 6 | 7 | .landing-cop-nav-title { 8 | inline-size: min-content; 9 | } 10 | -------------------------------------------------------------------------------- /dev/linter.env.example: -------------------------------------------------------------------------------- 1 | # environment file to configure docker compose 2 | 3 | # used to setup the linter container 4 | UID= 5 | GID= 6 | HOME= 7 | -------------------------------------------------------------------------------- /frontend/src/layouts/HomeLayout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Outlet } from "react-router-dom"; 3 | 4 | const HomeLayout = () => { 5 | return ( 6 | <> 7 | 8 | 9 | ); 10 | }; 11 | 12 | export default HomeLayout; 13 | -------------------------------------------------------------------------------- /frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | readonly VITE_APP_TITLE: string; 5 | // more env variables... 6 | } 7 | 8 | interface ImportMeta { 9 | readonly env: ImportMetaEnv; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/components/Carousel/_ClickCarousel.scss: -------------------------------------------------------------------------------- 1 | // Internal Imports 2 | @use "../Basics" as *; 3 | 4 | .click-carousel { 5 | &.hidden { 6 | @include hidden; 7 | } 8 | 9 | @include breakpoint-media-min("lgtablet") { 10 | @include hidden; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | // postcss.config.js 2 | // https://tailwindcss.com/docs/using-with-preprocessors#using-post-css-as-your-preprocessor 3 | module.exports = { 4 | plugins: { 5 | "postcss-import": {}, 6 | tailwindcss: {}, 7 | autoprefixer: {}, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /frontend/src/assets/images/svgs/icons/icon-chevron-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/images/svgs/icons/icon-chevron-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/components/Basics/_index.scss: -------------------------------------------------------------------------------- 1 | @forward "alignment" as align-*; 2 | @forward "breakpoints" as breakpoint-*; 3 | @forward "colors" as color-*; 4 | @forward "font" as font-*; 5 | @forward "layout"; 6 | @forward "spacing"; 7 | @forward "typography"; 8 | @forward "utils"; 9 | @forward "z-index"; 10 | -------------------------------------------------------------------------------- /frontend/src/components/Transition/_Wrapper.scss: -------------------------------------------------------------------------------- 1 | .fade-appear { 2 | opacity: 0; 3 | } 4 | 5 | .fade-appear-active { 6 | opacity: 1; 7 | transition-duration: 1000ms; 8 | } 9 | 10 | .fade-exit { 11 | opacity: 1; 12 | } 13 | 14 | .fade-exit-active { 15 | opacity: 0; 16 | transition-duration: 1000ms; 17 | } 18 | -------------------------------------------------------------------------------- /backend/backend/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.views.generic import TemplateView 3 | 4 | catchall_dev = TemplateView.as_view(template_name="dev-mode.html") 5 | 6 | catchall_prod = TemplateView.as_view(template_name="index.html") 7 | 8 | catchall = catchall_dev if settings.DEBUG else catchall_prod 9 | -------------------------------------------------------------------------------- /.github/linters/.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "selector-class-pattern": null 4 | }, 5 | "extends": [ 6 | "stylelint-config-standard-scss", 7 | "stylelint-config-prettier-scss" 8 | ], 9 | "ignoreFiles": [ 10 | "../../frontend/src/components/_normalize.scss" 11 | ], 12 | "ignorePatterns": ["*.md"] 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/sortOpenedIssues/config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "body": "'### Dependency\n\n- [ ]'", 4 | "column": 10928270 5 | }, 6 | { 7 | "labels": "'Dependency'", 8 | "column": 10928270 9 | }, 10 | { 11 | "body": "'### Dependencies\n\n- [ ]'", 12 | "column": 10928270 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /mkdocs/docs/joining-the-team/other-volunteer.md: -------------------------------------------------------------------------------- 1 | **_DRAFT NOT YET FILLED OUT_** 2 | 3 | # Other Volunteer 4 | 5 | 1. Get an overview of the project from the Wiki. 6 | 7 | 1. Review the [Project Board](https://github.com/hackforla/[INSERT-REPO-HERE]/projects/1). 8 | 9 | 1. Chat with a [INSERT PROJECT NAME HERE] PM to discuss your interest and background. 10 | -------------------------------------------------------------------------------- /frontend/src/layouts/DefaultNavLayout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Outlet } from "react-router-dom"; 3 | import { HeaderNav, FooterNav } from "tw-components"; 4 | 5 | const DefaultNavLayout = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default DefaultNavLayout; 16 | -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | # frontend dockerignore 2 | # .dockerignore should be in the build context root 3 | 4 | # Environment files 5 | .env 6 | */*.env 7 | .DS_store 8 | 9 | # Dependency directories 10 | **/node_modules/ 11 | 12 | # Docker 13 | .dockerignore 14 | 15 | # git 16 | .git 17 | .gitignore 18 | 19 | # builds 20 | dist 21 | 22 | # vite 23 | vite.config.*.timestamp-* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Environment files 2 | .env 3 | dev.env 4 | stage.env 5 | secret_key.txt 6 | *venv/ 7 | 8 | # Django # 9 | *.log 10 | *.pot 11 | *.pyc 12 | __pycache__ 13 | db.sqlite3 14 | data 15 | media 16 | 17 | # IDEs 18 | .vscode/ 19 | .idea 20 | .DS_store 21 | 22 | # linters 23 | dev/linter.env 24 | **/super-linter.log 25 | 26 | # static build files 27 | backend/frontend_dist 28 | backend/staticfiles -------------------------------------------------------------------------------- /frontend/src/assets/images/svgs/icons/icon-search.svg: -------------------------------------------------------------------------------- 1 | 7 | 15 | -------------------------------------------------------------------------------- /frontend/src/assets/images/svgs/login-tan-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "noImplicitAny": true, 5 | "module": "esnext", 6 | "target": "es6", 7 | "jsx": "react", 8 | "allowJs": true, 9 | "moduleResolution": "node", 10 | "esModuleInterop": true, 11 | "baseUrl": "./src", 12 | "strict": true 13 | }, 14 | "include": ["./src", "./tests", "global.d.ts"] 15 | } 16 | -------------------------------------------------------------------------------- /docker-compose.docs.yml: -------------------------------------------------------------------------------- 1 | name: civic-tech-jobs-mkdocs 2 | 3 | services: 4 | mkdocs: 5 | container_name: mkdocs 6 | image: hackforlaops/mkdocs:latest 7 | command: mkdocs serve -a "0.0.0.0:8005" 8 | ports: 9 | - "8005:8005" 10 | volumes: 11 | - ./mkdocs:/app 12 | develop: 13 | watch: 14 | - action: sync 15 | path: ./mkdocs 16 | target: /app 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/post-an-open-role.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Post an open role 3 | about: Recruit volunteers for specific open roles template 4 | title: 'CTJ: Open Role for: [Replace with NAME OF ROLE]' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | [INSERT DRAFT FROM THE Recruit volunteers for team open roles issue] 13 | -------------------------------------------------------------------------------- /frontend/src/assets/images/svgs/landing-page-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /backend/.dockerignore: -------------------------------------------------------------------------------- 1 | # backend dockerignore 2 | # .dockerignore should be in the build context root 3 | 4 | # Environment files 5 | .env 6 | */*.env 7 | *venv/ 8 | .DS_store 9 | 10 | # Django 11 | *.log 12 | *.pot 13 | *.pyc 14 | **/__pycache__/ 15 | db.sqlite3 16 | data 17 | media 18 | 19 | # Docker 20 | .dockerignore 21 | 22 | # git 23 | .git 24 | .gitignore 25 | 26 | # Builds 27 | frontend/static/vite_assets_dist/* 28 | staticfiles -------------------------------------------------------------------------------- /frontend/src/pages/LandingPage/LandingPage.tsx: -------------------------------------------------------------------------------- 1 | // External Imports 2 | import React, { Fragment } from "react"; 3 | 4 | import { LandingPageIntro } from "./LandingPageIntro"; 5 | import { LandingPageCop } from "./LandingPageCop"; 6 | 7 | function LandingPage() { 8 | return ( 9 | 10 |
11 | 12 | 13 |
14 |
15 | ); 16 | } 17 | 18 | export { LandingPage }; 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/update-team-roster.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Update Team Roster 3 | about: Provides new team members a link to team roster to input their information 4 | title: '' 5 | labels: documentation, good first issue, question 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Overview 11 | Rolling Roster of Team Participants. 12 | 13 | ### Action Items 14 | Add or update your personal info to the team roster. 15 | 16 | ### Resources/Instructions 17 | ADD URL TO TEAM ROSTER HERE 18 | -------------------------------------------------------------------------------- /backend/backend/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for backend project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /backend/backend/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for backend project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /frontend/src/components/Notification/_Notification.scss: -------------------------------------------------------------------------------- 1 | // External Imports 2 | @use "sass:map"; 3 | 4 | // Internal Imports 5 | @use "../Basics" as *; 6 | 7 | /***************************** 8 | *** Notification Bar class *** 9 | *****************************/ 10 | 11 | .notification { 12 | background-color: $color-tan; 13 | position: relative; 14 | height: 55px; 15 | } 16 | 17 | .notification-x { 18 | right: 16px; 19 | position: absolute; 20 | top: 8px; 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/components/Basics/_colors.scss: -------------------------------------------------------------------------------- 1 | // Primary Colors 2 | $blue-dark: #3450a1; 3 | $blue-darker: #323d69; 4 | $blue-dark-hover: #445ea9; 5 | $blue-dark-focused: #273c79; 6 | 7 | // Primary on Dark Colors 8 | $blue: #44aff1; 9 | $blue-focused: #3fa1de; 10 | 11 | // Secondary Colors 12 | $tan: #ffe0b9; 13 | $tan-light: #ffefdb; 14 | $green: #13831e; 15 | 16 | // Neutral Colors 17 | $white: #fff; 18 | $grey-light: #f2f2f2; 19 | $grey: #c1c1c1; 20 | $grey-dark: #585858; 21 | $charcoal: #333; 22 | -------------------------------------------------------------------------------- /frontend/src/pages/Demo/_Demo.scss: -------------------------------------------------------------------------------- 1 | @use "@/components/Basics" as *; 2 | 3 | .box { 4 | border: 1px solid red; 5 | height: 50px; 6 | overflow: hidden; 7 | } 8 | 9 | .demo-chevron-scroll-btn { 10 | scroll-snap-align: start; 11 | border: 1px solid #333; 12 | height: 42px; 13 | padding: 0 21px; 14 | font-style: normal; 15 | font-weight: 700; 16 | font-size: 20px; 17 | line-height: 137%; 18 | border-radius: 64px; 19 | background-color: #fff; 20 | white-space: nowrap; 21 | } 22 | -------------------------------------------------------------------------------- /dev/vite.Dockerfile: -------------------------------------------------------------------------------- 1 | # Use official Node.js 20 as base image 2 | FROM node:20-alpine3.19 3 | 4 | # Set working directory 5 | WORKDIR /usr/src/app 6 | 7 | # Copy package.json and package-lock.json 8 | COPY package*.json ./ 9 | 10 | # Install dependencies 11 | RUN npm install 12 | 13 | # Copy the rest of the frontend code 14 | COPY . . 15 | 16 | # Expose port 5175 for the client 17 | EXPOSE 5175 18 | 19 | # Start the Vite client in development mode 20 | CMD [ "npm", "run", "dev" ] 21 | -------------------------------------------------------------------------------- /mkdocs/docs/misc/glossary.md: -------------------------------------------------------------------------------- 1 | **_DRAFT NOT YET FILLED OUT_** 2 | 3 | # Glossary 4 | 5 | When we have a shiny glossary it will come here. 6 | 7 | 9 | 10 | 11 | 12 | 13 | 15 | 17 | 18 | 19 | 21 | 23 | 24 | 25 |
8 | TermAlternate termsOfficial LinkDescription
14 | 16 |
20 | 22 |
26 | -------------------------------------------------------------------------------- /backend/ctj_api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import ( 4 | CommunityOfPractice, 5 | CustomUser, 6 | Opportunity, 7 | Project, 8 | Role, 9 | Skill, 10 | SkillMatrix, 11 | ) 12 | 13 | # Register your models here. 14 | admin.site.register(CommunityOfPractice) 15 | admin.site.register(Role) 16 | admin.site.register(Skill) 17 | admin.site.register(Project) 18 | admin.site.register(SkillMatrix) 19 | admin.site.register(CustomUser) 20 | admin.site.register(Opportunity) 21 | -------------------------------------------------------------------------------- /mkdocs/docs/joining-the-team/data-scientist.md: -------------------------------------------------------------------------------- 1 | **_DRAFT NOT YET FILLED OUT_** 2 | 3 | # Data Scientist 4 | 5 | 1. Read the [Readme](https://github.com/hackforla/CivicTechJobs/blob/main/README.md). 6 | 7 | 2. Review the [Project Board](https://github.com/hackforla/CivicTechJobs/projects/1) and identify an actionable backlog item. **We are working to resolve some database architecture issues and are not onboarding new data scientist at this time.** 8 | 9 | 3. Communicate with the PM on your interest in being assigned a task. 10 | -------------------------------------------------------------------------------- /frontend/src/components/Inputs/calendar_data.ts: -------------------------------------------------------------------------------- 1 | const daysOfWeek = [ 2 | "Sunday", 3 | "Monday", 4 | "Tuesday", 5 | "Wednesday", 6 | "Thursday", 7 | "Friday", 8 | "Saturday", 9 | ]; 10 | 11 | function hoursOfDay() { 12 | const arr = []; 13 | for (const meridiem of ["AM", "PM"]) { 14 | arr.push(`12:00 ${meridiem}`); 15 | for (let i = 1; i <= 11; i++) { 16 | arr.push(`${i}:00 ${meridiem}`); 17 | } 18 | } 19 | arr.push("12:00 AM"); 20 | return arr; 21 | } 22 | 23 | export { daysOfWeek, hoursOfDay }; 24 | -------------------------------------------------------------------------------- /mkdocs/docs/joining-the-team/content-writer.md: -------------------------------------------------------------------------------- 1 | **_DRAFT NOT YET FILLED OUT_** 2 | 3 | # Content Writer 4 | 5 | 1. Review existing copy on the [CivicTechJobs](https://Civictechjobs.org). 6 | 7 | 1. Review the [Figma](https://www.figma.com/file/G5bOqhud6azbxyR9El9Ygp/Civic-Tech-Jobs?node-id=0%3A1) for pages that are not yet published. 8 | 9 | 1. Review the [Project Board](https://github.com/hackforla/CivicTechJobs/projects/1) and identify an actionable backlog item. 10 | 11 | 1. Communicate with PM your interest in being assigned a task. 12 | -------------------------------------------------------------------------------- /mkdocs/docs/misc/research-wiki-template.md: -------------------------------------------------------------------------------- 1 | # Template for presenting research on wiki 2 | 3 | ``` 4 | # [Name of Research] 5 | 6 | ## Research Date: 7 | 8 | ## Current Status 9 | 10 | ## Overview Issue #: 11 | 12 | ## Outstanding task items 13 | 14 | ## Assets 15 | 16 | ### Research Plan (including Audience Identification documentation) 17 | 18 | ### Scripts 19 | 20 | ### Interview recordings & Transcripts 21 | 22 | ### Synthesis (Miro or Figjam) 23 | 24 | ### Presentation of Findings 25 | 26 | ### Action Items Spreadsheet 27 | ``` 28 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Civic Tech Jobs 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/src/tw-components/StandardCard.tsx: -------------------------------------------------------------------------------- 1 | // External Imports 2 | import React from "react"; 3 | import { cn } from "lib/utils"; 4 | 5 | interface CardProps extends React.PropsWithChildren { 6 | className?: string; 7 | } 8 | 9 | function Card({ ...props }: CardProps) { 10 | return ( 11 |
17 | {props.children} 18 |
19 | ); 20 | } 21 | 22 | export { Card }; 23 | -------------------------------------------------------------------------------- /frontend/src/assets/images/svgs/icons/icon-checkmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/images/svgs/icons/icon-checkmark-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/create-agenda.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Create Agenda 3 | about: Assign issue to all team members day after meetup in prep for next meetup 4 | title: '' 5 | labels: documentation, help wanted, question 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Overview 11 | Gather all items team members want to propose for this weeks' agenda. 12 | 13 | ### Action Items 14 | Add comment to this issue with your proposed agenda item. if item warrants an issue of its own please start a new issue, by going to the issues tab above and clicking on the New Issues green button. 15 | -------------------------------------------------------------------------------- /frontend/src/tw-components/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as AuthNav } from "./AuthNav"; 2 | export { default as CookieBanner } from "./CookieBanner"; 3 | export { default as FooterNav } from "./FooterNav"; 4 | export { default as HeaderNav } from "./HeaderNav"; 5 | export { default as TextField } from "./TextField"; 6 | export { default as Dialog } from "./Dialog"; 7 | export { default as Typography } from "./Typography"; 8 | export { CircleCard } from "./CircleCard"; 9 | export { Checkbox } from "./Checkbox"; 10 | export { Button, SearchButton, IconButton } from "./Buttons"; 11 | -------------------------------------------------------------------------------- /frontend/src/components/Inputs/_Chip.scss: -------------------------------------------------------------------------------- 1 | // Internal Imports 2 | @use "../Basics" as *; 3 | 4 | @mixin chip-base { 5 | background-color: $color-white; 6 | border-radius: 24px; 7 | height: 40px; 8 | 9 | &.active { 10 | background-color: $color-blue-dark; 11 | color: $color-white; 12 | } 13 | } 14 | 15 | .multi-chip { 16 | @include chip-base; 17 | 18 | border: 1px solid $color-blue-dark; 19 | color: $color-charcoal; 20 | } 21 | 22 | .single-chip { 23 | @include chip-base; 24 | 25 | border: 1px solid $color-grey; 26 | color: $color-grey-dark; 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/components/Inputs/_Textfield.scss: -------------------------------------------------------------------------------- 1 | // Internal Imports 2 | @use "../Basics" as *; 3 | @use "./ProtoInput" as *; 4 | 5 | /**************************** 6 | *** Classes for textfield *** 7 | ****************************/ 8 | 9 | @mixin textfield { 10 | @include input; 11 | 12 | flex: 1; 13 | z-index: $z-focus; 14 | } 15 | 16 | .textfield { 17 | @include textfield; 18 | } 19 | 20 | .textfield-left { 21 | @include textfield; 22 | @include empty-right-border; 23 | } 24 | 25 | .textfield-right { 26 | @include textfield; 27 | @include empty-left-border; 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/components/Basics/_utils.scss: -------------------------------------------------------------------------------- 1 | // Only visibile to screen readers, but not visully on screen 2 | // Code providd by: https://accessibility.18f.gov/hidden-content/ 3 | @mixin sr-only { 4 | border: 0; 5 | clip: rect(0 0 0 0); 6 | height: 1px; 7 | margin: -1px; 8 | overflow: hidden; 9 | padding: 0; 10 | position: absolute; 11 | width: 1px; 12 | } 13 | 14 | @mixin hidden { 15 | display: none; 16 | } 17 | 18 | .sr-only { 19 | @include sr-only; 20 | } 21 | 22 | .hidden { 23 | @include hidden; 24 | } 25 | 26 | .ovflow-hidden { 27 | overflow: hidden; 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/pages/NotFoundPage/_NotFoundPage.scss: -------------------------------------------------------------------------------- 1 | @use "@/components/Basics" as *; 2 | 3 | .not-found-container { 4 | margin: 234px auto; 5 | max-width: 904px; 6 | 7 | @include breakpoint-media-max("smtablet") { 8 | margin: 48px auto; 9 | } 10 | } 11 | 12 | .not-found-box { 13 | width: "348px"; 14 | } 15 | 16 | .not-found-title { 17 | @include breakpoint-media-max("smtablet") { 18 | @include title(4); 19 | } 20 | } 21 | 22 | .not-found-paragraph { 23 | max-width: 348px; 24 | 25 | @include breakpoint-media-max("smtablet") { 26 | @include paragraph(3); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { createRoot } from "react-dom/client"; 4 | import App from "./App"; 5 | import "./index.scss"; 6 | import "tailwindcss/tailwind.css"; 7 | import reactAxe from "@axe-core/react"; 8 | 9 | if (import.meta.env.MODE !== "production") { 10 | reactAxe(React, ReactDOM, 1000); 11 | } 12 | 13 | const container = document.getElementById("app"); 14 | if (container) { 15 | const root = createRoot(container); 16 | 17 | root.render( 18 | 19 | 20 | , 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # stage dockerignore 2 | 3 | dev 4 | stage 5 | mkdocs 6 | 7 | # Environment files 8 | **/.env 9 | **/*.env 10 | *venv/ 11 | **/.DS_store 12 | 13 | # Dependency directories 14 | **/node_modules 15 | 16 | # Django 17 | *.log 18 | *.pot 19 | *.pyc 20 | **/__pycache__/ 21 | **/db.sqlite3 22 | **/data 23 | **/media 24 | 25 | # Docker 26 | **/Dockerfile 27 | **/*.Dockerfile 28 | **/.dockerignore 29 | 30 | # git 31 | .git 32 | **/.gitignore 33 | 34 | # Builds 35 | backend/frontend_dist 36 | backend/staticfiles 37 | backend/openapi-schema.yml 38 | 39 | # vite 40 | frontend/vite.config.*.timestamp-* 41 | frontend/dist -------------------------------------------------------------------------------- /frontend/src/assets/images/svgs/icons/icon-checkbox-no.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/index.scss: -------------------------------------------------------------------------------- 1 | // Normalize all styles 2 | @use "@/components/normalize"; 3 | 4 | // Import basic styles 5 | @use "@/components/Basics" as *; 6 | 7 | // Import component styles 8 | @use "@/components/Carousel/ScrollCarousel"; 9 | @use "@/components/Inputs" as *; 10 | @use "@/components/Notification/Notification"; 11 | @use "@/components/Transition/Wrapper"; 12 | @use "@/components/Scroll/ChevronScroll"; 13 | 14 | // Import page styles 15 | @use "@/pages/Demo/Demo"; 16 | @use "@/pages/NotFoundPage/NotFoundPage"; 17 | @use "@/pages/LandingPage" as *; 18 | 19 | body { 20 | font-family: Roboto, Tahoma, Verdana, sans-serif; 21 | } 22 | -------------------------------------------------------------------------------- /dev/dev.env.example: -------------------------------------------------------------------------------- 1 | # environment file to configure docker containers 2 | 3 | # postgres 4 | POSTGRES_DB= 5 | POSTGRES_USER=postgres 6 | POSTGRES_PASSWORD= 7 | 8 | # Django 9 | ENVIRON=dev 10 | DEBUG=True 11 | SECRET_KEY= 12 | DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1] django 13 | SQL_ENGINE=django.db.backends.postgresql 14 | SQL_DATABASE= 15 | SQL_USER=postgres 16 | SQL_PASSWORD= 17 | SQL_HOST=pgdb 18 | SQL_PORT=5432 19 | DATABASE=postgres 20 | 21 | # Frontend 22 | MODE=development 23 | DEVTOOL=inline-source-map 24 | -------------------------------------------------------------------------------- /frontend/jest.config.js: -------------------------------------------------------------------------------- 1 | // Or async function 2 | module.exports = async () => { 3 | return { 4 | preset: "ts-jest/presets/js-with-ts", 5 | verbose: true, 6 | moduleFileExtensions: ["ts", "tsx", "js", "jsx"], 7 | moduleDirectories: ["node_modules", "src"], 8 | moduleNameMapper: { 9 | "\\.(css|less)$": "/tests/__mocks__/styleMock.js", 10 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": 11 | "/tests/__mocks__/fileMock.js", 12 | "\\.(svg)(\\?url)$": "/tests/__mocks__/fileMock.js", 13 | }, 14 | testEnvironment: "jsdom", 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /mkdocs/docs/joining-the-team/uiux-researcher.md: -------------------------------------------------------------------------------- 1 | # UI/UX Researcher 2 | 3 | ## Getting Started 4 | 5 | 1. Review the UI/UX issues on the [Project Management Board](https://github.com/orgs/hackforla/projects/37/views/5) and identify an actionable backlog item. 6 | 7 | 2. Communicate with the UI/UX Research Team Lead about your interest in being assigned a task. 8 | 9 | ## Research Documents 10 | 11 | - [Completed Research](research-completed) 12 | - [In Progress Research](research-inprogress) 13 | - [Researching being audited](research-audits) 14 | - [WIKI Template for Research](https://hackforla.github.io/CivicTechJobs/misc/research-wiki-template/) 15 | -------------------------------------------------------------------------------- /backend/backend/templates/backend/dev-mode.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Civic Tech Jobs 11 | 12 | 13 | 14 |

Welcome

15 |

You are running the CivicTechJobs django server in development mode.

16 |

Go to http://localhost:5175 to see the React frontend in development mode.

17 | 18 | 19 | -------------------------------------------------------------------------------- /frontend/src/components/Carousel/_ScrollCarousel.scss: -------------------------------------------------------------------------------- 1 | // Internal Imports 2 | @use "../Basics" as *; 3 | 4 | .scroll-carousel { 5 | display: flex; 6 | flex: 0 0 auto; 7 | 8 | & > * { 9 | flex: 0 0 auto; 10 | } 11 | } 12 | 13 | // TODO delete once done 14 | .scroll-carousel-cop { 15 | // matches small cop card 16 | width: 100vw; 17 | min-height: 550px; 18 | overflow-x: hidden; 19 | scrollbar-width: none; 20 | justify-self: center; 21 | 22 | & > * { 23 | width: 85vw; 24 | max-width: none; 25 | } 26 | 27 | &.hidden { 28 | @include hidden; 29 | } 30 | 31 | @include breakpoint-media-min("lgtablet") { 32 | @include hidden; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /stage/stage.env.example: -------------------------------------------------------------------------------- 1 | # postgres 2 | POSTGRES_DB= 3 | POSTGRES_USER= 4 | POSTGRES_PASSWORD= 5 | 6 | # Django 7 | ENVIRON=stage 8 | DEBUG=False 9 | SECRET_KEY= 10 | DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1] 11 | SQL_ENGINE=django.db.backends.postgresql 12 | SQL_DATABASE= 13 | SQL_USER= 14 | SQL_PASSWORD= 15 | SQL_HOST=pgdb 16 | SQL_PORT=5432 17 | DATABASE=postgres 18 | 19 | # SSL settings 20 | # These vars should all be `True` in prod 21 | CSRF_COOKIE_SECURE=False 22 | SESSION_COOKIE_SECURE=False 23 | SECURE_SSL_REDIRECT=False 24 | HSTS_ENABLED=False -------------------------------------------------------------------------------- /backend/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Prod + stage Docker container entrypoint script 4 | 5 | # Set ECS environment variables 6 | if [ "$TARGET" = "ECS" ]; then 7 | ECS_ALLOWED_HOST=$(curl -s "${ECS_CONTAINER_METADATA_URI}" | jq -r .Networks[0].IPv4Addresses[0]) 8 | export ECS_ALLOWED_HOST 9 | fi 10 | 11 | # Next 4 lines build the django server 12 | python manage.py collectstatic --noinput --clear --link && \ 13 | python manage.py makemigrations && \ 14 | python manage.py migrate && \ 15 | python manage.py generateschema --file openapi-schema.yml && \ 16 | 17 | # This command starts the daphne ASGI server 18 | daphne -b 0.0.0.0 -p 8000 backend.asgi:application 19 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | // Redirect civictechjobs.org to stage site 2 | if (window.location.hostname === "civictechjobs.org") { 3 | window.location.replace("https://stage.civictechjobs.org"); 4 | } 5 | 6 | // External imports 7 | import React, { Suspense } from "react"; 8 | import { RouterProvider } from "react-router-dom"; 9 | 10 | // Internal imports 11 | import router from "router/Router"; 12 | import CookieBanner from "tw-components/CookieBanner"; 13 | 14 | export default function App() { 15 | return ( 16 | <> 17 | Loading...}> 18 | 19 | 20 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/update-content-for-readme-file.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Update Content for ReadMe file 3 | about: Instructions for revising the README.md file inside this repository 4 | title: '' 5 | labels: documentation, good first issue, help wanted, question 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Overview 11 | We need to have a working READme file to easily on-board new team members. 12 | 13 | ### Action Items 14 | - [ ] Add the following information as a comment to this issue: 15 | - [ ] Identify what information needs to be collected. 16 | - [ ] Identify who holds each piece of information. 17 | - [ ] Collect information. 18 | 19 | ### Resources/Instructions 20 | PUT LINK TO README.md FILE HERE 21 | -------------------------------------------------------------------------------- /frontend/src/components/Basics/_alignment.scss: -------------------------------------------------------------------------------- 1 | .text-center { 2 | text-align: center; 3 | } 4 | 5 | .text-justify { 6 | text-align: justify; 7 | } 8 | 9 | .text-left { 10 | text-align: left; 11 | } 12 | 13 | .text-right { 14 | text-align: right; 15 | } 16 | 17 | .justify-center { 18 | justify-content: center; 19 | } 20 | 21 | .justify-left { 22 | justify-content: start; 23 | } 24 | 25 | .justify-right { 26 | justify-content: end; 27 | } 28 | 29 | .justify-between { 30 | justify-content: space-between; 31 | } 32 | 33 | .align-center { 34 | align-items: center; 35 | } 36 | 37 | .align-top { 38 | align-items: flex-start; 39 | } 40 | 41 | .align-bottom { 42 | align-items: flex-end; 43 | } 44 | -------------------------------------------------------------------------------- /frontend/src/components/components.tsx: -------------------------------------------------------------------------------- 1 | import { ScrollCarousel } from "./Carousel/ScrollCarousel"; 2 | import { Calendar } from "./Inputs/Calendar"; 3 | import { Chip } from "./Inputs/Chip"; 4 | import { Dropdown, DropdownOption } from "./Inputs/Dropdown"; 5 | import { TextField } from "./Inputs/Textfield"; 6 | import { Notification } from "./Notification/Notification"; 7 | import { TransitionWrapper } from "./Transition/Wrapper"; 8 | import { ChevronScroll } from "./Scroll/ChevronScroll"; 9 | 10 | export { 11 | ScrollCarousel, 12 | ChevronScroll, 13 | // Dialog 14 | // Inputs 15 | Calendar, 16 | Chip, 17 | Dropdown, 18 | DropdownOption, 19 | TextField, 20 | Notification, 21 | TransitionWrapper, 22 | }; 23 | -------------------------------------------------------------------------------- /.github/workflows/jest-react-test.yml: -------------------------------------------------------------------------------- 1 | name: Jest-React Test 2 | on: 3 | push: 4 | branches-ignore: [master, main] 5 | # Remove the line above to run when pushing to master 6 | pull_request: 7 | branches: [master, main] 8 | jobs: 9 | build: 10 | name: Jest-React Test 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout repo 14 | uses: actions/checkout@v4 15 | - name: Setup Node 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 18 19 | - name: Install modules 20 | run: | 21 | cd frontend 22 | npm install 23 | - name: Run test 24 | run: | 25 | cd frontend 26 | npm run test 27 | -------------------------------------------------------------------------------- /frontend/src/assets/images/svgs/icons/icon-dropdown-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/wave-chrome-extension--accessibility-review.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Wave Chrome Extension: Accessibility review' 3 | about: Describe this issue template's purpose here. 4 | title: 'Wave Chrome Extension: Accessibility review' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Overview 11 | 12 | #### Action Items 13 | 1. add [WAVE chrome extension](https://chrome.google.com/webstore/detail/wave-evaluation-tool/jbbplnpkjmmeebjpijfedlgcdilocofh) 14 | 2. visit site 15 | 3. click the extension and review the red flags. 16 | 4. Run the same steps for the development site (localhost). Ensure that the chrome extension has the "Allow access to file URLs" enabled. 17 | 5. Document all suggested changes in the comments. 18 | -------------------------------------------------------------------------------- /dev/linter.dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.15 2 | 3 | # install dependencies 4 | RUN apk add --no-cache \ 5 | bash~=5.1 \ 6 | docker~=20.10 \ 7 | git~=2.34 \ 8 | gcc~=10.3 \ 9 | libgcc~=10.3 \ 10 | libstdc++~=10.3 \ 11 | openrc~=0.44 \ 12 | python3~=3.9 \ 13 | python3-dev~=3.9 \ 14 | musl-dev~=1.2 \ 15 | py3-pip~=20.3 \ 16 | nodejs~=16.20 \ 17 | npm~=8.1.3 18 | 19 | 20 | # install python dependencies 21 | ENV PYTHONDONTWRITEBYTECODE 1 22 | ENV PYTHONUNBUFFERED=1 23 | RUN ln -sf python3 /usr/bin/python &&\ 24 | python -m ensurepip &&\ 25 | pip3 install --no-cache --no-cache-dir --ignore-installed --upgrade \ 26 | pip==22.1 \ 27 | pre-commit==2.19 \ 28 | setuptools==62.3 29 | 30 | 31 | WORKDIR /src 32 | ENTRYPOINT ["pre-commit"] 33 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build start stop build-linter lint migrations migrate db-shell test-server test-frontend 2 | 3 | # Run commands with `make ` 4 | 5 | build: 6 | docker compose build 7 | 8 | start: 9 | docker compose up 10 | 11 | stop: 12 | docker compose down 13 | 14 | build-linter: 15 | docker compose build linter 16 | 17 | lint: 18 | docker compose up linter 19 | 20 | # Below commands require the docker containers to be running 21 | 22 | migrations: 23 | docker compose exec django python manage.py makemigrations 24 | 25 | migrate: 26 | docker compose exec django python manage.py migrate 27 | 28 | db-shell: 29 | docker compose exec django python manage.py shell 30 | 31 | test-server: 32 | docker exec django python manage.py test server.tests -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /backend/ctj_api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path, re_path 2 | from rest_framework.routers import DefaultRouter 3 | 4 | from ctj_api import views 5 | 6 | router = DefaultRouter() 7 | router.register(r"opportunities", views.OpportunityViewSet) 8 | router.register(r"communityOfPractice", views.CommunityOfPracticeViewSet) 9 | router.register(r"roles", views.RoleViewSet) 10 | router.register(r"skills", views.SkillViewSet) 11 | router.register(r"projects", views.ProjectViewSet) 12 | 13 | urlpatterns = [ 14 | path("healthcheck", views.healthcheck, name="healthcheck"), 15 | re_path(r"^", include(router.urls)), 16 | path("users//", views.UserDetail.as_view()), 17 | # Catch-all for incorrect API routes 18 | re_path(r"^.*$", views.api_not_found), 19 | ] 20 | -------------------------------------------------------------------------------- /frontend/src/pages/QualifierPage/components/QualifierNav.tsx: -------------------------------------------------------------------------------- 1 | // External Imports 2 | import React from "react"; 3 | import clsx from "clsx"; 4 | 5 | interface QualifierNavProps { 6 | className?: string; 7 | children?: React.ReactNode; 8 | } 9 | 10 | function QualifierNav({ className, children }: QualifierNavProps) { 11 | return ( 12 |
18 |
19 | {children} 20 |
21 |
22 | ); 23 | } 24 | 25 | export { QualifierNav }; 26 | -------------------------------------------------------------------------------- /mkdocs/docs/joining-the-team/uiux-designer.md: -------------------------------------------------------------------------------- 1 | # UI/UX Designer 2 | 3 | ## Starting Checklist 4 | 5 | 1. Review the UI/UX issues on the [Project Management Board](https://github.com/hackforla/CivicTechJobs/projects/1?card_filter_query=label%3A%22role%3A+ui%2Fux%22) and identify an actionable backlog item. 6 | 7 | 1. Communicate with the UI/UX Design Team Lead about your interest in being assigned a task. 8 | 9 | ## Additional Reading 10 | 11 | 1. This is a [generic software development lifecycle diagram](https://drive.google.com/file/d/1emxhYv9N6KuCVrG-gnqkqHdGnjhm_Qvb/view?usp=sharing) for Hack for LA. We would like to talk to you about how this project is different. 12 | 13 | 1. Read about [WCAG 2.0 accessibility standards](https://medium.com/pulsar/which-accessibility-testing-tool-should-you-use-e5990e6ef0a). 14 | -------------------------------------------------------------------------------- /frontend/src/assets/images/svgs/logos/logo-logomark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /frontend/src/components/Inputs/_ProtoInput.scss: -------------------------------------------------------------------------------- 1 | // Internal Imports 2 | @use "../Basics" as *; 3 | 4 | /**************************** 5 | *** Classes for prototype *** 6 | ****************************/ 7 | 8 | @mixin input { 9 | background-color: $color-white; 10 | border: 1px solid $color-grey; 11 | border-radius: 8px; 12 | box-sizing: border-box; 13 | height: 44px; 14 | overflow: hidden; 15 | padding: 13px 16px; 16 | } 17 | 18 | @mixin empty-right-border { 19 | border-radius: 8px 0 0 8px; 20 | border-right: none; 21 | } 22 | 23 | @mixin empty-left-border { 24 | border-left: none; 25 | border-radius: 0 8px 8px 0; 26 | } 27 | 28 | .input-icon-left { 29 | @include input; 30 | @include empty-right-border; 31 | } 32 | 33 | .input-icon-right { 34 | @include input; 35 | @include empty-left-border; 36 | } 37 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Fixes # 4 | 5 | 6 | 7 | ### Changes 8 | 9 | - 10 | - 11 | - 12 | 13 | 14 | 15 | ### Screenshots, if applicable 16 | 17 |
18 | Title 19 |
20 | 21 |
22 |
23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/lighthouse--cross-origin-destinations-are-unsafe.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Lighthouse: Cross-origin destinations are unsafe' 3 | about: Instructions for addressing the cross-origin linking vulnerabilities 4 | title: 'Lighthouse Issue: Cross-origin destinations are unsafe' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Overview 11 | Links to cross-origin destinations are unsafe both from a security and performance perspective. 12 | 13 | ### Action Item 14 | Run [Lighthouse](https://developers.google.com/web/tools/lighthouse/) and then follow the instructions in [cross-origin destinations are unsafe] 15 | (https://developers.google.com/web/tools/lighthouse/audits/noopener) . 16 | 17 | ## Summary of instructions 18 | When using *target=_blank* also adding *rel="noopener"* to the tag ensures that new page runs in a separate process. 19 | -------------------------------------------------------------------------------- /docker-compose.stage.yml: -------------------------------------------------------------------------------- 1 | name: civic-tech-jobs-stage 2 | 3 | services: 4 | pgdb: 5 | image: postgres:16 6 | container_name: pgdb-stage 7 | # user should be the same as your POSTGRES_USER stage.env variable 8 | user: postgres 9 | volumes: 10 | - postgres_data:/lib/postgresql/data 11 | env_file: 12 | - stage/stage.env 13 | ports: 14 | - "5434:5434" 15 | healthcheck: 16 | test: [ "CMD-SHELL", "pg_isready" ] 17 | interval: 10s 18 | timeout: 5s 19 | retries: 5 20 | 21 | django: 22 | build: 23 | context: . 24 | dockerfile: ./stage/Dockerfile 25 | container_name: django-stage 26 | ports: 27 | - "8000:8000" 28 | env_file: 29 | - stage/stage.env 30 | depends_on: 31 | pgdb: 32 | condition: service_healthy 33 | 34 | volumes: 35 | postgres_data: 36 | -------------------------------------------------------------------------------- /frontend/src/components/Inputs/_Dropdown.scss: -------------------------------------------------------------------------------- 1 | // Internal Imports 2 | @use "../Basics" as *; 3 | @use "./ProtoInput" as *; 4 | 5 | .dropdown { 6 | @include input; 7 | @include empty-right-border; 8 | 9 | appearance: none; 10 | flex: 1; 11 | white-space: nowrap; 12 | z-index: $z-focus; 13 | } 14 | 15 | .dropdown-box { 16 | background-color: $color-white; 17 | border: 1px solid $color-grey; 18 | border-radius: 8px; 19 | height: 336px; 20 | list-style-type: none; 21 | overflow-y: scroll; 22 | z-index: $z-popup; 23 | } 24 | 25 | .dropdown-row { 26 | background-color: $color-white; 27 | min-height: 44px; 28 | 29 | & > div { 30 | padding: 13px 0; 31 | } 32 | 33 | &:not(:first-child) > div { 34 | border-top: 1px solid $color-grey; 35 | } 36 | 37 | &:hover { 38 | background-color: rgba($color-blue-dark, 15%); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mkdocs/docs/misc/security-updates.md: -------------------------------------------------------------------------------- 1 | # Security Updates 2 | 3 | This project subscribes to GitHub's automated security alerting service. Occasionally the repository home page may have a yellow banner saying "We have found a potential security vulnerability in one of your dependencies" and a link to view the security alert. If you see this, please check our issues list to see if anyone has added an issue for fixing this. If not, please create an issue for this problem. If you feel up to it, please assign the issue to yourself and try to fix it. As with any issue, once you have fixed it on your fork of the repository, push the fixes to your fork and then open a pull request to merge this fix into the main repository. 4 | 5 | **GitHub's "dependabot" may try to generate an automated pull request to fix this issue. Please do not accept this pull request without verifying that it works by applying the update on your local copy of the site.** 6 | -------------------------------------------------------------------------------- /frontend/src/assets/images/svgs/icons/icon-arrow-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/images/svgs/icons/icon-dropdown-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/lighthouse--accessibility---links.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Lighthouse: Accessibility - Links' 3 | about: AKA Links must have discernible text 4 | title: 'Lighthouse: Accessibility - Links' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Overview 11 | The formatting of links can make them readable or unreadable by screen readers. Which includes creating programmatic events for links without making them device specific (e.g., onfocus() instead of onmouseover(), etc.), and other ways of making sure all links are visible by screen readers. 12 | 13 | ### Action Items 14 | *If your site already has links* review the instructions and document the changes needed to bring your link(s) into WCAG compliance, by commenting on this issue. 15 | *If your site does not have links yet* review the instructions and design all new links using the WCAG standards. 16 | 17 | ### Instructions 18 | Deque University 19 | https://dequeuniversity.com/rules/axe/3.2/link-name 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/lighthouse--image-optimization.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Lighthouse: Image Optimization' 3 | about: Instructions for optimizing images 4 | title: 'Lighthouse: Image Optimization' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Overview 11 | When you run the lighthouse review it may suggest some specific image optimizations such as choosing another image format and making those changes may or may not improve your sites actual performance. 12 | 13 | ### Action Items 14 | Run lighthouse on a local version of the site and then apply suggested changes and retest locally before determining if you want to keep the changes. 15 | 16 | ### Instructions/Resources 17 | Google's Tools for Web Developers: [Optimize Images](https://developers.google.com/web/tools/lighthouse/audits/optimize-images) 18 | Read [closed issue #111](https://github.com/hackforla/website/issues/111) from when HackforLA.org did our audit, to see why we decided not to do the image optimization 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/lighthouse--accessibility---forms.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Lighthouse: Accessibility - Forms' 3 | about: Instructions for creating or improving forms to make them accessible when visitors 4 | use screen readers AKA Form elements must have labels 5 | title: 'Lighthouse: Accessibility - Forms' 6 | labels: '' 7 | assignees: '' 8 | 9 | --- 10 | 11 | ### Overview 12 | In order for your sites form(s) to be usable by visitors using screen readers all the form elements need labels. There are specific details and exceptions, which can be found in the instructions below. 13 | 14 | ### Action Items 15 | *If your site already has forms* review the instructions and document the changes needed to bring your form(s) into WCAG compliance, by commenting on this issue. 16 | *If your site does not have forms* review the instructions and design new forms using the WCAG standards. 17 | 18 | ### Instructions 19 | Deque University 20 | https://dequeuniversity.com/rules/axe/3.2/label 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/control-what-appears-when-you-paste-your-sites-link-in-social-media-sites.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Control what appears when you paste your sites link in social media sites 3 | about: Add Open Graph Markup tags to header 4 | title: Control what appears when you paste your sites link in social media sites 5 | labels: enhancement, question 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Overview 11 | When your site is shared on slack, facebook, twitter, etc. It should automatically display with an image and title instead of just the URL. 12 | 13 | ### Action items 14 | Identify what to put in the following fields: 15 | og:url 16 | g:type 17 | og:title 18 | og:description 19 | og:image 20 | og:type (optional) 21 | og:local (option) 22 | using the standards set forth in the instructions. 23 | 24 | Add content to header and test with the tool provided in the instructions. 25 | 26 | ### Instructions 27 | [A Guide to Sharing for Webmasters](https://developers.facebook.com/docs/sharing/webmasters#markup) 28 | -------------------------------------------------------------------------------- /frontend/tests/components/Button.test.tsx: -------------------------------------------------------------------------------- 1 | // External imports 2 | import React from "react"; 3 | import { render, screen } from "@testing-library/react"; 4 | import "@testing-library/jest-dom"; 5 | 6 | // Internal imports 7 | import { Button } from "tw-components"; 8 | 9 | describe("Button", () => { 10 | test("Button component", () => { 11 | render(); 12 | expect(screen.getByText("Log in")).toBeInTheDocument(); 13 | expect(screen.queryByText("Log out")).not.toBeInTheDocument(); 14 | }); 15 | 16 | test("Button tag accessibility", () => { 17 | render( 27 | 28 |
29 | 30 |
31 | 32 | 33 | 34 | 35 | ); 36 | } 37 | 38 | export { NotFoundPage }; 39 | -------------------------------------------------------------------------------- /frontend/src/components/Scroll/_ChevronScroll.scss: -------------------------------------------------------------------------------- 1 | @use "../Basics/_colors" as *; 2 | @use "@/components/Basics" as *; 3 | 4 | .chevron-scroll-outer-container { 5 | white-space: nowrap; 6 | display: flex; 7 | width: 1088px; 8 | height: 47px; 9 | } 10 | 11 | .chevron-scroll-child-container { 12 | scroll-snap-type: x mandatory; 13 | overflow: auto; 14 | display: flex; 15 | align-items: center; 16 | gap: 16px; 17 | scrollbar-width: none; /* Firefox */ 18 | } 19 | 20 | .chevron-scroll-child-container::-webkit-scrollbar { 21 | display: none; /* Chrome, Safari, Opera */ 22 | } 23 | 24 | .chevron-scroll-left-btn { 25 | background-color: white; 26 | padding: 0 32px 0 15px; 27 | cursor: pointer; 28 | border: none; 29 | 30 | &.hidden { 31 | @include hidden; 32 | } 33 | } 34 | 35 | .chevron-scroll-right-btn { 36 | background-color: white; 37 | padding: 0 15px 0 32px; 38 | border: none; 39 | cursor: pointer; 40 | 41 | &.hidden { 42 | @include hidden; 43 | } 44 | } 45 | 46 | .chevron-scroll-clear-btn { 47 | display: flex; 48 | align-items: center; 49 | text-decoration: underline; 50 | font-weight: 700; 51 | font-size: 20px; 52 | color: $charcoal; 53 | margin-left: 16px; 54 | background-color: transparent; 55 | padding: 0; 56 | border: none; 57 | cursor: pointer; 58 | } 59 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/dev-onboarding.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Dev Onboarding Template 3 | about: This issue has been created for our new Devs to familiarize themselves with 4 | CTJ project and set-up their dev environment. 5 | title: 'Dev Onboarding: [replace with your Name] ' 6 | labels: 'feature: Onboarding, size: 0.5pt' 7 | assignees: '' 8 | 9 | --- 10 | 11 | ### Overview 12 | 13 | This issue has been created for our new Developer [PLEASE ADD WHETHER FRONT-END OR BACK-END] to familiarize themselves with CTJ project and set-up their dev environment. 14 | 15 | ### Action Items 16 | 17 | - [ ] Review [Read Me Page](https://github.com/hackforla/CivicTechJobs) 18 | - [ ] Review [Mk Docs Project Documentation](https://hackforla.github.io/CivicTechJobs/) - please note we are working on updating this 19 | - [ ] Review [Wiki Onboarding [Start Here]](https://github.com/hackforla/CivicTechJobs/wiki/Onboarding-%5BStart-Here%5D) 20 | - [ ] Follow this guide to install the required downloads and set up a development environment: https://hackforla.github.io/CivicTechJobs/developer/installation/ 21 | 22 | Tasks 23 | 24 | - [ ] Get a development environment up and successfully running on your local machine 25 | - [ ] Pick up one "good first issue" on the project board 26 | 27 | ### Resources 28 | 29 | - [Resources](https://hackforla.github.io/CivicTechJobs/resources/) 30 | -------------------------------------------------------------------------------- /frontend/src/pages/LandingPage/_LandingPageCopCards.scss: -------------------------------------------------------------------------------- 1 | // Internal Imports 2 | @use "@/components/Basics" as *; 3 | 4 | /********************************** 5 | *** Classes for large COP cards *** 6 | **********************************/ 7 | .cop-card-lg { 8 | background-color: $color-grey-light; 9 | margin: 8px; // used to go over the box shadow 10 | max-width: 1088px; 11 | min-height: 624px; 12 | 13 | &.hidden { 14 | @include hidden; 15 | } 16 | 17 | @include breakpoint-media-max("smtablet") { 18 | @include hidden; 19 | } 20 | } 21 | 22 | .cop-card-lg-x { 23 | float: right; 24 | } 25 | 26 | .cop-card-lg-content { 27 | padding: { 28 | top: 56px; 29 | right: 7.6vw; 30 | left: 3.4vw; 31 | bottom: 40px; 32 | } 33 | } 34 | 35 | /********************************** 36 | *** Classes for small COP cards *** 37 | **********************************/ 38 | .cop-card-sm { 39 | background-color: $color-grey-light; 40 | margin: 8px; 41 | max-width: 312px; 42 | min-height: 600px; 43 | 44 | &.hidden { 45 | @include hidden; 46 | } 47 | 48 | @include breakpoint-media-min("lgtablet") { 49 | @include hidden; 50 | } 51 | } 52 | 53 | .cop-card-sm-x { 54 | float: right; 55 | } 56 | 57 | .cop-card-sm-content { 58 | padding: { 59 | top: 74px; 60 | right: 6.4vw; 61 | left: 6.4vw; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /frontend/src/components/Transition/Wrapper.tsx: -------------------------------------------------------------------------------- 1 | // External exports 2 | import React, { useState, useRef, useEffect } from "react"; 3 | import { CSSTransition } from "react-transition-group"; 4 | 5 | interface TransitionWrapper extends React.PropsWithChildren { 6 | autoExit?: boolean; 7 | onExited?: () => void; 8 | show?: boolean; 9 | } 10 | 11 | const TransitionWrapper = ({ 12 | autoExit = false, 13 | show = true, 14 | ...props 15 | }: TransitionWrapper) => { 16 | const [isNotificationOnDOM, setIsNotificationOnDOM] = useState(show); 17 | const nodeRef = useRef(null); 18 | const autoHiddenTimeout = 500; 19 | 20 | useEffect(() => { 21 | setIsNotificationOnDOM(show); 22 | }, [show]); 23 | 24 | return ( 25 | { 33 | if (autoExit) { 34 | window.setTimeout(() => { 35 | setIsNotificationOnDOM(false); 36 | }, autoHiddenTimeout); 37 | } 38 | }} 39 | onExited={() => { 40 | if (props.onExited) { 41 | props.onExited(); 42 | } 43 | }} 44 | > 45 |
{props.children}
46 |
47 | ); 48 | }; 49 | 50 | export { TransitionWrapper }; 51 | -------------------------------------------------------------------------------- /frontend/src/pages/QualifierPage/index.tsx: -------------------------------------------------------------------------------- 1 | // External Imports 2 | import React from "react"; 3 | import { useParams } from "react-router-dom"; 4 | 5 | // Internal Imports 6 | import { Stepper } from "./components/Stepper"; 7 | import { QualifiersProvider } from "context/QualifiersContext"; 8 | import { QualifierPageCalendar } from "./QualifierPageCalendar"; 9 | 10 | import { QualifierPage1 } from "./pages/QualifierPage1"; 11 | import { QualifierPage2 } from "./pages/QualifierPage2"; 12 | 13 | function Content({ page }: { page: string }) { 14 | switch (page) { 15 | case "1": 16 | return ; 17 | case "2": 18 | return ; 19 | case "3": 20 | return ; 21 | default: 22 | throw new Error("Page not found"); 23 | } 24 | } 25 | 26 | function QualifierPage() { 27 | let { page } = useParams(); 28 | if (!page) { 29 | page = "1"; 30 | } 31 | 32 | return ( 33 | 34 |
35 |
36 |

Qualifier Page

37 | 38 |
39 | 40 |
41 |
42 |
43 |
44 | ); 45 | } 46 | 47 | export default QualifierPage; 48 | -------------------------------------------------------------------------------- /mkdocs/docs/joining-the-team/product-manager.md: -------------------------------------------------------------------------------- 1 | **_DRAFT NOT YET FILLED OUT_** 2 | 3 | # Product Manager and Owner 4 | 5 | Each of the resources below can be considered a work-in-progress. These resources will evolve and adapt as the team and team needs change. Feel free to open an issue with a link to whichever resource needs improvement and a description of the suggested change. 6 | 7 | 1. Learn how we setup our [GitHub Kanban boards](https://docs.google.com/document/d/1CuRX6hhWzs8ydVCnl6OrGZ4LeVSk9X_pIzoKchAqFcU/edit?tab=t.0#heading=h.xwpqk588zdpy) and please comment if there is anything in the document that is unclear. 8 | 9 | 2. This [Software Development Lifecycle Diagram](https://drive.google.com/file/d/1emxhYv9N6KuCVrG-gnqkqHdGnjhm_Qvb/view?usp=sharing) is a sample of what the process is generally like at Hack for LA and each project is different. 10 | 11 | 3. Communicate with other product team members and leadership to discuss project priorities and strategic direction. 12 | 13 | 4. Review the [Project Board](https://github.com/orgs/hackforla/projects/37/views/4) and the [Product Management issues available on that board](https://github.com/orgs/hackforla/projects/37/views/7) to identify an actionable backlog item. 14 | 15 | 5. Review Hack for LA [Product Management Templates](https://github.com/hackforla/product-management/wiki#started). 16 | 17 | 6. Review our [OKRS](INSERT-PROJECT-OKRS-SPREADSHEET-LINK). 18 | -------------------------------------------------------------------------------- /frontend/src/components/Inputs/ProtoInput.tsx: -------------------------------------------------------------------------------- 1 | // Eternal Imports 2 | import React from "react"; 3 | 4 | // Internal Imports 5 | import { combineClasses } from "../Utility/utils"; 6 | 7 | interface ProtoInputProps extends React.PropsWithChildren { 8 | addClass?: string; 9 | icon?: React.ElementType; 10 | iconPosition?: "left" | "right"; 11 | id: string; 12 | label: string; 13 | labelHidden?: boolean; 14 | passRef?: React.RefCallback; 15 | } 16 | 17 | function ProtoInput({ 18 | iconPosition = "left", 19 | labelHidden = false, 20 | ...props 21 | }: ProtoInputProps) { 22 | return ( 23 |
24 | 30 |
31 | {iconPosition == "left" && props.icon && ( 32 | 33 | 34 | 35 | )} 36 | {props.children} 37 | {iconPosition == "right" && props.icon && ( 38 | 39 | 40 | 41 | )} 42 |
43 |
44 | ); 45 | } 46 | 47 | export { ProtoInput, ProtoInputProps }; 48 | -------------------------------------------------------------------------------- /frontend/src/assets/images/svgs/icons/icon-eye-open.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/images/svgs/communities-of-practice/cop-icon-engineering.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/src/assets/images/svgs/icons/icon-checkbox-yes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/lighthouse--how-to.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Lighthouse: How To' 3 | about: Provides overview of how to use Lighthouse and links to additional resources 4 | title: 'Lighthouse: How To' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Overview 11 | Lighthouse is an open-source, automated tool for improving the quality of web pages. You can run it against any web page, public or requiring authentication. It has audits for performance, accessibility, progressive web apps, and more. Hack For LA recommends that you run the tests and evaluate what changes you might want to make on your site to improve performance and accessability. 12 | 13 | ### How To Use 14 | Lighthouse is in the Audits panel of the Chrome DevTools. To run a report: 15 | 16 | 1. Download Google Chrome for Desktop. 17 | 2. In Google Chrome, go to the URL you want to audit. You can audit any URL on the web. 18 | 3. Open Chrome DevTools. 19 | 4. Click the Audits tab. 20 | 5. Click Perform an audit. DevTools shows you a list of audit categories. Leave them all enabled. 21 | 6. Click Run audit. After 60 to 90 seconds, Lighthouse gives you a report on the page. 22 | 23 | For more information go to : 24 | https://developers.google.com/web/tools/lighthouse/ 25 | 26 | ### Tip 27 | You will want to re-run lighthouse on any code changes before integrating them into your site. Sometimes the specific suggestions it makes, do not actually result in improved performance or can actually harm performance. 28 | -------------------------------------------------------------------------------- /frontend/vite.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig, loadEnv } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import tsconfigPaths from "vite-tsconfig-paths"; 4 | import svgr from "vite-plugin-svgr"; 5 | import { resolve } from "path"; 6 | 7 | export default defineConfig(({ mode }) => { 8 | process.env = { ...process.env, ...loadEnv(mode, process.cwd()) }; 9 | 10 | const SERVER_URL = `${ 11 | process.env.VITE_APP_SERVER_URL ?? "http://localhost:8000" 12 | }`; 13 | 14 | return { 15 | //to resolve relative file paths for sass (no plugin) 16 | resolve: { 17 | alias: { 18 | "@": resolve("./src"), 19 | }, 20 | }, 21 | plugins: [ 22 | svgr(), 23 | react(), 24 | //to resolve relative file paths for tsx 25 | tsconfigPaths(), 26 | ], 27 | server: { 28 | host: true, 29 | port: 5175, 30 | origin: "http://127.0.0.1:5175", 31 | proxy: { 32 | "/api": { 33 | target: `${SERVER_URL}/api`, 34 | changeOrigin: true, 35 | rewrite: (path) => path.replace(/^\/api/, ""), 36 | }, 37 | }, 38 | watch: { 39 | usePolling: true, 40 | }, 41 | }, 42 | build: { 43 | manifest: true, 44 | emptyOutDir: true, 45 | rollupOptions: { 46 | output: { 47 | assetFileNames: "static/[name]-[hash][extname]", 48 | chunkFileNames: "static/[name]-[hash].js", 49 | entryFileNames: "static/[name]-[hash].js", 50 | }, 51 | }, 52 | }, 53 | }; 54 | }); 55 | -------------------------------------------------------------------------------- /.github/workflows/deploy-stage.yml: -------------------------------------------------------------------------------- 1 | name: deploy-stage 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths-ignore: 7 | - "mkdocs/**" 8 | - "dev/**" 9 | workflow_dispatch: 10 | jobs: 11 | deploy: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | id-token: write 15 | contents: write 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: configure aws credentials 20 | uses: aws-actions/configure-aws-credentials@v3 21 | with: 22 | role-to-assume: arn:aws:iam::035866691871:role/incubator-cicd-civic-tech-jobs 23 | role-session-name: incubator-cicd-civic-tech-jobs 24 | aws-region: us-west-2 25 | 26 | - name: Login to Amazon ECR 27 | id: login-ecr 28 | uses: aws-actions/amazon-ecr-login@v1 29 | 30 | 31 | - name: Build, tag, and push the image to Amazon ECR 32 | id: build-push-image 33 | env: 34 | ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} 35 | ECR_REPOSITORY: civic-tech-jobs-fullstack 36 | IMAGE_TAG: stage 37 | run: | 38 | docker build -f ./stage/Dockerfile -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . 39 | docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG 40 | 41 | - name: Redeploy Image in Amazon ECS-Fargate 42 | id: redeploy-service 43 | env: 44 | CLUSTER_NAME: incubator-prod 45 | SERVICE_NAME: civic-tech-jobs-fs-stage 46 | run: | 47 | aws ecs update-service --force-new-deployment --service $SERVICE_NAME --cluster $CLUSTER_NAME -------------------------------------------------------------------------------- /frontend/src/components/Basics/_breakpoints.scss: -------------------------------------------------------------------------------- 1 | /* 2 | BREAKPOINTS: 3 | <= 576px for mobile phones 4 | 577px-768px for small tablets 5 | 769px-1024px for tablets, small laptops 6 | 1025px-1200px for medium laptops, small monitors 7 | >= 1201px large laptops, medium monitors, desktops 8 | */ 9 | 10 | /* There is a hidden mobile breakpoint that uses max-width calculated as smtablet - 1px */ 11 | $breakpoints-min: ( 12 | "smtablet": 577px, 13 | "lgtablet": 769px, 14 | "laptop": 1025px, 15 | "desktop": 1201px, 16 | ); 17 | 18 | /* There is a hidden desktop breakpoint that uses min-width calculated as laptop + 1px */ 19 | $breakpoints-max: ( 20 | "mobile": 576px, 21 | "smtablet": 768px, 22 | "lgtablet": 1024px, 23 | "laptop": 1200px, 24 | ); 25 | 26 | // Note: At this device and ABOVE, use @content properties & values. 27 | @mixin media-min($device) { 28 | @if $device == mobile { 29 | @media only screen and (max-width: (map-get($map: $breakpoints-min, $key: smtablet) - 1px)) { 30 | @content; 31 | } 32 | } @else { 33 | @media only screen and (min-width: map-get($map: $breakpoints-min, $key: $device)) { 34 | @content; 35 | } 36 | } 37 | } 38 | 39 | // Note: At this device and BELOW, use @content properties & values. 40 | @mixin media-max($device) { 41 | @if $device == desktop { 42 | @media only screen and (min-width: (map-get($map: $breakpoints-max, $key: laptop) + 1px)) { 43 | @content; 44 | } 45 | } @else { 46 | @media only screen and (max-width: map-get($map: $breakpoints-max, $key: $device)) { 47 | @content; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/src/pages/CreditsPage/TopSvg.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { FC, SVGProps } from "react"; 3 | 4 | const TopSvg: FC> = (props) => ( 5 | 12 | 13 | 18 | 22 | 27 | 28 | 29 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | ); 46 | 47 | export default TopSvg; 48 | -------------------------------------------------------------------------------- /frontend/src/components/Utility/utils.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | /** 4 | * Takes an array of different and combine them into one string to be placed in an element's class attribute. 5 | * @param {...any} args an array of anything 6 | * @returns a joined string after filtering out non-strings from args 7 | */ 8 | function combineClasses(...args: (string | boolean | undefined)[]) { 9 | return args.filter((x) => typeof x === "string").join(" "); 10 | } 11 | 12 | /** 13 | * A wrapper function that takes in a function and any number of strings denoting key values. When the key is passed as part of a browser event, the function is run if the key passed in matches the key value. 14 | * @param {*} fn a function that is called after a key is pressed 15 | * @param {...string[]} keyValues strings denoting key values 16 | * @returns a function that runs only when the event key passed in matches any of the keyValues 17 | */ 18 | 19 | type Handler = (() => void) | React.EventHandler; 20 | function onKey(fn: Handler, ...keyValues: string[]) { 21 | return (e: React.KeyboardEvent) => { 22 | if (keyValues.includes(e.key)) { 23 | e.preventDefault(); 24 | fn(e); 25 | } 26 | }; 27 | } 28 | 29 | // Credit to mdn documentation for the function https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#sequence_generator_range 30 | function range(start: number, stop: number, step: number = 1) { 31 | return Array.from( 32 | { length: (stop - start) / step + 1 }, 33 | (_, i) => start + i * step, 34 | ); 35 | } 36 | 37 | export { combineClasses, onKey, range }; 38 | -------------------------------------------------------------------------------- /frontend/src/components/Inputs/Textfield.tsx: -------------------------------------------------------------------------------- 1 | // Eternal Imports 2 | import React, { useId } from "react"; 3 | 4 | // Internal Imports 5 | import { combineClasses } from "../Utility/utils"; 6 | import { ProtoInput, ProtoInputProps } from "./ProtoInput"; 7 | 8 | interface TextFieldProps 9 | extends Omit { 10 | addInputClass?: string; 11 | onChange?: React.ChangeEventHandler; 12 | placeholder?: string; 13 | type?: 14 | | "text" 15 | | "date" 16 | | "datetime-local" 17 | | "email" 18 | | "month" 19 | | "number" 20 | | "password" 21 | | "search" 22 | | "tel" 23 | | "time" 24 | | "url" 25 | | "week"; 26 | } 27 | 28 | function TextField({ 29 | iconPosition = "left", 30 | labelHidden = false, 31 | type = "text", 32 | ...props 33 | }: TextFieldProps) { 34 | const textFieldId = useId(); 35 | 36 | return ( 37 | 45 | { 55 | if (props.onChange) props.onChange(e); 56 | }} 57 | placeholder={props.placeholder} 58 | /> 59 | 60 | ); 61 | } 62 | 63 | export { TextField }; 64 | -------------------------------------------------------------------------------- /stage/Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile for building the stage env django image 2 | 3 | # ----- Stage 1: build the react frontend ----- 4 | FROM node:20-alpine3.19 AS react-builder 5 | WORKDIR /code 6 | 7 | # Install frontend dependencies 8 | COPY ./frontend/package.json . 9 | COPY ./frontend/package-lock.json . 10 | RUN npm install 11 | 12 | # Build vite static assets 13 | COPY ./frontend/ . 14 | RUN npm run build 15 | 16 | 17 | # ----- Stage 2: set up django backend ----- 18 | FROM python:3.12-alpine AS django-builder 19 | 20 | RUN mkdir -p /usr/src/app 21 | WORKDIR /usr/src/app 22 | 23 | # Set up environment 24 | ENV PYTHONDONTWRITEBYTECODE=1 25 | ENV PYTHONUNBUFFERED=1s 26 | 27 | # Install system dependencies 28 | RUN apk update && apk upgrade && \ 29 | apk add --no-cache gcc g++ musl-dev curl libffi-dev postgresql-dev zlib-dev jpeg-dev freetype-dev 30 | 31 | # Set up the shell to fail on any command error, improving reliability 32 | SHELL ["/bin/ash", "-o", "pipefail", "-c"] 33 | 34 | # Download Poetry into Path 35 | RUN curl -sSL https://install.python-poetry.org | python3 - 36 | ENV PATH="${PATH}:/root/.local/bin" 37 | 38 | # Install python project dependencies with poetry 39 | COPY backend/pyproject.toml . 40 | COPY backend/poetry.lock . 41 | RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi 42 | COPY ./backend/ . 43 | 44 | # Copy frontend application static files into django project 45 | COPY --from=react-builder /code/dist/ ./frontend_dist/ 46 | 47 | # Set run permission on entrypoint.sh 48 | RUN chmod +x entrypoint.sh 49 | 50 | # ENTRYPOINT contains the start server script 51 | ENTRYPOINT ["./entrypoint.sh"] 52 | -------------------------------------------------------------------------------- /frontend/tests/pages/LandingPage.test.tsx: -------------------------------------------------------------------------------- 1 | // External imports 2 | import React from "react"; 3 | import { render, screen } from "@testing-library/react"; 4 | import "@testing-library/jest-dom"; 5 | import userEvent from "@testing-library/user-event"; 6 | import "regenerator-runtime/runtime"; // needed to run async tests 7 | import { config } from "react-transition-group"; 8 | 9 | // Internal imports 10 | import { LandingPage } from "pages/LandingPage/LandingPage"; 11 | import { MemoryRouter } from "react-router-dom"; 12 | 13 | // Disables animation transition time so it will not hamper testing 14 | config.disabled = true; 15 | 16 | describe("Landing Page", () => { 17 | test("Landing Page dialog", async () => { 18 | const user = userEvent.setup(); 19 | render( 20 | 21 | 22 | , 23 | ); 24 | 25 | expect(screen.getByRole("presentation")).toHaveClass("hidden"); 26 | 27 | await user.click( 28 | screen.getByText(/Engineering/, { 29 | selector: ".landing-cop-circle-title", 30 | }), 31 | ); 32 | expect(await screen.findByRole("presentation")).not.toHaveClass("hidden"); 33 | 34 | await user.click(screen.getByLabelText("close")); 35 | expect(await screen.findByRole("presentation")).toHaveClass("hidden"); 36 | 37 | await user.click( 38 | screen.getByText(/Data Science/, { 39 | selector: ".landing-cop-circle-title", 40 | }), 41 | ); 42 | expect(await screen.findByRole("presentation")).not.toHaveClass("hidden"); 43 | 44 | await user.click(screen.getByRole("presentation")); 45 | expect(await screen.findByRole("presentation")).toHaveClass("hidden"); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /mkdocs/docs/joining-the-team/intro.md: -------------------------------------------------------------------------------- 1 | Welcome to the Civic Tech Jobs team! This guide will help you get up to speed with everything you need to know to get started as a project team member. 2 | 3 | ### Let's go! 4 | 5 | 1. Before proceeding, please confirm that you have reviewed and completed the steps outlined in Hack for LA’s [Guide for New Volunteers](https://www.hackforla.org/getting-started) 6 | 2. Read [Introduction to the Project](https://hackforla.github.io/CivicTechJobs/) if you haven't already read it. 7 | 3. Check the [Community of Practice -> Open Roles](https://github.com/orgs/hackforla/projects/67/views/8) for current open roles in the Civic Tech Jobs project. Please follow the steps outlined in the respective open role issue. 8 | 4. Attend our monthly all team meeting on the third Tuesday at 5pm PST. You can find the link pinned in our [Civic Tech Jobs slack channel](https://hackforla.slack.com/archives/C02509WHFQQ). 9 | 10 | --- 11 | 12 | ### Who are you? 13 | 14 | Click the link that applies to you to find a sequence of immediate action steps: 15 | 16 | [Web Developer](Web-Developer) 17 | 18 | [UI/UX Designer](UI-UX-Designer) 19 | 20 | [UI/UX Researcher](UI-UX-Researcher) 21 | 22 | [Product Manager/Owner](Product-Manager-and-Owner) 23 | 24 | [Other Volunteer](Other-Volunteer) 25 | 26 | --- 27 | 28 | ### Accessibility standards 29 | 30 | Title III of the Americans with Disabilities Act (ADA) requires that all sites be accessible to people with disabilities. The World Wide Web (W3C) Consortium's Web Content Accessibility Guidelines (WCAG) 2.0 Level AA function as the current legal standard for site accessibility. 31 | 32 | Get acquainted with accessibility: https://www.ada.gov/pcatoolkit/chap5toolkit.htm 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/blank-issue-form.yml: -------------------------------------------------------------------------------- 1 | name: "Blank Issue Form (w/o Dependency)" 2 | description: "Form version of HackforLA blank issue template without dependency" 3 | 4 | body: 5 | - type: textarea 6 | id: overview 7 | attributes: 8 | label: Overview 9 | description: Clearly state the purpose of this issue in 2 sentences or less 10 | placeholder: | 11 | Example: As a [developer/programmer/designer/member of the team/etc], we need to [insert some sort of standard, such as "keep out code clean" or "fix bugs" or "make sure website appears welcoming"]. For this issue, we will [do something to fulfill our standard, such as "edit the code in the data folder" or "correct a bug in out components" or "increase the title size on the homepage"]. 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: action-items 16 | attributes: 17 | label: Action Items 18 | description: "List the research to be done, or the steps to be completed. Note: If the steps can be divided into tasks for more than one person, we recommend dividing it up into separate issues, or assigning it as a pair programming task." 19 | value: | 20 | - [ ] Item 1 21 | - [ ] Item 2 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: resources-instructions 26 | attributes: 27 | label: "Resources/Instructions" 28 | description: "Provide links to resources or instructions that may help with this issue. This can include files to be worked on, external sites with solutions, documentation, etc." 29 | value: | 30 | [Resources](https://hackforla.github.io/CivicTechJobs/resources/) 31 | validations: 32 | required: true 33 | -------------------------------------------------------------------------------- /mkdocs/docs/joining-the-team/web-developer.md: -------------------------------------------------------------------------------- 1 | # Web Developer 2 | 3 | ## Starting Checklist 4 | 5 | 1. Read the development [Readme](https://github.com/hackforla/[INSERT-REPO-NAME]/blob/main/README.md) and the [CONTRIBUTING.md](https://github.com/hackforla/CivicTechJobs/blob/main/CONTRIBUTING.md) file set up your development environment. 6 | 7 | 1. After setting up your enviornment, review the project board by the type of issue you are looking for. The [Frontend Coding Project Board](https://github.com/orgs/hackforla/projects/37/views/4?filterQuery=label%3A%22role%3A+frontend%22) or the [Backend Coding Project Board](https://github.com/orgs/hackforla/projects/37/views/4?filterQuery=label%3A%22role%3A+backend%22). Or you can check out the entire [project board](https://github.com/hackforla/CivicTechJobs/projects/37) here. 8 | 9 | 1. Communicate with the Developer Team Lead about your interest in being assigned a task. 10 | 11 | 1. Your first commit will likely be an issue labeled good first issue. Check [the board](https://github.com/hackforla/CivicTechJobs/projects/37) for those issues. Don't worry if you don't see anything now, we are working on it. 12 | 13 | ## Our Tech Stack 14 | 15 | #### Languages: TypeScript, Python, SCSS, JavaScript, Dockerfile, Shell, HTML, Makefile, CSS 16 | #### Technologies: React, Express, Node.JS, Docker, PostgreSQL, Django, Figma 17 | 18 | ## Additional Reading 19 | 20 | 1. Read about [WCAG 2.0 accessibility standards](https://hackforla.github.io/CivicTechJobs/misc/ada-guide/) and set up third-party tools for compliance testing. 21 | 22 | 1. Review notes on [security updates](https://hackforla.github.io/CivicTechJobs/misc/security-updates/). 23 | 24 | 1. [Site architecture document]() is coming soon. 25 | -------------------------------------------------------------------------------- /frontend/src/pages/QualifierPage/components/ProgressIndicator.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface ProgressIndicatorProps { 4 | currentPart: number; 5 | totalParts: number; 6 | title: string; 7 | progressPercentage: number; // New prop 8 | } 9 | 10 | export const ProgressIndicator: React.FC = ({ 11 | currentPart, 12 | totalParts, 13 | title, 14 | progressPercentage, 15 | }) => { 16 | // Ensure progressPercentage is clamped between 0 and 100 17 | const validProgressPercentage = Math.min( 18 | Math.max(progressPercentage, 0), 19 | 100, 20 | ); 21 | const strokeDashoffset = 62.8 - (62.8 * validProgressPercentage) / 100; 22 | 23 | return ( 24 |
25 | 32 | 40 | 51 | 52 |
53 | 54 | Part {currentPart} of {totalParts} 55 | 56 | {title} 57 |
58 |
59 | ); 60 | }; 61 | -------------------------------------------------------------------------------- /frontend/src/components/Inputs/Chip.tsx: -------------------------------------------------------------------------------- 1 | // Eternal Imports 2 | import { iconCheckMark, iconPlus } from "assets/images/images"; 3 | import React, { useEffect, useState } from "react"; 4 | 5 | // Internal Imports 6 | import { combineClasses } from "../Utility/utils"; 7 | 8 | interface ChipProps { 9 | addClass?: string; 10 | checked?: boolean; 11 | onClick?: (active: boolean, value: string) => unknown; 12 | value: string; 13 | variant?: "single" | "multi"; 14 | } 15 | 16 | function Chip({ 17 | addClass, 18 | checked = false, 19 | onClick, 20 | value, 21 | variant = "single", 22 | }: ChipProps) { 23 | const [active, isActive] = useState(checked); 24 | 25 | useEffect(() => { 26 | isActive(checked); 27 | }, [checked]); 28 | 29 | function handleClick() { 30 | if (onClick) onClick(!active, value); 31 | isActive(!active); 32 | } 33 | 34 | function MultiSelectIcon() { 35 | if (active) { 36 | return ( 37 | 43 | ); 44 | } else { 45 | return ( 46 | 52 | ); 53 | } 54 | } 55 | 56 | return ( 57 | 72 | ); 73 | } 74 | 75 | export { Chip }; 76 | -------------------------------------------------------------------------------- /frontend/src/pages/CreditsPage/Card.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface CardProps { 4 | key: number; 5 | name: string; 6 | usedIn: string; 7 | provider: string; 8 | imgSrc: string; 9 | imgContainerStyleClasses: string; 10 | imgStyleClasses: string; 11 | learnMoreLink: string; 12 | } 13 | 14 | const Card: React.FC = ({ 15 | name, 16 | usedIn, 17 | provider, 18 | imgSrc, 19 | imgContainerStyleClasses, 20 | imgStyleClasses, 21 | learnMoreLink, 22 | }) => { 23 | return ( 24 |
25 |
26 | {name} 27 |
28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
Name:{name}
Used In:{usedIn}
Provider:{provider}
46 | 47 | 55 |
56 |
57 | ); 58 | }; 59 | 60 | export default Card; 61 | -------------------------------------------------------------------------------- /frontend/src/components/Basics/_spacing.scss: -------------------------------------------------------------------------------- 1 | // External Imports 2 | @use "sass:list"; 3 | @use "sass:map"; 4 | 5 | // Internal Imports 6 | @use "./breakpoints" as bp; 7 | 8 | $margin-abbrs: ( 9 | "m": margin, 10 | "mt": margin-top, 11 | "mr": margin-right, 12 | "mb": margin-bottom, 13 | "ml": margin-left, 14 | "mx": margin-right margin-left, 15 | "my": margin-top margin-bottom, 16 | ); 17 | $padding-abbrs: ( 18 | "p": padding, 19 | "pt": padding-top, 20 | "pr": padding-right, 21 | "pb": padding-bottom, 22 | "pl": padding-left, 23 | "px": padding-right padding-left, 24 | "py": padding-top padding-bottom, 25 | ); 26 | $gap-abbrs: ( 27 | "gap": gap, 28 | "gap-x": column-gap, 29 | "gap-y": row-gap, 30 | ); 31 | $spacing-abbrs: map.merge(map.merge($margin-abbrs, $padding-abbrs), $gap-abbrs); 32 | $spacing-sizes: 0, 1, 2, 3, 4, 5, 6; 33 | 34 | /*********************************************** 35 | *** Spacing classes for margins and paddings *** 36 | ***********************************************/ 37 | 38 | @mixin spacing-size($name, $size) { 39 | @if list.index("mx" "my" "px" "py", $name) { 40 | @each $attr in map.get($spacing-abbrs, $name) { 41 | #{$attr}: $size * 8px; 42 | } 43 | } @else { 44 | #{map.get($spacing-abbrs, $name)}: $size * 8px; 45 | } 46 | } 47 | 48 | @each $name in map.keys($spacing-abbrs) { 49 | @each $size in $spacing-sizes { 50 | .#{$name}-#{$size} { 51 | @include spacing-size($name, $size); 52 | } 53 | } 54 | } 55 | 56 | /************************************************ 57 | *** Spacing classes concerning responsiveness *** 58 | ************************************************/ 59 | 60 | @mixin spacing-responsive($device, $type, $size) { 61 | @include bp.media-max($device) { 62 | @include spacing-size($type, $size); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | name: civic-tech-jobs 2 | 3 | services: 4 | pgdb: 5 | image: postgres:16 6 | container_name: pgdb 7 | # user should be the same as your POSTGRES_USER .env variable 8 | user: postgres 9 | env_file: 10 | - dev/dev.env 11 | volumes: 12 | - postgres_data:/lib/postgresql/data 13 | ports: 14 | - "5432:5432" 15 | healthcheck: 16 | test: [ "CMD-SHELL", "pg_isready" ] 17 | interval: 10s 18 | timeout: 5s 19 | retries: 5 20 | 21 | django: 22 | build: 23 | context: backend 24 | dockerfile: ../dev/django.dockerfile 25 | container_name: django 26 | command: > 27 | sh -c "python manage.py makemigrations && 28 | python manage.py migrate && 29 | python manage.py runserver 0.0.0.0:8000 && 30 | python manage.py generateschema --file openapi-schema.yml" 31 | ports: 32 | - "8000:8000" 33 | env_file: 34 | - dev/dev.env 35 | depends_on: 36 | pgdb: 37 | condition: service_healthy 38 | develop: 39 | watch: 40 | - action: sync 41 | path: ./backend 42 | target: /usr/src/app 43 | 44 | vite: 45 | build: 46 | context: frontend 47 | dockerfile: ../dev/vite.Dockerfile 48 | container_name: vite 49 | env_file: 50 | - dev/dev.env 51 | environment: 52 | - VITE_APP_SERVER_URL=django://django:8000 53 | - VITE_APP_CLIENT_URL=vite://vite:5175 54 | ports: 55 | - "5175:5175" 56 | develop: 57 | watch: 58 | - action: sync 59 | path: ./frontend 60 | target: /usr/src/app 61 | volumes: 62 | - ./frontend:/usr/src/app 63 | - /usr/src/app/node_modules 64 | 65 | volumes: 66 | postgres_data: {} 67 | -------------------------------------------------------------------------------- /frontend/tests/components/Notification.test.tsx: -------------------------------------------------------------------------------- 1 | // External imports 2 | import React from "react"; 3 | import { 4 | render, 5 | screen, 6 | waitForElementToBeRemoved, 7 | } from "@testing-library/react"; 8 | import "@testing-library/jest-dom"; 9 | import userEvent from "@testing-library/user-event"; 10 | import { config } from "react-transition-group"; 11 | 12 | // Internal imports 13 | import { Notification } from "components/components"; 14 | 15 | config.disabled = true; 16 | 17 | describe("Notification", () => { 18 | test("Notification component", () => { 19 | render(This is a warning); 20 | expect(screen.getByText("This is a warning")).toBeInTheDocument(); 21 | expect(screen.getByRole("status")).toBeInTheDocument(); 22 | expect(screen.queryByRole("alert")).not.toBeInTheDocument(); 23 | }); 24 | 25 | test("Notification component closable", async () => { 26 | const user = userEvent.setup(); 27 | render( 28 | 29 | This is a warning 30 | , 31 | ); 32 | await user.click(screen.getByRole("button")!); 33 | expect(screen.getByRole("status", { hidden: true })).toHaveClass("hidden"); 34 | }); 35 | 36 | test("Notification component autoHidden", async () => { 37 | // const user = userEvent.setup(); 38 | render( 39 | 40 | This is a warning 41 | , 42 | ); 43 | await waitForElementToBeRemoved(screen.getByRole("status"), { 44 | timeout: 10, 45 | }).catch(() => { 46 | expect(screen.getByText("This is a warning")).toBeInTheDocument(); 47 | expect(screen.queryByRole("alert")).not.toBeInTheDocument(); 48 | }); 49 | await waitForElementToBeRemoved(screen.getByRole("status"), { 50 | timeout: 1000, 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /frontend/src/components/Carousel/ClickCarousel.tsx: -------------------------------------------------------------------------------- 1 | // External Imports 2 | import PropTypes from "prop-types"; 3 | import React, { useEffect, useState } from "react"; 4 | 5 | // Internal Imports 6 | import { combineClasses } from "../Utility/utils"; 7 | import { Button } from "tw-components"; 8 | 9 | function ClickCarousel({ hidden = false, selected = 0, ...props }) { 10 | const [items, setItems] = useState([]); 11 | const [lastIndex, setLastIndex] = useState(0); 12 | const [index, setIndex] = useState(selected); 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | const [isHidden, setHidden] = useState(hidden); 15 | 16 | useEffect(() => { 17 | setItems(props.items); 18 | setLastIndex(props.items.length - 1); 19 | }, [props.items]); 20 | 21 | function handleClick(increase: boolean) { 22 | if (increase) { 23 | if (index == lastIndex) { 24 | setIndex(0); 25 | } else { 26 | setIndex(index + 1); 27 | } 28 | } else { 29 | if (index == 0) { 30 | setIndex(lastIndex); 31 | } else { 32 | setIndex(index - 1); 33 | } 34 | } 35 | } 36 | 37 | return ( 38 |
45 | 48 | 51 | {items ? items[index] : ""} 52 |
53 | ); 54 | } 55 | 56 | // Type declaration for props 57 | ClickCarousel.propTypes = { 58 | addClass: PropTypes.string, 59 | hidden: PropTypes.bool, 60 | items: PropTypes.arrayOf(PropTypes.element), 61 | selected: PropTypes.number, 62 | }; 63 | 64 | export { ClickCarousel }; 65 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_language_version: 2 | # default language version for each language used in the repository 3 | python: python3.9 4 | repos: 5 | - repo: https://github.com/pre-commit/pre-commit-hooks 6 | rev: v4.2.0 7 | hooks: 8 | # See https://pre-commit.com/hooks.html for more hooks 9 | - id: check-ast 10 | - id: check-case-conflict 11 | - id: check-executables-have-shebangs 12 | - id: check-merge-conflict 13 | - id: debug-statements 14 | - id: end-of-file-fixer 15 | - id: name-tests-test 16 | args: ["--django"] 17 | - id: trailing-whitespace 18 | - repo: https://github.com/pycqa/isort 19 | rev: 5.12.0 20 | hooks: 21 | - id: isort 22 | args: ["--settings-file", "app/setup.cfg"] 23 | - repo: https://github.com/psf/black 24 | rev: 22.12.0 25 | hooks: 26 | - id: black 27 | args: [] 28 | - repo: https://github.com/PyCQA/flake8 29 | rev: 6.0.0 30 | hooks: 31 | - id: flake8 32 | args: ["--config", "app/setup.cfg"] 33 | additional_dependencies: 34 | [ 35 | "flake8-bugbear", 36 | "flake8-comprehensions", 37 | "flake8-mutable", 38 | "flake8-print", 39 | "flake8-simplify", 40 | ] 41 | - repo: https://github.com/pycqa/pylint 42 | rev: "v2.15.10" 43 | hooks: 44 | - id: pylint 45 | exclude: "[a-zA-Z]*/(migrations)/(.)*|(mkdocs)/(.)*" 46 | args: 47 | [ 48 | "--load-plugins=pylint_django", 49 | "--disable=C0114,C0115,C0116", 50 | "--django-settings-module=config.settings", 51 | "--rcfile=app/setup.cfg", 52 | ] 53 | additional_dependencies: [django, djangorestframework, pylint_django] 54 | - repo: https://github.com/pre-commit/mirrors-prettier 55 | rev: "v2.7.1" 56 | hooks: 57 | - id: prettier -------------------------------------------------------------------------------- /.github/linters/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | # Template is found at https://github.com/github/super-linter/tree/main/TEMPLATES 2 | ############################# 3 | ############################# 4 | ## JavaScript Linter rules ## 5 | ############################# 6 | ############################# 7 | 8 | ############ 9 | # Env Vars # 10 | ############ 11 | env: 12 | browser: true 13 | es6: true 14 | jest: true 15 | 16 | ############### 17 | # Global Vars # 18 | ############### 19 | globals: 20 | Atomics: readonly 21 | SharedArrayBuffer: readonly 22 | 23 | ############### 24 | # Parser vars # 25 | ############### 26 | parser: "@typescript-eslint/parser" 27 | parserOptions: 28 | ecmaVersion: 2018 29 | sourceType: module 30 | project: "../frontend/tsconfig.json" 31 | tsconfigRootDir: __dirname 32 | 33 | ignorePatterns: 34 | - "**/*.config.js" 35 | 36 | ########### 37 | # Plugins # 38 | ########### 39 | plugins: 40 | - "@typescript-eslint" 41 | - "jsx-a11y" 42 | 43 | extends: 44 | - plugin:jsx-a11y/recommended 45 | 46 | settings: 47 | jsx-a11y: 48 | components: 49 | Button: button 50 | IconButton: button 51 | 52 | ######### 53 | # Rules # 54 | ######### 55 | rules: {} 56 | 57 | ############################## 58 | # Overrides for JSON parsing # 59 | ############################## 60 | overrides: 61 | # JSON files 62 | - files: 63 | - "*.json" 64 | extends: 65 | - plugin:jsonc/recommended-with-json 66 | parser: jsonc-eslint-parser 67 | parserOptions: 68 | jsonSyntax: JSON 69 | 70 | # JSONC files 71 | - files: 72 | - "*.jsonc" 73 | extends: 74 | - plugin:jsonc/recommended-with-jsonc 75 | parser: jsonc-eslint-parser 76 | parserOptions: 77 | jsonSyntax: JSONC 78 | 79 | # JSON5 files 80 | - files: 81 | - "*.json5" 82 | extends: 83 | - plugin:jsonc/recommended-with-json5 84 | parser: jsonc-eslint-parser 85 | parserOptions: 86 | jsonSyntax: JSON5 87 | -------------------------------------------------------------------------------- /frontend/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import pluginJs from "@eslint/js"; 3 | import tseslint from "typescript-eslint"; 4 | import pluginReact from "eslint-plugin-react"; 5 | import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; 6 | import eslintPluginTailwindcss from "eslint-plugin-tailwindcss"; 7 | import eslintPluginReactHooks from "eslint-plugin-react-hooks"; 8 | import eslintPluginJsxA11y from "eslint-plugin-jsx-a11y"; 9 | 10 | export default [ 11 | { files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] }, 12 | { 13 | languageOptions: { 14 | globals: globals.browser, 15 | parser: "@typescript-eslint/parser", 16 | }, 17 | }, 18 | { 19 | settings: { 20 | react: { 21 | version: "detect", 22 | }, 23 | }, 24 | }, 25 | { 26 | rules: { 27 | "no-unused-vars": "warn", 28 | "no-console": "warn", 29 | indent: ["error", 2], 30 | "no-irregular-whitespace": "error", 31 | "prettier/prettier": [ 32 | "error", 33 | { 34 | endOfLine: "auto", 35 | }, 36 | ], 37 | "react/no-unescaped-entities": "off", 38 | "@typescript-eslint/no-unused-vars": ["error"], 39 | "tailwindcss/no-contradicting-classname": "error", 40 | "tailwindcss/no-unnecessary-arbitrary-value": "error", 41 | "tailwindcss/classnames-order": "error", 42 | "react-hooks/rules-of-hooks": "error", 43 | "react-hooks/exhaustive-deps": "warn", 44 | "jsx-a11y/alt-text": "error", 45 | }, 46 | }, 47 | { 48 | ignores: ["node_modules/", "dist/", "*.config.js", "tests/__mocks__/*"], 49 | }, 50 | pluginJs.configs.recommended, 51 | ...tseslint.configs.recommended, 52 | pluginReact.configs.flat.recommended, 53 | eslintPluginPrettierRecommended, 54 | { 55 | plugins: { 56 | tailwindcss: eslintPluginTailwindcss, 57 | "react-hooks": eslintPluginReactHooks, 58 | "jsx-a11y": eslintPluginJsxA11y, 59 | }, 60 | }, 61 | ]; 62 | -------------------------------------------------------------------------------- /backend/ctj_api/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework import permissions 2 | 3 | 4 | class OpportunityPermission(permissions.BasePermission): 5 | """ 6 | Only PM users can create new opportunities. 7 | Only the creator of an opportunity can edit it. 8 | """ 9 | 10 | def has_permission(self, request, view): 11 | """ 12 | Check global permissions for the request method. 13 | """ 14 | # Allow safe methods for all users 15 | if request.method in permissions.SAFE_METHODS: 16 | return True 17 | 18 | # Only PM's can create opportunities 19 | if request.method == "POST": 20 | return getattr(request.user, "isProjectManager", False) 21 | 22 | # For PUT and DELETE, defer to object-level permissions 23 | if request.method in ["PUT", "DELETE"]: 24 | return request.user.is_authenticated 25 | 26 | return False 27 | 28 | def has_object_permission(self, request, view, obj): 29 | """ 30 | Check object-level permissions for the request method. 31 | """ 32 | # Read permissions are allowed to any request, 33 | # so we'll always allow GET, HEAD or OPTIONS requests. 34 | if request.method in permissions.SAFE_METHODS: 35 | return True 36 | 37 | # PUT permissions are only allowed to the PM that created the opportunity. 38 | if request.method == "PUT": 39 | return obj.created_by == request.user 40 | 41 | # Any PM can delete any opportunity 42 | if request.method == "DELETE": 43 | return getattr(request.user, "isProjectManager", False) 44 | 45 | return False 46 | 47 | 48 | class UserDetailPermission(permissions.BasePermission): 49 | """ 50 | Only the authenticated user can access and modify their own user data 51 | """ 52 | 53 | def has_object_permission(self, request, view, obj): 54 | return obj.id == request.user.id 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/blank-issue-form-d.yml: -------------------------------------------------------------------------------- 1 | name: "Blank Issue Form (w/ Dependency)" 2 | description: "Form version of HackforLA blank issue template with dependency" 3 | labels: ["Dependency"] 4 | 5 | body: 6 | - type: textarea 7 | id: dependency-explanation 8 | attributes: 9 | label: Dependency 10 | description: If there is a dependency, please explain what it is. 11 | placeholder: | 12 | - [ ] Dependency 1 13 | validations: 14 | required: true 15 | - type: textarea 16 | id: overview 17 | attributes: 18 | label: Overview 19 | description: Clearly state the purpose of this issue in 2 sentences or less 20 | placeholder: | 21 | Example: As a [developer/programmer/designer/member of the team/etc], we need to [insert some sort of standard, such as "keep out code clean" or "fix bugs" or "make sure website appears welcoming"]. For this issue, we will [do something to fulfill our standard, such as "edit the code in the data folder" or "correct a bug in out components" or "increase the title size on the homepage"]. 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: action-items 26 | attributes: 27 | label: Action Items 28 | description: "List the research to be done, or the steps to be completed. Note: If the steps can be divided into tasks for more than one person, we recommend dividing it up into separate issues, or assigning it as a pair programming task." 29 | value: | 30 | - [ ] Item 1 31 | - [ ] Item 2 32 | validations: 33 | required: true 34 | - type: textarea 35 | id: resources-instructions 36 | attributes: 37 | label: "Resources/Instructions" 38 | description: "Provide links to resources or instructions that may help with this issue. This can include files to be worked on, external sites with solutions, documentation, etc." 39 | value: | 40 | [Resources](https://hackforla.github.io/CivicTechJobs/resources/) 41 | validations: 42 | required: true 43 | -------------------------------------------------------------------------------- /frontend/src/components/Utility/hooks/dragToSelectUnselect.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { cell } from "../../Inputs/Calendar"; 3 | 4 | function dissect(str: string, partition: number = 7) { 5 | const arr = str.match(new RegExp(`.{1,${partition}}`, "g")); 6 | const nestedArr = []; 7 | if (arr) { 8 | for (const substring of arr) { 9 | nestedArr.push(substring.split("")); 10 | } 11 | } 12 | return nestedArr; 13 | } 14 | 15 | function connect(nested_arr: string[][]) { 16 | const arr = []; 17 | for (const subArr of nested_arr) { 18 | arr.push(subArr.join("")); 19 | } 20 | return arr.join(""); 21 | } 22 | 23 | function useDragState() { 24 | const [isMouseDown, setIsMouseDown] = useState(false); 25 | const [toSelect, setToSelect] = useState(false); 26 | return [isMouseDown, setIsMouseDown, toSelect, setToSelect] as const; 27 | } 28 | 29 | // sets the state to selected or deselected 30 | function useDragToSelectUnselect(cell: cell, data_: string, toSelect: boolean) { 31 | const [nextdata, setData] = useState(data_); 32 | const { row, col } = cell; 33 | const [next, setNext] = useState(CellStatus()); 34 | const handleSelect = (isMouseDown: boolean) => { 35 | if (isMouseDown) { 36 | const newrow = row - 1; 37 | const newcol = col - 1; 38 | const nestedArr = dissect(data_); 39 | if (nestedArr) { 40 | nestedArr[newrow][newcol] = toSelect ? "1" : "0"; 41 | setData(connect(nestedArr)); 42 | setNext(nestedArr[newrow][newcol] === "1"); 43 | } 44 | } 45 | return; 46 | }; 47 | 48 | function CellStatus() { 49 | const newrow = row - 1; 50 | const newcol = col - 1; 51 | const nestedArr = dissect(nextdata); 52 | if (nestedArr) { 53 | if (nestedArr[newrow][newcol] === "1") { 54 | return true; 55 | } else { 56 | return false; 57 | } 58 | } else { 59 | return false; 60 | } 61 | } 62 | return [next, nextdata, handleSelect] as const; 63 | } 64 | 65 | export { useDragState, useDragToSelectUnselect }; 66 | -------------------------------------------------------------------------------- /mkdocs/docs/misc/history.md: -------------------------------------------------------------------------------- 1 | # History 2 | 3 | How Hack for LA evolved from in person onboarding to remote, and the iterative approach that we have taken to arrive at the need for a dedicated interface to list volunteer opportunities. 4 | 5 | ## Pre-Covid times 6 | 7 | Hack for LA practiced in person recruiting on onboarding nights. It had its own benefits, like each volunteer felt valued and had agency 8 | 9 | ## 2020-mid 2021 10 | 11 | The team later moved on to a new process where the open roles on each team were posted on the Hack For LA site. This led to: 12 | 13 | - Some volunteers contacted teams directly and the teams became responsible for all onboarding, which was inefficient use of time, leading to poor cohesiveness of our org (onboarding conducted differently by various people and projects). And some projects got too many volunteers but not the right skill level. Ultimately this method led to high turnover of volunteers and product managers getting burnt out. 14 | - Other volunteers attending a Zoom onboarding session (held weekly). In this process the project leads would show up to recruit at the end, but sometimes there would be no one who could fill the domain specific role. This process was also inefficient. 15 | 16 | ## mid 2021- current 17 | 18 | Hack for LA moved to a new model where all new volunteer attend onboarding and then join a communities of practice (CoP) and no open roles are posted on the hackforla.org site. These CoPs have volunteer opportunity boards so that when project leads recruit, they can go to a larger group of people who are more likely to be a good fit for the role available. Also CoP leads can help provide coaching if someone is unsure of if they are a good fit. Listings at CoP allow the org to determine if the project is actually ready to receive volunteers. 19 | 20 | The Hack for LA organization team has now green lighted a project to create a dedicated job board page where volunteers can search and find volunteer opportunities that match their goal and aspirations while still maintaining the involvement of onboarding and CoPs. 21 | -------------------------------------------------------------------------------- /frontend/src/router/Router.tsx: -------------------------------------------------------------------------------- 1 | // External Imports 2 | import React, { Suspense, lazy } from "react"; 3 | import { createBrowserRouter } from "react-router-dom"; 4 | 5 | // Internal Imports 6 | import { CreditsPage } from "pages/CreditsPage/CreditsPage"; 7 | import { Demo } from "pages/Demo/Demo"; 8 | import DemoTailwind from "pages/Demo/DemoTailwind"; 9 | import { NotFoundPage } from "pages/NotFoundPage/NotFoundPage"; 10 | import { LandingPage } from "pages/LandingPage/LandingPage"; 11 | import AuthenticationPage from "pages/Authentication/page"; 12 | import HomeLayout from "layouts/HomeLayout"; 13 | import DefaultNavLayout from "layouts/DefaultNavLayout"; 14 | import PrivacyPolicyPage from "pages/PrivacyPolicyPage/PrivacyPolicyPage"; 15 | 16 | const QualifierPage = lazy(() => import("../pages/QualifierPage")); 17 | 18 | const router = createBrowserRouter([ 19 | { 20 | path: "/", 21 | element: , 22 | errorElement: , 23 | children: [ 24 | { 25 | path: "/", 26 | element: , 27 | children: [ 28 | { 29 | index: true, 30 | element: , 31 | }, 32 | { 33 | path: "qualifier/:page", 34 | element: ( 35 | ...loading}> 36 | 37 | 38 | ), 39 | }, 40 | { 41 | path: "credits", 42 | element: , 43 | }, 44 | { 45 | path: "demo", 46 | element: , 47 | }, 48 | { 49 | path: "demo-tailwind", 50 | element: , 51 | }, 52 | { 53 | path: "privacypolicy", 54 | element: , 55 | }, 56 | ], 57 | }, 58 | ], 59 | }, 60 | { 61 | path: "login", 62 | element: , 63 | }, 64 | { 65 | path: "signup", 66 | element: , 67 | }, 68 | ]); 69 | 70 | export default router; 71 | -------------------------------------------------------------------------------- /frontend/src/tw-components/AccordionFaq.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Typography from "tw-components/Typography"; 3 | import { IconDropdownDown, IconDropdownUp } from "assets/images/images"; 4 | 5 | type FaqItem = { 6 | id: string; 7 | question: string; 8 | answer: string[]; 9 | }; 10 | 11 | const AccordionItem: React.FC> = ({ question, answer }) => { 12 | const [isOpen, setIsOpen] = React.useState(false); 13 | 14 | const toggleAccordion = () => { 15 | setIsOpen(!isOpen); 16 | }; 17 | 18 | return ( 19 |
20 |
24 | 25 | {question} 26 | 27 | 28 |
29 | {isOpen ? ( 30 | 31 | ) : ( 32 | 33 | )} 34 |
35 |
36 | 37 | {isOpen && ( 38 |
39 | {answer.map((paragraph, index) => ( 40 | 44 | {paragraph} 45 | 46 | ))} 47 |
48 | )} 49 |
50 | ); 51 | }; 52 | 53 | // Main FAQ Component: 54 | type AccordionFaqProps = { 55 | items: FaqItem[]; 56 | }; 57 | 58 | const AccordionFaq: React.FC = ({ items }) => { 59 | return ( 60 |
61 | {items.map((item) => ( 62 |
66 | 67 |
68 | ))} 69 |
70 | ); 71 | }; 72 | 73 | export { AccordionFaq }; 74 | -------------------------------------------------------------------------------- /frontend/tests/components/Calendar.test.tsx: -------------------------------------------------------------------------------- 1 | // External imports 2 | import React from "react"; 3 | import { render, screen, fireEvent, waitFor } from "@testing-library/react"; 4 | import "@testing-library/jest-dom"; 5 | import "regenerator-runtime/runtime"; 6 | import { config } from "react-transition-group"; 7 | 8 | // Internal imports 9 | import { Calendar } from "components/components"; 10 | 11 | config.disabled = true; 12 | 13 | describe("Calendar", () => { 14 | test("Calendar Component", async () => { 15 | render( {}} />); 16 | expect(screen.getByTestId("calendar-root")).toBeInTheDocument(); 17 | }); 18 | 19 | test("Able to Drag to Select and Unselect Availability", async () => { 20 | const { container } = render( {}} />); 21 | const calendarCells = container.querySelectorAll(".calendar-cell"); 22 | const checkbox1 = calendarCells[0].querySelector('[role="checkbox"]'); 23 | const checkbox2 = calendarCells[1].querySelector('[role="checkbox"]'); 24 | if (checkbox1 && checkbox2) { 25 | fireEvent.mouseDown(checkbox1); 26 | fireEvent.mouseMove(checkbox1); 27 | fireEvent.mouseMove(checkbox2); 28 | await waitFor(() => { 29 | expect(calendarCells[0]).toHaveClass("selected"); 30 | expect(calendarCells[1]).toHaveClass("selected"); 31 | }); 32 | fireEvent.mouseDown(checkbox2); 33 | fireEvent.mouseMove(checkbox2); 34 | fireEvent.mouseMove(checkbox1); 35 | await waitFor(() => { 36 | expect(calendarCells[0]).not.toHaveClass("selected"); 37 | expect(calendarCells[1]).not.toHaveClass("selected"); 38 | }); 39 | } 40 | }); 41 | 42 | test("Calendar Accessibility Labels are Applied", () => { 43 | render( {}} />); 44 | const cells = screen.getAllByRole("checkbox"); 45 | cells.forEach((cell, index) => { 46 | const row = Math.floor(index / 7) + 1; 47 | const col = (index % 7) + 1; 48 | expect(cell).toHaveAttribute( 49 | "aria-label", 50 | `I am available on ${row}, ${col}`, 51 | ); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/component-issue-form.yml: -------------------------------------------------------------------------------- 1 | name: "Component Issue Form" 2 | description: "Issue form for Design to Dev hand-off issues concerning components" 3 | title: "[Component]: " 4 | labels: ["role: frontend"] 5 | 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | ## Before we begin: 11 | 12 | Please give an appropriate title to the issue. Make sure to include the name or names of the component/s in question in the title. For example: 13 | 14 | > #### [Component]: Update dropdown and buttons with new colors 15 | 16 | --- 17 | - type: textarea 18 | id: dependency-explanation 19 | attributes: 20 | label: Dependency 21 | description: If there is a dependency, please explain what it is. 22 | placeholder: | 23 | - [ ] Dependency 1 24 | - type: textarea 25 | id: overview 26 | attributes: 27 | label: Overview 28 | description: Please replace [name] with the name or names of the components this issue concerns. 29 | value: | 30 | As a designer, we need to create or update our component library so that our designs are reusable and designers and developers are on the same page. For this issue, we will create or make updates to the [name] component/s. 31 | validations: 32 | required: true 33 | - type: textarea 34 | id: action-items 35 | attributes: 36 | label: Action Items 37 | description: "Please skip this portion." 38 | value: | 39 | - [ ] Item 1 40 | - [ ] Item 2 41 | validations: 42 | required: true 43 | - type: textarea 44 | id: resources-instructions 45 | attributes: 46 | label: "Resources/Instructions" 47 | description: "Please provide links to the Figma page of the component/s, and add extra notes as necessary for developers to locate and understand the design. Feel free to provide other necessary information such as related issues, files to be worked on, external sites with solutions, documentation, etc." 48 | value: | 49 | [Resources](https://hackforla.github.io/CivicTechJobs/resources/) 50 | validations: 51 | required: true 52 | -------------------------------------------------------------------------------- /frontend/src/tw-components/FooterNav.tsx: -------------------------------------------------------------------------------- 1 | // External imports 2 | import React, { Fragment } from "react"; 3 | import { Link } from "react-router-dom"; 4 | 5 | // Internal imports 6 | import { Button } from "tw-components"; 7 | import { logoHorizontalOnDark, logoStackedOnDark } from "assets/images/images"; 8 | 9 | interface menuObject { 10 | name?: string; 11 | link: string; 12 | } 13 | 14 | const menuItems: menuObject[] = [ 15 | { name: "Credits", link: "credits" }, 16 | { name: "Sitemap", link: "#" }, 17 | { name: "Join Us", link: "qualifier/1" }, 18 | ]; 19 | 20 | const Logo = () => { 21 | return ( 22 | 23 | Civic Tech Jobs - Home 28 | Civic Tech Jobs - Home 33 | 34 | ); 35 | }; 36 | 37 | function FooterNav() { 38 | return ( 39 |
40 | 41 | 56 |
57 | 64 |
65 |
66 | ); 67 | } 68 | 69 | export default FooterNav; 70 | -------------------------------------------------------------------------------- /frontend/src/components/Basics/_font.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: Roboto; 3 | src: url("/src/assets/fonts/Roboto-Thin.ttf") format("truetype"); 4 | font-weight: 100; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: Roboto; 10 | src: url("/src/assets/fonts/Roboto-ThinItalic.ttf") format("truetype"); 11 | font-weight: 100; 12 | font-style: italic; 13 | } 14 | 15 | @font-face { 16 | font-family: Roboto; 17 | src: url("/src/assets/fonts/Roboto-Light.ttf") format("truetype"); 18 | font-weight: 300; 19 | font-style: normal; 20 | } 21 | 22 | @font-face { 23 | font-family: Roboto; 24 | src: url("/src/assets/fonts/Roboto-LightItalic.ttf") format("truetype"); 25 | font-weight: 300; 26 | font-style: italic; 27 | } 28 | 29 | @font-face { 30 | font-family: Roboto; 31 | src: url("/src/assets/fonts/Roboto-Regular.ttf") format("truetype"); 32 | font-weight: 400; 33 | font-style: normal; 34 | } 35 | 36 | @font-face { 37 | font-family: Roboto; 38 | src: url("/src/assets/fonts/Roboto-Italic.ttf") format("truetype"); 39 | font-weight: 400; 40 | font-style: italic; 41 | } 42 | 43 | @font-face { 44 | font-family: Roboto; 45 | src: url("/src/assets/fonts/Roboto-Medium.ttf") format("truetype"); 46 | font-weight: 500; 47 | font-style: normal; 48 | } 49 | 50 | @font-face { 51 | font-family: Roboto; 52 | src: url("/src/assets/fonts/Roboto-MediumItalic.ttf") format("truetype"); 53 | font-weight: 500; 54 | font-style: italic; 55 | } 56 | 57 | @font-face { 58 | font-family: Roboto; 59 | src: url("/src/assets/fonts/Roboto-Bold.ttf") format("truetype"); 60 | font-weight: 700; 61 | font-style: normal; 62 | } 63 | 64 | @font-face { 65 | font-family: Roboto; 66 | src: url("/src/assets/fonts/Roboto-BoldItalic.ttf") format("truetype"); 67 | font-weight: 700; 68 | font-style: italic; 69 | } 70 | 71 | @font-face { 72 | font-family: Roboto; 73 | src: url("/src/assets/fonts/Roboto-Black.ttf") format("truetype"); 74 | font-weight: 900; 75 | font-style: normal; 76 | } 77 | 78 | @font-face { 79 | font-family: Roboto; 80 | src: url("/src/assets/fonts/Roboto-BlackItalic.ttf") format("truetype"); 81 | font-weight: 900; 82 | font-style: italic; 83 | } 84 | -------------------------------------------------------------------------------- /frontend/src/components/Notification/Notification.tsx: -------------------------------------------------------------------------------- 1 | // External Imports 2 | import { combineClasses } from "components/Utility/utils"; 3 | import React, { useEffect, useState } from "react"; 4 | 5 | // Internal Imports 6 | import { IconButton } from "tw-components"; 7 | import { iconX } from "assets/images/images"; 8 | import { TransitionWrapper } from "components/components"; 9 | 10 | interface NotificationProps extends React.PropsWithChildren { 11 | autoHidden?: boolean; 12 | closable?: boolean; 13 | fade?: boolean; 14 | role?: "status" | "alert"; 15 | show?: boolean; 16 | children: React.ReactNode; 17 | } 18 | 19 | function Notification({ 20 | autoHidden = false, 21 | closable = false, 22 | fade = false, 23 | role = "status", 24 | show = true, 25 | ...props 26 | }: NotificationProps) { 27 | const [isHidden, setIsHidden] = useState(false); 28 | const [isShow, setIsShow] = useState(show); 29 | 30 | useEffect(() => { 31 | setIsShow(show); 32 | if (!show) { 33 | setIsHidden(true); 34 | } 35 | }, [show]); 36 | 37 | const CloseButton = () => { 38 | function handleClick() { 39 | if (autoHidden || fade) { 40 | setIsShow(false); 41 | } else { 42 | setIsHidden(true); 43 | } 44 | } 45 | 46 | return ( 47 | { 52 | handleClick(); 53 | }} 54 | /> 55 | ); 56 | }; 57 | 58 | const Bar = () => { 59 | return ( 60 |
70 | {closable && } 71 |
{props.children}
72 |
73 | ); 74 | }; 75 | 76 | return fade || autoHidden ? ( 77 | 78 | 79 | 80 | ) : ( 81 | 82 | ); 83 | } 84 | 85 | export { Notification }; 86 | -------------------------------------------------------------------------------- /frontend/src/tw-components/TextField.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { UseFormRegister, FieldError } from "react-hook-form"; 3 | import { IconEyeOpen } from "assets/images/images"; 4 | 5 | interface FormValues { 6 | password: string; 7 | } 8 | 9 | interface TextFieldProps extends React.PropsWithChildren { 10 | label: string; 11 | id: string; 12 | type: "text" | "email" | "password"; 13 | register: UseFormRegister; 14 | validations?: object; 15 | errors?: FieldError | undefined; 16 | } 17 | 18 | /** Reusable input group component 19 | * 20 | * @prop label -> Label for the input 21 | * @prop id -> ID for the input which also allows label to be linked to input 22 | * @prop type -> The type of input (text, email, password) may add more later 23 | * @prop register -> React Hook Form's register function 24 | * @prop validations -> React Hook Form's validation object 25 | * @prop errors -> React Hook Form's errors object 26 | */ 27 | 28 | export default function TextField({ 29 | label, 30 | id, 31 | type, 32 | register, 33 | validations, 34 | errors, 35 | }: TextFieldProps) { 36 | return ( 37 |
38 |
39 | 40 | {type === "password" && ( 41 | 42 | Forgot password? 43 | 44 | )} 45 |
46 |
47 | 57 | {type === "password" && ( 58 |
59 | 60 |
61 | )} 62 |
63 | 64 |
65 | {errors && errors.message} 66 |
67 |
68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /frontend/src/tw-components/HeaderNav.tsx: -------------------------------------------------------------------------------- 1 | // External Imports 2 | import React from "react"; 3 | import { Link } from "react-router-dom"; 4 | 5 | // Internal Imports 6 | import { logoHorizontal } from "assets/images/images"; 7 | import { IconHamburgerMenu } from "assets/images/images"; 8 | import { Button } from "tw-components"; 9 | 10 | interface menuObject { 11 | name?: string; 12 | link: string; 13 | } 14 | 15 | const menuItems: menuObject[] = [ 16 | { name: "Hack for LA", link: "https://www.hackforla.org/" }, 17 | { 18 | name: "How to Join", 19 | link: "https://www.hackforla.org/getting-started", 20 | }, 21 | { name: "Projects", link: "https://www.hackforla.org/projects/" }, 22 | ]; 23 | 24 | const Logo = () => { 25 | return ( 26 | 27 | Civic Tech Jobs - Home 32 | 33 | ); 34 | }; 35 | 36 | function HeaderNav() { 37 | return ( 38 |
39 |
40 | 41 |
42 | 43 |
44 | 61 | 62 | 63 | 64 | 65 | 73 |
74 |
75 | ); 76 | } 77 | 78 | export default HeaderNav; 79 | -------------------------------------------------------------------------------- /frontend/src/tw-components/Dialog.tsx: -------------------------------------------------------------------------------- 1 | // External Imports 2 | import React, { useState, useEffect, useRef } from "react"; 3 | import { cn } from "lib/utils"; 4 | 5 | interface DialogProps extends React.PropsWithChildren { 6 | className?: string; 7 | ariaLabel: string; 8 | onClose: () => void; 9 | open: boolean; 10 | } 11 | 12 | function Dialog({ open = false, ...props }: DialogProps) { 13 | const [isBackdropOpen, setIsBackdropOpen] = useState(false); 14 | const windowRef = useRef(null); 15 | const nodeRef = useRef(null); 16 | 17 | // Adjust padding on body when scrollbar is hidden so that page content does not jump 18 | useEffect(() => { 19 | if (isBackdropOpen) { 20 | const scrollWidth = Math.abs( 21 | window.innerWidth - document.documentElement.clientWidth, 22 | ); 23 | document.body.style.paddingRight = `${scrollWidth}px`; 24 | document.body.style.overflow = "hidden"; 25 | } else { 26 | document.body.style.removeProperty("padding-right"); 27 | document.body.style.overflow = "auto"; 28 | } 29 | }, [isBackdropOpen]); 30 | 31 | useEffect(() => { 32 | if (open) { 33 | setIsBackdropOpen(true); 34 | } else { 35 | setIsBackdropOpen(false); 36 | } 37 | }, [open]); 38 | 39 | function handleClose(e: React.MouseEvent) { 40 | if (e.target === windowRef.current) { 41 | props.onClose(); 42 | } 43 | } 44 | 45 | return ( 46 |
57 |
68 | {props.children} 69 |
70 |
71 | ); 72 | } 73 | 74 | export default Dialog; 75 | -------------------------------------------------------------------------------- /frontend/src/pages/LandingPage/LandingPageIntro.tsx: -------------------------------------------------------------------------------- 1 | // External Imports 2 | import React from "react"; 3 | import { Link } from "react-router-dom"; 4 | 5 | // Internal Imports 6 | import { 7 | iconArrowDown, 8 | LandingPageBg, 9 | LandingPageFg, 10 | } from "assets/images/images"; 11 | 12 | function LandingPageIntro() { 13 | return ( 14 |
15 |
16 |

17 | Together, 18 |
19 | we can create greater civic change 20 |

21 |

22 | CivicTechJobs unites ambitious technology practitioners with volunteer 23 | opportunities from a central hub of listings to build digital 24 | products, programs, and services. 25 |

26 | 30 | Join us 31 | 32 |
36 | 37 |
38 |
39 | 40 |
41 | Arrow Down Icon 46 |

47 | Our Mission 48 |

49 |

50 | We bring together civic-minded volunteers from diverse backgrounds 51 | such as YOU to help local communities and governments. Thanks to the 52 | power of our volunteers, we are able to positively impact the 53 | communities of Los Angeles region and beyond! 54 |

55 |
56 |
57 | ); 58 | } 59 | 60 | export { LandingPageIntro }; 61 | -------------------------------------------------------------------------------- /frontend/src/pages/Authentication/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useLocation } from "react-router-dom"; 3 | import { AuthNav } from "tw-components"; 4 | 5 | import LoginForm from "./LoginForm"; 6 | import SignupForm from "./SignupForm"; 7 | 8 | import { loginTanBg, dotsSvg, loginIllustration } from "assets/images/images"; 9 | 10 | /** AuthenticationPage 11 | * @dev handles both "/login" and "/signup" paths 12 | */ 13 | export default function AuthenticationPage() { 14 | const { pathname } = useLocation(); 15 | 16 | return ( 17 | <> 18 | 19 |
23 |
24 |
25 |

26 | Together, we can create 27 |
civic change. 28 |

29 | Team work Pana Illustration 34 |
35 | Tan background for login/register page 40 | Corner dots pattern 45 | Corner dots pattern 50 |
51 |
52 |
53 |
54 |
55 | {pathname === "/login" && } 56 | {pathname === "/signup" && } 57 |
58 |
59 |
60 |
61 |
62 | 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /mkdocs/docs/developer/mkdocs-edit-instructions.md: -------------------------------------------------------------------------------- 1 | # How to Publish new Documentation 2 | 3 | The goal of this page is to document how to create and publish new changes to the CTJ documentation. 4 | 5 | Writing documentation into mkdocs requires some knowledge of [Markdown](https://www.markdownguide.org/). 6 | 7 | ### Making changes to the mkdocs 8 | 9 | What you essentially need to know: 10 | 11 | - To make changes to the docs, edit the `.md` files located in the `mkdocs/docs/` folder. 12 | - To make a new page, create a new `.md` file in the appropriate folder. 13 | - To add or edit links in the navmenu, configure the `mkdocs.yml` file. 14 | 15 | MkDocs provides a development server that makes it convenient to see your changes in `localhost` before you deploy them to github. 16 | 17 | *For more a detailed editing guide please see the official [MkDocs Tutorial](https://www.mkdocs.org/getting-started/)* 18 | 19 | ### Quickstart 20 | 21 | To start the development server, simply go to the root of your project in the terminal and run the following command: 22 | 23 | ```bash 24 | docker-compose -f docker-compose.docs.yml up --watch 25 | ``` 26 | 27 | Next, go to `http://localhost:8005` in your browser. 28 | 29 | Now when you save new changes to the `.md` files, the respective page will automatically be updated in the browser. 30 | 31 | When you are done editing, the next step is to deploy your changes. 32 | 33 | ### Deploying your changes 34 | 35 | When you are satisfied with your edits, make a pull request so that they can be reviewed. 36 | 37 | Once the pull request is approved and the code is merged into the `main` branch, the changes will be automatically deployed to the official CivicTechJobs documentation site (the site you are reading this page in right now). That's it! 38 | 39 | If anything goes wrong, you can investigate the workflow in the project's [github actions page](https://github.com/hackforla/CivicTechJobs/actions) 40 | 41 | *Check out our [MkDocs Architecture](mkdocs-architecture.md) page for more details on how it all fits together.* 42 | 43 | ### Recap 44 | 45 | To sum it all up, you can make changes in 4 easy steps: 46 | 47 | 1. Start the development server using `docker-compose -f docker-compose.docs.yml up --watch` 48 | 2. Make changes to the `.md` files and observe them in `http://localhost:8005` 49 | 3. Open a Pull Request with your new changes 50 | 4. Merge the Pull Request into the `main` branch 51 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | ################################# 2 | ################################# 3 | ## Super Linter GitHub Actions ## 4 | ################################# 5 | ################################# 6 | name: Lint Code Base 7 | 8 | # 9 | # Documentation: 10 | # https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions 11 | # 12 | 13 | ############################# 14 | # Start the job on all push # 15 | ############################# 16 | on: 17 | push: 18 | branches-ignore: [master, main] 19 | # Remove the line above to run when pushing to master 20 | pull_request: 21 | branches: [master, main] 22 | workflow_dispatch: 23 | 24 | ############### 25 | # Set the Job # 26 | ############### 27 | jobs: 28 | build: 29 | # Name the Job 30 | name: Lint Code Base 31 | # Set the agent to run on 32 | runs-on: ubuntu-latest 33 | 34 | ################## 35 | # Load all steps # 36 | ################## 37 | steps: 38 | ########################## 39 | # Checkout the code base # 40 | ########################## 41 | - name: Checkout Code 42 | uses: actions/checkout@v3 43 | with: 44 | # Full git history is needed to get a proper list of changed files within `super-linter` 45 | fetch-depth: 0 46 | 47 | - run: cd frontend 48 | - name: Download Linter Plugins 49 | uses: actions/setup-node@v3 50 | with: 51 | node-version: "14" 52 | - run: | 53 | npm install stylelint-config-prettier-scss 54 | 55 | ################################ 56 | # Run Linter against code base # 57 | ################################ 58 | - name: Lint Code Base 59 | uses: github/super-linter/slim@v4 60 | env: 61 | JAVASCRIPT_DEFAULT_STYLE: prettier 62 | TYPESCRIPT_DEFAULT_STYLE: prettier 63 | VALIDATE_ALL_CODEBASE: ${{ (github.event_name == 'workflow_dispatch' && true) || false }} 64 | VALIDATE_HTML: false 65 | VALIDATE_MARKDOWN: false 66 | VALIDATE_NATURAL_LANGUAGE: false 67 | VALIDATE_PYTHON: false 68 | VALIDATE_PYTHON_BLACK: false 69 | VALIDATE_PYTHON_FLAKE8: false 70 | VALIDATE_PYTHON_ISORT: false 71 | VALIDATE_PYTHON_MYPY: false 72 | VALIDATE_PYTHON_PYLINT: false 73 | VALIDATE_TYPESCRIPT_STANDARD: false 74 | DEFAULT_BRANCH: ${{ (github.event_name == 'pull_request' && github.base_ref) || 'main' }} 75 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 76 | -------------------------------------------------------------------------------- /mkdocs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: CivicTechJobs Docs 2 | site_url: https://hackforla.github.io/CivicTechJobs/ 3 | 4 | # Optionals 5 | edit_uri: "" 6 | nav: 7 | - Joining the Team: 8 | - Intro: joining-the-team/intro.md 9 | - Content Writer: joining-the-team/content-writer.md 10 | - Data Scientist: joining-the-team/data-scientist.md 11 | - Project Manager: joining-the-team/product-manager.md 12 | - UI/UX Designer: joining-the-team/uiux-designer.md 13 | - UI/UX Researcher: joining-the-team/uiux-researcher.md 14 | - Web Developer: joining-the-team/web-developer.md 15 | - Other Volunteer: joining-the-team/other-volunteer.md 16 | - Docs: 17 | #- Product Manager: 18 | # - Placeholder: about.md 19 | #- UI Designer: 20 | # - Placeholder: about.md 21 | #- UX Researcher: 22 | # - Placeholder: about.md 23 | - Web Developer: 24 | - CONTRIBUTING.md: https://github.com/hackforla/CivicTechJobs/blob/main/CONTRIBUTING.md#welcome-to-the-civictechjobs-contributing-guide 25 | - Backend Architecture: developer/backend.md 26 | - Design System Helper: developer/design-system.md 27 | - DevOps Architecture: developer/devops.md 28 | - Development Culture: developer/development-culture.md 29 | - ESLint Guide: developer/eslint-guide.md 30 | - Frontend Architecture: developer/frontend.md 31 | - GitHub Architecture: developer/github.md 32 | - Git branch Structure: developer/git-branch-structure.md 33 | - Installation Instructions: developer/installation.md 34 | - Quickstart Guide: developer/quickstart-guide.md 35 | - MkDocs Architecture: developer/mkdocs-architecture.md 36 | - MkDocs - Documentation Guide: developer/mkdocs-guide.md 37 | - MkDocs - How to Edit: developer/mkdocs-edit-instructions.md 38 | - Misc: 39 | - ADA Guide: misc/ada-guide.md 40 | - Glossary: misc/glossary.md 41 | - History: misc/history.md 42 | - Our Process: misc/our-process.md 43 | - Research Wiki Template: misc/research-wiki-template.md 44 | - Security Updates: misc/security-updates.md 45 | - The Team: misc/the-team.md 46 | - Resources: resources.md 47 | - CivicTechJobs Repo: https://github.com/hackforla/CivicTechJobs 48 | markdown_extensions: 49 | # - mkdocs-config 50 | repo_url: https://github.com/hackforla/CivicTechJobs/tree/main/mkdocs 51 | repo_name: GitHub 52 | theme: 53 | name: material 54 | -------------------------------------------------------------------------------- /mkdocs/docs/index.md: -------------------------------------------------------------------------------- 1 | # Welcome to the Civictechjobs.org Wiki! 2 | 3 | Here, you'll find all the essential information about our project. This wiki is designed to be a helpful resource for both new and existing team members. 4 | 5 | ## Project Definition 6 | 7 | CivicTechJobs will be a site to find open volunteer positions for projects at Hack for LA. 8 | 9 | ## Introduction To The Project 10 | 11 | ### What is Civictechjobs.org? 12 | 13 | Civic Tech Jobs is a platform designed to connect prospective Hack for LA volunteers with interdisciplinary projects that align with their career goals while driving positive civic impact. The custom-built CMS enables this by allowing projects to list required skills and then matching volunteers based on their availability, skills, and program area. 14 | 15 | ### Why do it? 16 | 17 | CTJ will tackle key challenges in project recruitment and organizational sustainability, with future development focused on connecting volunteers to opportunities that align with paid job postings. 18 | 19 | ### Hasn't it been done already? 20 | 21 | Yes, there are recruitment issues on project boards. However, the process is cumbersome and involves multiple steps to match candidates with the right roles. 22 | 23 | Read more about what lead up to us developing this project, at our [History](misc/history) page. 24 | 25 | ### So how is this different? 26 | 27 | We aim to streamline the process so that Product Managers can efficiently post open opportunities within their team. Most importantly, we want to ensure that volunteers can easily find the right match with a project based on their skills and aspirations. 28 | 29 | ## Guiding Objectives 30 | 31 | ### What are our guiding objectives? 32 | 33 | 1. Enable product managers ability to post, edit, or close open positions on an easy to use instinctive site (CMS). 34 | 2. Assist potential volunteers to self-filter roles based on their availability. 35 | 3. Provide potential volunteers list of open roles with different projects that matches their skills, and interest. 36 | 37 | ## Project One Sheet 38 | 39 | Access our project one sheet [CTJ_ Civic Tech Jobs One Sheet - 2025.pdf](https://github.com/user-attachments/files/19147708/CTJ_.Civic.Tech.Jobs.One.Sheet.-.2025.pdf) 40 | 41 | ## Key Resources 42 | 43 | To take a look at some of our key resources: 44 | 45 | - [Civictechjobs Google Drive](https://drive.google.com/drive/folders/1hXxvpC8W5Uuzjqo4CxnjDpAMI7sbVnq8?usp=sharing) 46 | - [Civictechjobs Figma File](https://www.figma.com/file/G5bOqhud6azbxyR9El9Ygp/Civic-Tech-Jobs?node-id=0%3A1) 47 | -------------------------------------------------------------------------------- /frontend/src/pages/LandingPage/LandingPageCopCards.tsx: -------------------------------------------------------------------------------- 1 | // External Imports 2 | import React from "react"; 3 | import clsx from "clsx"; 4 | 5 | // Internal Imports 6 | import { IconButton } from "tw-components"; 7 | import { iconX } from "assets/images/images"; 8 | import { Card } from "tw-components/StandardCard"; 9 | 10 | interface CopCardProps extends React.PropsWithChildren { 11 | addClass?: string; 12 | onClick: (e?: React.SyntheticEvent) => void; 13 | isHidden?: boolean; 14 | size?: "lg" | "sm"; 15 | } 16 | 17 | function CopCard({ isHidden = true, size = "sm", ...props }: CopCardProps) { 18 | return ( 19 | 26 | 32 |
{props.children}
33 |
34 | ); 35 | } 36 | 37 | interface InnerCopCardProps extends React.PropsWithChildren { 38 | addClass?: string; 39 | } 40 | 41 | function InnerCopCard(props: InnerCopCardProps) { 42 | return ( 43 | 49 |
50 | {props.children} 51 |
52 |
53 | ); 54 | } 55 | 56 | interface InnerCopNavCardSharedProps extends React.PropsWithChildren { 57 | className?: string; 58 | isActive?: boolean; 59 | } 60 | 61 | interface InnerCopNavCardAnchorProps extends InnerCopNavCardSharedProps { 62 | href: string; 63 | onClick?: never; 64 | } 65 | 66 | interface InnerCopNavCardButtonProps extends InnerCopNavCardSharedProps { 67 | href?: never; 68 | onClick: () => void; 69 | } 70 | 71 | type InnerCopNavCardProps = 72 | | InnerCopNavCardAnchorProps 73 | | InnerCopNavCardButtonProps; 74 | 75 | function InnerCopNavCard({ isActive = false, ...props }: InnerCopNavCardProps) { 76 | return ( 77 | 87 | ); 88 | } 89 | 90 | export { CopCard, InnerCopCard, InnerCopNavCard }; 91 | -------------------------------------------------------------------------------- /frontend/src/components/Basics/_layout.scss: -------------------------------------------------------------------------------- 1 | // External Imports 2 | @use "sass:list"; 3 | @use "sass:map"; 4 | 5 | // Internal Imports 6 | @use "./breakpoints" as bp; 7 | @use "./spacing" as *; 8 | 9 | /****************************************** 10 | *** Container class to create a flexbox *** 11 | *******************************************/ 12 | .flex-container { 13 | box-sizing: border-box; 14 | display: flex; 15 | flex-flow: row wrap; 16 | } 17 | 18 | .flex-column { 19 | box-sizing: border-box; 20 | display: flex; 21 | flex-flow: column wrap; 22 | } 23 | 24 | .flex-center-x { 25 | box-sizing: border-box; 26 | display: flex; 27 | flex-flow: row wrap; 28 | justify-content: center; 29 | } 30 | 31 | .flex-center-y { 32 | align-items: center; 33 | box-sizing: border-box; 34 | display: flex; 35 | flex-flow: row wrap; 36 | } 37 | 38 | .fill { 39 | width: 100%; 40 | } 41 | 42 | /****************** 43 | *** Row classes *** 44 | ******************/ 45 | 46 | .row { 47 | box-sizing: border-box; 48 | display: flex; 49 | } 50 | 51 | /************************************* 52 | *** Column classes for column size *** 53 | *************************************/ 54 | $columns: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12; 55 | 56 | @mixin col-size($col) { 57 | box-sizing: border-box; 58 | flex: 0 1 calc(8.33% * $col); 59 | 60 | @each $name in map.keys($margin-abbrs) { 61 | @each $size in $spacing-sizes { 62 | &.#{$name}-#{$size} { 63 | // recalculates column size to accomodate margins 64 | @if list.index("mx" "my" "px" "py", $name) { 65 | flex: 0 1 calc(8.33% * $col - $size * 16px); 66 | } @else { 67 | flex: 0 1 calc(8.33% * $col - $size * 8px); 68 | } 69 | } 70 | } 71 | } 72 | 73 | @each $name in map.keys($gap-abbrs) { 74 | @each $size in $spacing-sizes { 75 | .#{$name}-#{$size} > & { 76 | // recalculates column size to accommodate gaps 77 | @if list.index("gap" "gap-x", $name) { 78 | flex: 0 1 calc(8.33% * $col - ($size * 8px) * (1 - $col / 12)); 79 | } 80 | } 81 | } 82 | } 83 | } 84 | 85 | @each $col in $columns { 86 | .col-#{$col} { 87 | @include col-size($col); 88 | } 89 | } 90 | 91 | /*********************************************** 92 | *** Column classes concerning responsiveness *** 93 | ************************************************/ 94 | 95 | @mixin col-responsive($device, $col) { 96 | @include bp.media-max($device) { 97 | @include col-size($col); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /frontend/src/components/Basics/_typography.scss: -------------------------------------------------------------------------------- 1 | @use "./colors" as *; 2 | 3 | @mixin title($size) { 4 | font-family: Roboto, Tahoma, Verdana, sans-serif; 5 | font-weight: bold; 6 | 7 | @if $size == 1 { 8 | font-size: 3rem; 9 | line-height: 3.625rem; 10 | } @else if $size == 2 { 11 | font-size: 2.25rem; 12 | line-height: 2.75rem; 13 | } @else if $size == 3 { 14 | font-size: 1.75rem; 15 | line-height: 2.125rem; 16 | } @else if $size == 4 { 17 | font-size: 1.5rem; 18 | line-height: 1.875rem; 19 | } @else if $size == 5 { 20 | font-size: 1.25rem; 21 | line-height: 1.5rem; 22 | } @else if $size == 6 { 23 | font-size: 1rem; 24 | line-height: 1.25rem; 25 | } @else if $size == 7 { 26 | font-size: 0.875rem; 27 | line-height: 1.125rem; 28 | } 29 | } 30 | 31 | h1 { 32 | @include title(1); 33 | } 34 | 35 | h2 { 36 | @include title(2); 37 | } 38 | 39 | h3 { 40 | @include title(3); 41 | } 42 | 43 | h4 { 44 | @include title(4); 45 | } 46 | 47 | h5 { 48 | @include title(5); 49 | } 50 | 51 | h6 { 52 | @include title(6); 53 | } 54 | 55 | .title-1 { 56 | @include title(1); 57 | } 58 | 59 | .title-2 { 60 | @include title(2); 61 | } 62 | 63 | .title-3 { 64 | @include title(3); 65 | } 66 | 67 | .title-4 { 68 | @include title(4); 69 | } 70 | 71 | .title-5 { 72 | @include title(5); 73 | } 74 | 75 | .title-6 { 76 | @include title(6); 77 | } 78 | 79 | .title-7 { 80 | @include title(7); 81 | } 82 | 83 | @mixin paragraph($size) { 84 | font-family: Roboto, Tahoma, Verdana, sans-serif; 85 | font-weight: normal; 86 | 87 | @if $size == 1 { 88 | font-size: 1.25rem; 89 | line-height: 1.5rem; 90 | } @else if $size == 2 { 91 | font-size: 1.125rem; 92 | line-height: 1.375rem; 93 | } @else if $size == 3 { 94 | font-size: 1rem; 95 | line-height: 1.25rem; 96 | } @else if $size == 4 { 97 | font-size: 0.9375rem; 98 | line-height: 1.125rem; 99 | } @else if $size == 5 { 100 | font-size: 0.875rem; 101 | line-height: 1.125rem; 102 | } 103 | } 104 | 105 | .paragraph-1 { 106 | @include paragraph(1); 107 | } 108 | 109 | .paragraph-2 { 110 | @include paragraph(2); 111 | } 112 | 113 | .paragraph-3 { 114 | @include paragraph(3); 115 | } 116 | 117 | .paragraph-4 { 118 | @include paragraph(4); 119 | } 120 | 121 | .paragraph-5 { 122 | @include paragraph(5); 123 | } 124 | 125 | .links { 126 | color: $blue-dark; 127 | 128 | &:hover { 129 | color: $blue-dark-focused; 130 | cursor: pointer; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /frontend/src/pages/Authentication/LoginForm.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { TextField } from "tw-components"; 4 | import { useForm, SubmitHandler } from "react-hook-form"; 5 | 6 | type Inputs = { 7 | email: string; 8 | password: string; 9 | }; 10 | 11 | /** Login Form Component 12 | * @dev used on the Authentication page 13 | * @dev noValidate on form to disable browser vaildation so we can use react-hook-form validations instead 14 | */ 15 | export default function LoginForm() { 16 | const { 17 | register, 18 | handleSubmit, 19 | formState: { errors }, 20 | } = useForm(); 21 | const onSubmit: SubmitHandler = (data) => { 22 | console.log("Sending form data to server...", data); 23 | }; 24 | 25 | return ( 26 |
27 |

Log in

28 |
29 | 43 | 58 |
59 | 60 |

Keep me signed in

61 |
62 | 65 | 66 |
67 |

68 | New to Civic Tech Jobs?{" "} 69 | 70 | Sign up 71 | 72 |

73 |
74 |
75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [main] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [main] 20 | schedule: 21 | - cron: "45 10 * * 6" 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: ["javascript", "python"] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v2 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v2 71 | -------------------------------------------------------------------------------- /frontend/src/tw-components/CookieBanner.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-unescaped-entities */ 2 | // External Imports 3 | import React, { useState } from "react"; 4 | import Cookies from "js-cookie"; 5 | 6 | // Internal Imports 7 | import { IconButton } from "./Buttons"; 8 | import { iconX } from "assets/images/images"; 9 | 10 | function CookieBanner() { 11 | const [hidden, setIsHidden] = useState( 12 | Cookies.get("cookieConsent") !== undefined, 13 | ); 14 | 15 | const handleAcceptCookies = () => { 16 | Cookies.set("cookieConsent", "true", { expires: 365 }); 17 | setIsHidden(true); 18 | }; 19 | 20 | const handleDeclineCookies = () => { 21 | Cookies.set("cookieConsent", "false", { expires: 365 }); 22 | setIsHidden(true); 23 | }; 24 | return ( 25 | 72 | ); 73 | } 74 | 75 | export default CookieBanner; 76 | -------------------------------------------------------------------------------- /mkdocs/docs/developer/deployment-infra.md: -------------------------------------------------------------------------------- 1 | _

Project deployment and hosting

_ 2 | 3 | 4 | 5 | ![CTJ Infrastructure Diagram](../assets/ctj-infra-diagram.png) 6 | 7 | 8 | ### Summary 9 | CivicTechJobs is hosted on the Incubator AWS account, which is managed by the DevOps CoP. The application is deployed through Fargate, which itself deploys the CivicTechJob docker image. This repo sends updates to the Incubator through a github action `deploy-stage.yml`. 10 | 11 | Incubator aws resources are managed using terraform files which are commited to the [Incubator Source](https://github.com/hackforla/incubator/tree/main/terraform/projects/civic-tech-jobs) 12 | 13 | The resources used by CTJ include the following: 14 | - A Database(shared with other projects) 15 | - DNS Entry 16 | - ECS Task Definition 17 | - A Fargate Task 18 | 19 | 20 | ### Github Action 21 | Github actions are the mechanism used to convey changes in this CivicTechJobs repo to the AWS deployment. The deploy-stage action is set to run on any updates to the main branch. On run the action will assume an AWS credential, push the newest docker image to Amazon's Elastic Container Registry(ECR), then force a ECS-Fargate redeployment. This process will depend on OIDC to grant AWS permissions for its execution 22 | 23 | ### Regarding Environment Variables 24 | The combination of Fargate(a managed service from AWS) and terraform means that environment variables are passed into the application differently than when the application is run locally. The environment variables are set in terraform at the following [location](https://github.com/hackforla/incubator/blob/main/terraform/projects/civic-tech-jobs/environment-stage.tf). 25 | Note that these variables are the same as those found in this repo's [env.example](https://github.com/hackforla/CivicTechJobs/blob/develop/dev/dev.env.example) 26 | 27 | Some values will come from the terraform modules, but in essense if you must add or edit environment variables for the application in deployment, you will need to change them in Incubator. 28 | 29 | ### DNS 30 | The staging environment is current set to run at: https://stage.civictechjobs.org/ 31 | 32 | Domain management is done in AWS Route53, through terraform files found in Incubator 33 | 34 | ### Fargate 35 | AWS Fargate is a managed service which allows CTJ to easily deploy it's containerized application. This is done by updating a AWS task definition and then restarting the associated Fargate service. 36 | 37 | Both of these steps are preformed automatically by the github action in `deploy-stage.yml` when new commits are merged into the `main` branch 38 | 39 | 40 | ## Additional Resources 41 | 42 | [Incubator Repo](https://github.com/hackforla/incubator/tree/main/terraform/projects/civic-tech-jobs) 43 | --------------------------------------------------------------------------------