diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index aaab0cb..6a260b6 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,72 +1,72 @@ -FROM ubuntu:22.04 - -# Setup workspace directory -RUN mkdir /workspace -WORKDIR /workspace - -# Install useful system utilities -ENV TZ=America/New_York -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update \ - && apt-get install --yes \ - apt-transport-https \ - ca-certificates \ - curl \ - debian-keyring \ - debian-archive-keyring \ - git \ - gnupg \ - locales \ - postgresql-client \ - software-properties-common \ - sudo \ - tzdata \ - wget \ - zsh \ - && rm -rf /var/lib/apt/lists/* - -# Install Python 3.11 -RUN add-apt-repository ppa:deadsnakes/ppa \ - && apt update \ - && apt install --yes \ - python3.11 \ - python3-pip \ - libpq-dev \ - python3.11-dev \ - && rm -rf /var/lib/apt/lists* \ - && unlink /usr/bin/python3 \ - && ln -s /usr/bin/python3.11 /usr/bin/python3 - -# Use a non-root user per https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user -ARG USERNAME=vscode -ARG USER_UID=1000 -ARG USER_GID=$USER_UID - -# Add non-root user and add to sudoers -RUN groupadd --gid $USER_GID $USERNAME \ - && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME -s /usr/bin/zsh \ - && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ - && chmod 0440 /etc/sudoers.d/$USERNAME - -# Set code to default git commit editor -RUN git config --system core.editor "code --wait" -# Set Safe Directory -RUN git config --system safe.directory '/workspace' - -# Configure zsh -USER $USERNAME -ENV HOME /home/$USERNAME - -# Add zsh theme with niceties -RUN curl https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh | bash - \ - && sed -i 's/robbyrussell/kennethreitz/g' ~/.zshrc \ - && echo 'source <(ng completion script)' >>~/.zshrc \ - && echo 'export PATH=$PATH:$HOME/.local/bin' >>~/.zshrc - -# Set Locale for Functional Autocompletion in zsh -RUN sudo locale-gen en_US.UTF-8 - -# Install Database Dependencies -COPY backend/requirements.txt /workspace/backend/requirements.txt -WORKDIR /workspace/backend +FROM ubuntu:22.04 + +# Setup workspace directory +RUN mkdir /workspace +WORKDIR /workspace + +# Install useful system utilities +ENV TZ=America/New_York +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update \ + && apt-get install --yes \ + apt-transport-https \ + ca-certificates \ + curl \ + debian-keyring \ + debian-archive-keyring \ + git \ + gnupg \ + locales \ + postgresql-client \ + software-properties-common \ + sudo \ + tzdata \ + wget \ + zsh \ + && rm -rf /var/lib/apt/lists/* + +# Install Python 3.11 +RUN add-apt-repository ppa:deadsnakes/ppa \ + && apt update \ + && apt install --yes \ + python3.11 \ + python3-pip \ + libpq-dev \ + python3.11-dev \ + && rm -rf /var/lib/apt/lists* \ + && unlink /usr/bin/python3 \ + && ln -s /usr/bin/python3.11 /usr/bin/python3 + +# Use a non-root user per https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user +ARG USERNAME=vscode +ARG USER_UID=1000 +ARG USER_GID=$USER_UID + +# Add non-root user and add to sudoers +RUN groupadd --gid $USER_GID $USERNAME \ + && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME -s /usr/bin/zsh \ + && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ + && chmod 0440 /etc/sudoers.d/$USERNAME + +# Set code to default git commit editor +RUN git config --system core.editor "code --wait" +# Set Safe Directory +RUN git config --system safe.directory '/workspace' + +# Configure zsh +USER $USERNAME +ENV HOME /home/$USERNAME + +# Add zsh theme with niceties +RUN curl https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh | bash - \ + && sed -i 's/robbyrussell/kennethreitz/g' ~/.zshrc \ + && echo 'source <(ng completion script)' >>~/.zshrc \ + && echo 'export PATH=$PATH:$HOME/.local/bin' >>~/.zshrc + +# Set Locale for Functional Autocompletion in zsh +RUN sudo locale-gen en_US.UTF-8 + +# Install Database Dependencies +COPY backend/requirements.txt /workspace/backend/requirements.txt +WORKDIR /workspace/backend RUN python3 -m pip install -r requirements.txt \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 793f5d8..8c0476c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,47 +1,47 @@ -{ - "dockerComposeFile": "docker-compose.yml", - "workspaceFolder": "/workspace", - "service": "httpd", - "remoteUser": "vscode", - "forwardPorts": [ - 5432, - 5050 - ], - "customizations": { - "vscode": { - "extensions": [ - "dbaeumer.vscode-eslint", - "ecmel.vscode-html-css", - "ms-vscode.vscode-typescript-next", - "esbenp.prettier-vscode", - "bradlc.vscode-tailwindcss", - "vscode-icons-team.vscode-icons", - "tamasfe.even-better-toml", - "ckolkman.vscode-postgres", - "ms-python.python", - "ms-python.vscode-pylance", - "ms-python.black-formatter", - "gruntfuggly.todo-tree", - "ms-azuretools.vscode-docker" - ], - "settings": { - "editor.formatOnSave": true, - "editor.formatOnSaveMode": "file", - "[python]": { - "editor.defaultFormatter": "ms-python.black-formatter" - }, - "python.analysis.extraPaths": [ - "/backend/" - ], - "python.testing.pytestEnabled": true, - "python.testing.unittestEnabled": false, - "python.analysis.diagnosticSeverityOverrides": { - "reportMissingParameterType": "error", - "reportGeneralTypeIssues": "error", - "reportDeprecated": "error", - "reportImportCycles": "error" - } - } - } - } +{ + "dockerComposeFile": "docker-compose.yml", + "workspaceFolder": "/workspace", + "service": "httpd", + "remoteUser": "vscode", + "forwardPorts": [ + 5432, + 5050 + ], + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "ecmel.vscode-html-css", + "ms-vscode.vscode-typescript-next", + "esbenp.prettier-vscode", + "bradlc.vscode-tailwindcss", + "vscode-icons-team.vscode-icons", + "tamasfe.even-better-toml", + "ckolkman.vscode-postgres", + "ms-python.python", + "ms-python.vscode-pylance", + "ms-python.black-formatter", + "gruntfuggly.todo-tree", + "ms-azuretools.vscode-docker" + ], + "settings": { + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "python.analysis.extraPaths": [ + "/backend/" + ], + "python.testing.pytestEnabled": true, + "python.testing.unittestEnabled": false, + "python.analysis.diagnosticSeverityOverrides": { + "reportMissingParameterType": "error", + "reportGeneralTypeIssues": "error", + "reportDeprecated": "error", + "reportImportCycles": "error" + } + } + } + } } \ No newline at end of file diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 1503d5b..d8aaa7b 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,35 +1,35 @@ -version: "3" - -services: - httpd: - build: - context: .. - dockerfile: .devcontainer/Dockerfile - volumes: - - ..:/workspace - command: /bin/sh -c "while sleep 1000; do :; done" - environment: - - windir # Defined on Windows but not on other platforms - - db: - image: "postgres:latest" - ports: - - "5432:5432" - env_file: - - ../backend/.env - volumes: - - compass-center-postgres:/var/lib/postgresql/data - # - ./backend/init.sql:/docker-entrypoint-initdb.d/init.sql:ro - - pgadmin: - image: dpage/pgadmin4:latest - environment: - PGADMIN_DEFAULT_EMAIL: admin@example.com - PGADMIN_DEFAULT_PASSWORD: admin - ports: - - "5050:80" - depends_on: - - db - -volumes: - compass-center-postgres: +version: "3" + +services: + httpd: + build: + context: .. + dockerfile: .devcontainer/Dockerfile + volumes: + - ..:/workspace + command: /bin/sh -c "while sleep 1000; do :; done" + environment: + - windir # Defined on Windows but not on other platforms + + db: + image: "postgres:latest" + ports: + - "5432:5432" + env_file: + - ../backend/.env + volumes: + - compass-center-postgres:/var/lib/postgresql/data + # - ./backend/init.sql:/docker-entrypoint-initdb.d/init.sql:ro + + pgadmin: + image: dpage/pgadmin4:latest + environment: + PGADMIN_DEFAULT_EMAIL: admin@example.com + PGADMIN_DEFAULT_PASSWORD: admin + ports: + - "5050:80" + depends_on: + - db + +volumes: + compass-center-postgres: diff --git a/.gitignore b/.gitignore index c303a94..0c60f1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -/backend/.env +/backend/.env __pycache__ \ No newline at end of file diff --git a/README.md b/README.md index 21b84a4..fcc0de0 100644 --- a/README.md +++ b/README.md @@ -1,105 +1,105 @@ -# 🧭 Compass Center's Internal Resource Management App - -## 🛠 Technologies - -- Next.js -- TailwindCSS -- TypeScript -- Supabase - -## 📁 File Setup - -``` -\compass - \components // Components organized in folders related to specific pages - \pages // Store all pages here - \api // API routes - \public // Local assets (minimize usage) - \utils // Constants, Routes, Classes, Dummy Data - \styles // CSS files -``` - -## 🚀 To Start - -Follow these steps to set up your local environment: - -``` -\\ Clone this repository -git clone https://github.com/cssgunc/compass.git -\\ Go into main folder -cd compass -\\ Install dependencies -npm install -\\ Run local environment -npm run dev -``` - -Also add following variables inside of a .env file inside of the backend directory - -``` -\\ .env file contents - -POSTGRES_DATABASE=compass -POSTGRES_USER=postgres -POSTGRES_PASSWORD=admin -POSTGRES_HOST=db -POSTGRES_PORT=5432 -HOST=localhost -``` - -## Backend Starter - -- Please open the VS Code Command Palette -- Run the command **Dev Containers: Rebuild and Reopen in Container** -- This should open the dev container with the same file directory mounted so any changes in the dev container will be seen in the local repo - -### In Dev Container - -Run this to reset the database and populate it with the approprate tables that reflect the entities folder -``` -python3 -m backend.script.reset_demo -``` - -### Possible Dev Container Errors - -- Sometimes the ports allocated to our services will be allocated (5432 for Postgres and 5050 for PgAdmin4) -- Run **docker stop** to stop all containers -- If that does not work using **sudo lsof -i :[PORT_NUMBER]** to find the process running on the needed ports and idenitfy the PID -- Run **sudo kill [PID]** -- If you are on Windows please consult ChatGPT or set up WSL (will be very useful in the future) - -### Accesing pgAdmin 4 - -- First go to http://localhost:5050/ on your browser -- Log in using the credentials admin@example.com and admin -- Click **Add New Server** -- Fill in the name field with Compass (can be anything) -- Click **Connection** tab and fill in the following: - - Host name/address: db - - Maintence database: compass - - Username: postgres - - Password: admin -- Click **Save** at the bottom to add connection -- Click **Server** dropdown on the left and click through items inside the **Compass** server - -### Testing Backend Code - -- Write tests for any service you create and any function in those services -- Make sure to add docstrings detailing what the file is doing and what each test is doing -- Name all test functions with test\_[testContent] (Must be prefixed with test to be recognized by pytest) -- Utitlize dependency injection for commonly used services - -``` -\\ Run all tests by being in the backend directory -pytest - -\\ Run specific tests by passing in file as a parameter -\\ Passing the -s allows us to see any print statements or debugging statements in the console -pytest -s --rootdir=/workspace [testFilePath]::[testFunctionSignature] -``` - -## 💡 Dev Notes - -- For each task, create a branch in the format '[your name]-[ticket number]-[task description]' -- Only commit your work to that branch and then make a git request to '/main' -- When creating new files in the backend and code is in python make sure to add a docstring for the file and any function you create ("""[content]"""") +# 🧭 Compass Center's Internal Resource Management App + +## 🛠 Technologies + +- Next.js +- TailwindCSS +- TypeScript +- Supabase + +## 📁 File Setup + +``` +\compass + \components // Components organized in folders related to specific pages + \pages // Store all pages here + \api // API routes + \public // Local assets (minimize usage) + \utils // Constants, Routes, Classes, Dummy Data + \styles // CSS files +``` + +## 🚀 To Start + +Follow these steps to set up your local environment: + +``` +\\ Clone this repository +git clone https://github.com/cssgunc/compass.git +\\ Go into main folder +cd compass +\\ Install dependencies +npm install +\\ Run local environment +npm run dev +``` + +Also add following variables inside of a .env file inside of the backend directory + +``` +\\ .env file contents + +POSTGRES_DATABASE=compass +POSTGRES_USER=postgres +POSTGRES_PASSWORD=admin +POSTGRES_HOST=db +POSTGRES_PORT=5432 +HOST=localhost +``` + +## Backend Starter + +- Please open the VS Code Command Palette +- Run the command **Dev Containers: Rebuild and Reopen in Container** +- This should open the dev container with the same file directory mounted so any changes in the dev container will be seen in the local repo + +### In Dev Container + +Run this to reset the database and populate it with the approprate tables that reflect the entities folder +``` +python3 -m backend.script.reset_demo +``` + +### Possible Dev Container Errors + +- Sometimes the ports allocated to our services will be allocated (5432 for Postgres and 5050 for PgAdmin4) +- Run **docker stop** to stop all containers +- If that does not work using **sudo lsof -i :[PORT_NUMBER]** to find the process running on the needed ports and idenitfy the PID +- Run **sudo kill [PID]** +- If you are on Windows please consult ChatGPT or set up WSL (will be very useful in the future) + +### Accesing pgAdmin 4 + +- First go to http://localhost:5050/ on your browser +- Log in using the credentials admin@example.com and admin +- Click **Add New Server** +- Fill in the name field with Compass (can be anything) +- Click **Connection** tab and fill in the following: + - Host name/address: db + - Maintence database: compass + - Username: postgres + - Password: admin +- Click **Save** at the bottom to add connection +- Click **Server** dropdown on the left and click through items inside the **Compass** server + +### Testing Backend Code + +- Write tests for any service you create and any function in those services +- Make sure to add docstrings detailing what the file is doing and what each test is doing +- Name all test functions with test\_[testContent] (Must be prefixed with test to be recognized by pytest) +- Utitlize dependency injection for commonly used services + +``` +\\ Run all tests by being in the backend directory +pytest + +\\ Run specific tests by passing in file as a parameter +\\ Passing the -s allows us to see any print statements or debugging statements in the console +pytest -s --rootdir=/workspace [testFilePath]::[testFunctionSignature] +``` + +## 💡 Dev Notes + +- For each task, create a branch in the format '[your name]-[ticket number]-[task description]' +- Only commit your work to that branch and then make a git request to '/main' +- When creating new files in the backend and code is in python make sure to add a docstring for the file and any function you create ("""[content]"""") diff --git a/backend/database.py b/backend/database.py index b53d444..a75f451 100644 --- a/backend/database.py +++ b/backend/database.py @@ -1,29 +1,29 @@ -"""SQLAlchemy DB Engine and Session niceties for FastAPI dependency injection.""" - -import sqlalchemy -from sqlalchemy.orm import Session -from .env import getenv - - -def _engine_str(database: str = getenv("POSTGRES_DATABASE")) -> str: - """Helper function for reading settings from environment variables to produce connection string.""" - dialect = "postgresql+psycopg2" - user = getenv("POSTGRES_USER") - password = getenv("POSTGRES_PASSWORD") - host = getenv("POSTGRES_HOST") - port = getenv("POSTGRES_PORT") - return f"{dialect}://{user}:{password}@{host}:{port}/{database}" - - -engine = sqlalchemy.create_engine(_engine_str(), echo=True) -"""Application-level SQLAlchemy database engine.""" - - -def db_session(): - """Generator function offering dependency injection of SQLAlchemy Sessions.""" - print("ran") - session = Session(engine) - try: - yield session - finally: - session.close() +"""SQLAlchemy DB Engine and Session niceties for FastAPI dependency injection.""" + +import sqlalchemy +from sqlalchemy.orm import Session +from .env import getenv + + +def _engine_str(database: str = getenv("POSTGRES_DATABASE")) -> str: + """Helper function for reading settings from environment variables to produce connection string.""" + dialect = "postgresql+psycopg2" + user = getenv("POSTGRES_USER") + password = getenv("POSTGRES_PASSWORD") + host = getenv("POSTGRES_HOST") + port = getenv("POSTGRES_PORT") + return f"{dialect}://{user}:{password}@{host}:{port}/{database}" + + +engine = sqlalchemy.create_engine(_engine_str(), echo=True) +"""Application-level SQLAlchemy database engine.""" + + +def db_session(): + """Generator function offering dependency injection of SQLAlchemy Sessions.""" + print("ran") + session = Session(engine) + try: + yield session + finally: + session.close() diff --git a/backend/entities/__init__.py b/backend/entities/__init__.py index 39abb6f..0b82067 100644 --- a/backend/entities/__init__.py +++ b/backend/entities/__init__.py @@ -1,2 +1,10 @@ -from .entity_base import EntityBase -from .sample_entity import SampleEntity +from .entity_base import EntityBase +from .sample_entity import SampleEntity +from .tag_entity import TagEntity +from .user_entity import UserEntity +from .resource_entity import ResourceEntity +from .resource_tag_entity import ResourceTagEntity +from .service_entity import ServiceEntity +from .service_tag_entity import ServiceTagEntity +from .program_enum import ProgramEnum +from .user_enum import RoleEnum diff --git a/backend/entities/entity_base.py b/backend/entities/entity_base.py index 5e34685..887fe50 100644 --- a/backend/entities/entity_base.py +++ b/backend/entities/entity_base.py @@ -1,12 +1,12 @@ -"""Abstract superclass of all entities in the application. - -There is no reason to instantiate this class directly. Instead, look toward the child classes. -Additionally, import from the top-level entities file which indexes all entity implementations. -""" - - -from sqlalchemy.orm import DeclarativeBase - - -class EntityBase(DeclarativeBase): - pass +"""Abstract superclass of all entities in the application. + +There is no reason to instantiate this class directly. Instead, look toward the child classes. +Additionally, import from the top-level entities file which indexes all entity implementations. +""" + + +from sqlalchemy.orm import DeclarativeBase + + +class EntityBase(DeclarativeBase): + pass diff --git a/backend/entities/program_enum.py b/backend/entities/program_enum.py new file mode 100644 index 0000000..3a207bd --- /dev/null +++ b/backend/entities/program_enum.py @@ -0,0 +1,10 @@ +from sqlalchemy import Enum + + +class ProgramEnum(Enum): + ECONOMIC = "economic" + DOMESTIC = "domestic" + COMMUNITY = "community" + + def __init__(self): + super().__init__(name="program_enum") diff --git a/backend/entities/resource_entity.py b/backend/entities/resource_entity.py new file mode 100644 index 0000000..b38e625 --- /dev/null +++ b/backend/entities/resource_entity.py @@ -0,0 +1,68 @@ +""" Defines the table for storing resources """ + +# Import our mapped SQL types from SQLAlchemy +from sqlalchemy import Integer, String, DateTime + +# Import mapping capabilities from the SQLAlchemy ORM +from sqlalchemy.orm import Mapped, mapped_column, relationship + +# Import the EntityBase that we are extending +from .entity_base import EntityBase + +# Import datetime for created_at type +from datetime import datetime + +# Import self for to model +from typing import Self +from backend.entities.program_enum import ProgramEnum + + +class ResourceEntity(EntityBase): + + # set table name + __tablename__ = "resource" + + # set fields + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now) + name: Mapped[str] = mapped_column(String(32), nullable=False) + summary: Mapped[str] = mapped_column(String(100), nullable=False) + link: Mapped[str] = mapped_column(String, nullable=False) + program: Mapped[ProgramEnum] = mapped_column(ProgramEnum, nullable=False) + + # relationships + resourceTags: Mapped[list["ResourceTagEntity"]] = relationship( + back_populates="resource", cascade="all,delete" + ) + + # + # @classmethod + # def from_model(cls, model: user_model) -> Self: + # """ + # Create a UserEntity from a User model. + + # Args: + # model (User): The model to create the entity from. + + # Returns: + # Self: The entity (not yet persisted). + # """ + + # return cls ( + # id = model.id, + # created_at = model.created_at, + # name = model.name, + # summary = model.summary, + # link = model.link, + # program = model.program, + # ) + + # def to_model(self) -> user_model: + # return user_model ( + # id = self.id, + # created_at = self.created_at, + # name = self.name, + # summary = self.summary, + # link = self.link, + # program = self.program, + # ) diff --git a/backend/entities/resource_tag_entity.py b/backend/entities/resource_tag_entity.py new file mode 100644 index 0000000..f1de522 --- /dev/null +++ b/backend/entities/resource_tag_entity.py @@ -0,0 +1,46 @@ +""" Defines the table for resource tags """ + +# Import our mapped SQL types from SQLAlchemy +from sqlalchemy import ForeignKey, Integer, String, DateTime + +# Import mapping capabilities from the SQLAlchemy ORM +from sqlalchemy.orm import Mapped, mapped_column, relationship + +# Import the EntityBase that we are extending +from .entity_base import EntityBase + +# Import datetime for created_at type +from datetime import datetime + +# Import self for to model +from typing import Self + + +class ResourceTagEntity(EntityBase): + + # set table name to user in the database + __tablename__ = "resourceTag" + + # set fields or 'columns' for the user table + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + resourceId: Mapped[int] = mapped_column(ForeignKey("resource.id")) + tagId: Mapped[int] = mapped_column(ForeignKey("tag.id")) + + # relationships + resource: Mapped["ResourceEntity"] = relationship(back_populates="resourceTags") + tag: Mapped["TagEntity"] = relationship(back_populates="resourceTags") + + # @classmethod + # def from_model (cls, model: resource_tag_model) -> Self: + # return cls ( + # id = model.id, + # resourceId = model.resourceId, + # tagId = model.tagId, + # ) + + # def to_model (self) -> resource_tag_model: + # return user_model( + # id = self.id, + # resourceId = self.resourceId, + # tagId = self.tagId, + # ) diff --git a/backend/entities/sample_entity.py b/backend/entities/sample_entity.py index 4759694..5372899 100644 --- a/backend/entities/sample_entity.py +++ b/backend/entities/sample_entity.py @@ -1,12 +1,12 @@ -from sqlalchemy import create_engine, Column, Integer, String -from sqlalchemy.orm import Mapped, mapped_column, relationship -from .entity_base import EntityBase - - -class SampleEntity(EntityBase): - __tablename__ = "persons" - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - name: Mapped[str] = mapped_column(String, nullable=False) - age: Mapped[int] = mapped_column(Integer) - email: Mapped[str] = mapped_column(String, unique=True, nullable=False) +from sqlalchemy import create_engine, Column, Integer, String +from sqlalchemy.orm import Mapped, mapped_column, relationship +from .entity_base import EntityBase + + +class SampleEntity(EntityBase): + __tablename__ = "persons" + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + name: Mapped[str] = mapped_column(String, nullable=False) + age: Mapped[int] = mapped_column(Integer) + email: Mapped[str] = mapped_column(String, unique=True, nullable=False) diff --git a/backend/entities/service_entity.py b/backend/entities/service_entity.py new file mode 100644 index 0000000..e927be7 --- /dev/null +++ b/backend/entities/service_entity.py @@ -0,0 +1,44 @@ +""" Defines the table for storing services """ + +# Import our mapped SQL types from SQLAlchemy +from sqlalchemy import Integer, String, DateTime, ARRAY + +# Import mapping capabilities from the SQLAlchemy ORM +from sqlalchemy.orm import Mapped, mapped_column, relationship + +# Import the EntityBase that we are extending +from .entity_base import EntityBase + +# Import datetime for created_at type +from datetime import datetime + +# Import enums for Program +import enum +from sqlalchemy import Enum + + +class ProgramEnum(enum.Enum): + """Determine program for Service""" + + DOMESTIC = "DOMESTIC" + ECONOMIC = "ECONOMIC" + COMMUNITY = "COMMUNITY" + + +class ServiceEntity(EntityBase): + + # set table name + __tablename__ = "service" + + # set fields + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now) + name: Mapped[str] = mapped_column(String(32), nullable=False) + summary: Mapped[str] = mapped_column(String(100), nullable=False) + requirements: Mapped[list[str]] = mapped_column(ARRAY(String)) + program: Mapped[ProgramEnum] = mapped_column(Enum(ProgramEnum), nullable=False) + + # relationships + serviceTags: Mapped[list["ServiceTagEntity"]] = relationship( + back_populates="service", cascade="all,delete" + ) diff --git a/backend/entities/service_tag_entity.py b/backend/entities/service_tag_entity.py new file mode 100644 index 0000000..c1dbdc7 --- /dev/null +++ b/backend/entities/service_tag_entity.py @@ -0,0 +1,25 @@ +""" Defines the table for service tags """ + +# Import our mapped SQL types from SQLAlchemy +from sqlalchemy import ForeignKey, Integer + +# Import mapping capabilities from the SQLAlchemy ORM +from sqlalchemy.orm import Mapped, mapped_column, relationship + +# Import the EntityBase that we are extending +from .entity_base import EntityBase + + +class ServiceTagEntity(EntityBase): + + # set table name to user in the database + __tablename__ = "serviceTag" + + # set fields or 'columns' for the user table + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + serviceId: Mapped[int] = mapped_column(ForeignKey("service.id")) + tagId: Mapped[int] = mapped_column(ForeignKey("tag.id")) + + # relationships + service: Mapped["ServiceEntity"] = relationship(back_populates="resourceTags") + tag: Mapped["TagEntity"] = relationship(back_populates="resourceTags") diff --git a/backend/entities/tag_entity.py b/backend/entities/tag_entity.py new file mode 100644 index 0000000..e61f1ee --- /dev/null +++ b/backend/entities/tag_entity.py @@ -0,0 +1,62 @@ +""" Defines the table for storing tags """ + +# Import our mapped SQL types from SQLAlchemy +from sqlalchemy import Integer, String, DateTime + +# Import mapping capabilities from the SQLAlchemy ORM +from sqlalchemy.orm import Mapped, mapped_column, relationship + +# Import the EntityBase that we are extending +from .entity_base import EntityBase + +# Import datetime for created_at type +from datetime import datetime + +class TagEntity(EntityBase): + + #set table name + __tablename__ = "tag" + + #set fields + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now) + content: Mapped[str] = mapped_column(String(100), nullable=False) + + #relationships + resourceTags: Mapped[list["ResourceTagEntity"]] = relationship(back_populates="tag", cascade="all,delete") + serviceTags: Mapped[list["ServiceTagEntity"]] = relationship(back_populates="tag", cascade="all,delete") + + + """ + @classmethod + def from_model(cls, model: Tag) -> Self: + + Create a user entity from model + + Args: model (User): the model to create the entity from + + Returns: + self: The entity + + + return cls( + id=model.id, + content=model.id, + ) + + def to_model(self) -> Tag: + + Create a user model from entity + + Returns: + User: A User model for API usage + + + return Tag( + id=self.id, + content=self.id, + ) + + """ + + diff --git a/backend/entities/user_entity.py b/backend/entities/user_entity.py new file mode 100644 index 0000000..8f82e4d --- /dev/null +++ b/backend/entities/user_entity.py @@ -0,0 +1,90 @@ +""" Defines the table for storing users """ + +# Import our mapped SQL types from SQLAlchemy +from sqlalchemy import Integer, String, DateTime, ARRAY + + +# Import mapping capabilities from the SQLAlchemy ORM +from sqlalchemy.orm import Mapped, mapped_column + + +# Import the EntityBase that we are extending +from .entity_base import EntityBase + + +# Import datetime for created_at type +from datetime import datetime + + +# Import enums for Role and Program +from backend.entities.program_enum import ProgramEnum +from .user_enum import RoleEnum + + +class UserEntity(EntityBase): + """Serves as the database model for User table""" + + # set table name to user in the database + __tablename__ = "user" + + # set fields or 'columns' for the user table + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now) + username: Mapped[str] = mapped_column( + String(32), nullable=False, default="", unique=True + ) + role: Mapped[RoleEnum] = mapped_column(RoleEnum, nullable=False) + username: Mapped[str] = mapped_column( + String(32), nullable=False, default="", unique=True + ) + role: Mapped[RoleEnum] = mapped_column(RoleEnum, nullable=False) + email: Mapped[str] = mapped_column(String(50), nullable=False, unique=True) + program: Mapped[list[ProgramEnum]] = mapped_column( + ARRAY(ProgramEnum), nullable=False + ) + program: Mapped[list[ProgramEnum]] = mapped_column( + ARRAY(ProgramEnum), nullable=False + ) + experience: Mapped[int] = mapped_column(Integer, nullable=False) + group: Mapped[str] = mapped_column(String(50)) + + """ + @classmethod + def from_model(cls, model: User) -> Self: + + Create a user entity from model + + Args: model (User): the model to create the entity from + + Returns: + self: The entity + + + return cls( + id=model.id, + username=model.username, + role=model.role, + email=model.email, + program=model.program, + experience=model.experience, + group=model.group, + ) + + def to_model(self) -> User: + + Create a user model from entity + + Returns: + User: A User model for API usage + + + return User( + id=self.id, + username=self.id, + role=self.role, + email=self.email, + program=self.program, + experience=self.experience, + group=self.group, + ) + """ diff --git a/backend/entities/user_enum.py b/backend/entities/user_enum.py new file mode 100644 index 0000000..99594ec --- /dev/null +++ b/backend/entities/user_enum.py @@ -0,0 +1,12 @@ +from sqlalchemy import Enum + + +class RoleEnum(Enum): + """Determine role for User""" + + ADMIN = "ADMIN" + EMPLOYEE = "EMPLOYEE" + VOLUNTEER = "VOLUNTEER" + + def __init__(self): + super().__init__(name="role_enum") diff --git a/backend/env.py b/backend/env.py index 356efb7..940ba10 100644 --- a/backend/env.py +++ b/backend/env.py @@ -1,21 +1,21 @@ -"""Load environment variables from a .env file or the process' environment.""" - -import os -import dotenv - -# Load envirnment variables from .env file upon module start. -dotenv.load_dotenv(f"{os.path.dirname(__file__)}/.env", verbose=True) - - -def getenv(variable: str) -> str: - """Get value of environment variable or raise an error if undefined. - - Unlike `os.getenv`, our application expects all environment variables it needs to be defined - and we intentionally fast error out with a diagnostic message to avoid scenarios of running - the application when expected environment variables are not set. - """ - value = os.getenv(variable) - if value is not None: - return value - else: - raise NameError(f"Error: {variable} Environment Variable not Defined") +"""Load environment variables from a .env file or the process' environment.""" + +import os +import dotenv + +# Load envirnment variables from .env file upon module start. +dotenv.load_dotenv(f"{os.path.dirname(__file__)}/.env", verbose=True) + + +def getenv(variable: str) -> str: + """Get value of environment variable or raise an error if undefined. + + Unlike `os.getenv`, our application expects all environment variables it needs to be defined + and we intentionally fast error out with a diagnostic message to avoid scenarios of running + the application when expected environment variables are not set. + """ + value = os.getenv(variable) + if value is not None: + return value + else: + raise NameError(f"Error: {variable} Environment Variable not Defined") diff --git a/backend/models/enum_for_models.py b/backend/models/enum_for_models.py new file mode 100644 index 0000000..8e6cdfe --- /dev/null +++ b/backend/models/enum_for_models.py @@ -0,0 +1,17 @@ +from pydantic import BaseModel, Field +from enum import Enum +from typing import List +from datetime import datetime +from typing import Optional + + +class ProgramTypeEnum(str, Enum): + DOMESTIC = "DOMESTIC" + ECONOMIC = "ECONOMIC" + COMMUNITY = "COMMUNITY" + + +class UserTypeEnum(str, Enum): + ADMIN = "ADMIN" + EMPLOYEE = "EMPLOYEE" + VOLUNTEER = "VOLUNTEER" diff --git a/backend/models/resource_model.py b/backend/models/resource_model.py new file mode 100644 index 0000000..8601c41 --- /dev/null +++ b/backend/models/resource_model.py @@ -0,0 +1,16 @@ +from pydantic import BaseModel, Field +from enum import Enum +from typing import List +from datetime import datetime +from typing import Optional +from .enum_for_models import ProgramTypeEnum +from .resource_model import Resource + + +class Resource(BaseModel): + id: int | None = None + name: str = Field(..., max_length=150, description="The name of the resource") + summary: str = Field(..., max_length=300, description="The summary of the resource") + link: str = Field(..., max_length=150, description="link to the resource") + programtype: ProgramTypeEnum + created_at: Optional[datetime] diff --git a/backend/models/resource_tag_model.py b/backend/models/resource_tag_model.py new file mode 100644 index 0000000..0a4adc5 --- /dev/null +++ b/backend/models/resource_tag_model.py @@ -0,0 +1,13 @@ +from pydantic import BaseModel, Field +from enum import Enum +from typing import List +from datetime import datetime +from typing import Optional +from .tag_model import Tag +from .resource_model import Resource + + +class ResourceTag(Resource, BaseModel): + id: int | None = None + resourceid: int | None = None + tagid: List[Tag] diff --git a/backend/models/service_model.py b/backend/models/service_model.py new file mode 100644 index 0000000..36c336b --- /dev/null +++ b/backend/models/service_model.py @@ -0,0 +1,16 @@ +from pydantic import BaseModel, Field +from enum import Enum +from typing import List +from datetime import datetime +from typing import Optional +from .enum_for_models import ProgramTypeEnum + + +class Service(BaseModel): + id: int | None = None + created_at: datetime | None = None + name: str + status: str + summary: str + requirements: List[str] + program: ProgramTypeEnum diff --git a/backend/models/service_tag_model.py b/backend/models/service_tag_model.py new file mode 100644 index 0000000..b9c07a9 --- /dev/null +++ b/backend/models/service_tag_model.py @@ -0,0 +1,19 @@ +from pydantic import BaseModel, Field +from enum import Enum +from typing import List +from datetime import datetime +from typing import Optional + +from .enum_for_models import ProgramTypeEnum +from .enum_for_models import UserTypeEnum +from .service_model import Service + +from .tag_model import Tag +from pydantic import BaseModel +from datetime import datetime + + +class ServiceTag(Service, BaseModel): + id: int | None = None + serviceid: int | None = None + tagId: List[Tag] diff --git a/backend/models/tag_model.py b/backend/models/tag_model.py new file mode 100644 index 0000000..44dcb02 --- /dev/null +++ b/backend/models/tag_model.py @@ -0,0 +1,13 @@ +from pydantic import BaseModel, Field +from enum import Enum +from typing import List +from datetime import datetime +from typing import Optional + + +class Tag(BaseModel): + id: int | None = None + content: str = Field( + ..., max_length=600, description="content associated with the tag" + ) + created_at: datetime | None = None diff --git a/backend/models/user_model.py b/backend/models/user_model.py new file mode 100644 index 0000000..c881d54 --- /dev/null +++ b/backend/models/user_model.py @@ -0,0 +1,17 @@ +from pydantic import BaseModel, Field +from enum import Enum +from typing import List +from datetime import datetime +from typing import Optional +from .enum_for_models import UserTypeEnum, ProgramTypeEnum + + +class User(BaseModel): + id: int | None = None + username: str = Field(..., description="The username of the user") + email: str = Field(..., description="The e-mail of the user") + experience: int = Field(..., description="Years of Experience of the User") + group: str + programtype: List[ProgramTypeEnum] + usertype: UserTypeEnum + created_at: Optional[datetime] diff --git a/backend/requirements.txt b/backend/requirements.txt index 5c532a5..af3beb6 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,6 +1,6 @@ -fastapi[all] >=0.100.0, <0.101.0 -sqlalchemy >=2.0.4, <2.1.0 -psycopg2 >=2.9.5, <2.10.0 -alembic >=1.10.2, <1.11.0 -pytest >=7.2.1, <7.3.0 +fastapi[all] >=0.100.0, <0.101.0 +sqlalchemy >=2.0.4, <2.1.0 +psycopg2 >=2.9.5, <2.10.0 +alembic >=1.10.2, <1.11.0 +pytest >=7.2.1, <7.3.0 python-dotenv >=1.0.0, <1.1.0 \ No newline at end of file diff --git a/backend/script/create_database.py b/backend/script/create_database.py index 197176f..f969f0b 100644 --- a/backend/script/create_database.py +++ b/backend/script/create_database.py @@ -1,14 +1,14 @@ -from sqlalchemy import text, create_engine -from ..database import engine, _engine_str -from ..env import getenv - -engine = create_engine(_engine_str(database=""), echo=True) -"""Application-level SQLAlchemy database engine.""" - -with engine.connect() as connection: - connection.execute( - text("COMMIT") - ) - database = getenv("POSTGRES_DATABASE") - stmt = text(f"CREATE DATABASE {database}") +from sqlalchemy import text, create_engine +from ..database import engine, _engine_str +from ..env import getenv + +engine = create_engine(_engine_str(database=""), echo=True) +"""Application-level SQLAlchemy database engine.""" + +with engine.connect() as connection: + connection.execute( + text("COMMIT") + ) + database = getenv("POSTGRES_DATABASE") + stmt = text(f"CREATE DATABASE {database}") connection.execute(stmt) \ No newline at end of file diff --git a/backend/script/delete_database.py b/backend/script/delete_database.py index 44b3f6a..513aec4 100644 --- a/backend/script/delete_database.py +++ b/backend/script/delete_database.py @@ -1,14 +1,14 @@ -from sqlalchemy import text, create_engine -from ..database import engine, _engine_str -from ..env import getenv - -engine = create_engine(_engine_str(database=""), echo=True) -"""Application-level SQLAlchemy database engine.""" - -with engine.connect() as connection: - connection.execute( - text("COMMIT") - ) - database = getenv("POSTGRES_DATABASE") - stmt = text(f"DROP DATABASE IF EXISTS {database}") +from sqlalchemy import text, create_engine +from ..database import engine, _engine_str +from ..env import getenv + +engine = create_engine(_engine_str(database=""), echo=True) +"""Application-level SQLAlchemy database engine.""" + +with engine.connect() as connection: + connection.execute( + text("COMMIT") + ) + database = getenv("POSTGRES_DATABASE") + stmt = text(f"DROP DATABASE IF EXISTS {database}") connection.execute(stmt) \ No newline at end of file diff --git a/backend/script/reset_demo.py b/backend/script/reset_demo.py index da06da8..d4c1aa0 100644 --- a/backend/script/reset_demo.py +++ b/backend/script/reset_demo.py @@ -1,18 +1,18 @@ -from sqlalchemy import create_engine -import subprocess - -from ..database import engine, _engine_str -from ..env import getenv -from .. import entities - -database = getenv("POSTGRES_DATABASE") - -engine = create_engine(_engine_str(), echo=True) -"""Application-level SQLAlchemy database engine.""" - -# Run Delete and Create Database Scripts -subprocess.run(["python3", "-m", "backend.script.delete_database"]) -subprocess.run(["python3", "-m", "backend.script.create_database"]) - -entities.EntityBase.metadata.drop_all(engine) -entities.EntityBase.metadata.create_all(engine) +from sqlalchemy import create_engine +import subprocess + +from ..database import engine, _engine_str +from ..env import getenv +from .. import entities + +database = getenv("POSTGRES_DATABASE") + +engine = create_engine(_engine_str(), echo=True) +"""Application-level SQLAlchemy database engine.""" + +# Run Delete and Create Database Scripts +subprocess.run(["python3", "-m", "backend.script.delete_database"]) +subprocess.run(["python3", "-m", "backend.script.create_database"]) + +entities.EntityBase.metadata.drop_all(engine) +entities.EntityBase.metadata.create_all(engine) diff --git a/backend/test/entities/conftest.py b/backend/test/entities/conftest.py index 5b2a984..63e15a5 100644 --- a/backend/test/entities/conftest.py +++ b/backend/test/entities/conftest.py @@ -1,50 +1,50 @@ -"""Shared pytest fixtures for database dependent tests.""" - -import pytest -from sqlalchemy import Engine, create_engine, text -from sqlalchemy.orm import Session -from sqlalchemy.exc import OperationalError - -from ...database import _engine_str -from ...env import getenv -from ... import entities - -POSTGRES_DATABASE = f'{getenv("POSTGRES_DATABASE")}_test' -POSTGRES_USER = getenv("POSTGRES_USER") - -def reset_database(): - engine = create_engine(_engine_str(database="")) - with engine.connect() as connection: - try: - conn = connection.execution_options(autocommit=False) - conn.execute(text("ROLLBACK")) # Get out of transactional mode... - conn.execute(text(f"DROP DATABASE IF EXISTS {POSTGRES_DATABASE}")) - except OperationalError: - print( - "Could not drop database because it's being accessed by others (psql open?)" - ) - exit(1) - - conn.execute(text(f"CREATE DATABASE {POSTGRES_DATABASE}")) - conn.execute( - text( - f"GRANT ALL PRIVILEGES ON DATABASE {POSTGRES_DATABASE} TO {POSTGRES_USER}" - ) - ) - - -@pytest.fixture(scope="session") -def test_engine() -> Engine: - reset_database() - return create_engine(_engine_str(POSTGRES_DATABASE)) - - -@pytest.fixture(scope="function") -def session(test_engine: Engine): - entities.EntityBase.metadata.drop_all(test_engine) - entities.EntityBase.metadata.create_all(test_engine) - session = Session(test_engine) - try: - yield session - finally: - session.close() +"""Shared pytest fixtures for database dependent tests.""" + +import pytest +from sqlalchemy import Engine, create_engine, text +from sqlalchemy.orm import Session +from sqlalchemy.exc import OperationalError + +from ...database import _engine_str +from ...env import getenv +from ... import entities + +POSTGRES_DATABASE = f'{getenv("POSTGRES_DATABASE")}_test' +POSTGRES_USER = getenv("POSTGRES_USER") + +def reset_database(): + engine = create_engine(_engine_str(database="")) + with engine.connect() as connection: + try: + conn = connection.execution_options(autocommit=False) + conn.execute(text("ROLLBACK")) # Get out of transactional mode... + conn.execute(text(f"DROP DATABASE IF EXISTS {POSTGRES_DATABASE}")) + except OperationalError: + print( + "Could not drop database because it's being accessed by others (psql open?)" + ) + exit(1) + + conn.execute(text(f"CREATE DATABASE {POSTGRES_DATABASE}")) + conn.execute( + text( + f"GRANT ALL PRIVILEGES ON DATABASE {POSTGRES_DATABASE} TO {POSTGRES_USER}" + ) + ) + + +@pytest.fixture(scope="session") +def test_engine() -> Engine: + reset_database() + return create_engine(_engine_str(POSTGRES_DATABASE)) + + +@pytest.fixture(scope="function") +def session(test_engine: Engine): + entities.EntityBase.metadata.drop_all(test_engine) + entities.EntityBase.metadata.create_all(test_engine) + session = Session(test_engine) + try: + yield session + finally: + session.close() diff --git a/backend/test/entities/sample_test.py b/backend/test/entities/sample_test.py index f21423e..637769c 100644 --- a/backend/test/entities/sample_test.py +++ b/backend/test/entities/sample_test.py @@ -1,21 +1,21 @@ -"""Sample Test File""" - -from sqlalchemy import Engine, select - -from ... import entities -from ...entities.sample_entity import SampleEntity - - -def test_entity_count(): - """Checks the number of entities to be inserted""" - print(entities.EntityBase.metadata.tables.keys()) - assert len(entities.EntityBase.metadata.tables.keys()) == 1 - - -def test_add_sample_data(session: Engine): - """Inserts a sample data point and verifies it is in the database""" - entity = SampleEntity(name="Praj", age=19, email="pmoha@unc.edu") - session.add(entity) - session.commit() - data = session.get(SampleEntity, 1) - assert data.name == "Praj" +"""Sample Test File""" + +from sqlalchemy import Engine, select + +from ... import entities +from ...entities.sample_entity import SampleEntity + + +def test_entity_count(): + """Checks the number of entities to be inserted""" + print(entities.EntityBase.metadata.tables.keys()) + assert len(entities.EntityBase.metadata.tables.keys()) == 1 + + +def test_add_sample_data(session: Engine): + """Inserts a sample data point and verifies it is in the database""" + entity = SampleEntity(name="Praj", age=19, email="pmoha@unc.edu") + session.add(entity) + session.commit() + data = session.get(SampleEntity, 1) + assert data.name == "Praj" diff --git a/backend/test/entities/tag_entity_test.py b/backend/test/entities/tag_entity_test.py new file mode 100644 index 0000000..b2b2f7f --- /dev/null +++ b/backend/test/entities/tag_entity_test.py @@ -0,0 +1,19 @@ +""" Testing Tag Entity """ + +from sqlalchemy import Engine +from ... import entities +from ...entities.tag_entity import TagEntity + + +def test_add_sample_data_tag(session: Engine): + + """Inserts a sample data point and verifies it is in the database""" + entity = TagEntity(content="Test tag") + session.add(entity) + session.commit() + data = session.get(TagEntity, 1) + assert data.id == 1 + assert data.content == "Test tag" + + + \ No newline at end of file diff --git a/backend/test/entities/user_entity_test.py b/backend/test/entities/user_entity_test.py new file mode 100644 index 0000000..1f775ce --- /dev/null +++ b/backend/test/entities/user_entity_test.py @@ -0,0 +1,24 @@ +""" Testing User Entity """ + +from sqlalchemy import Engine +from ... import entities +from ...entities.user_entity import UserEntity +from ...entities.user_entity import RoleEnum +from ...entities.user_entity import ProgramEnum + +def test_add_sample_data_user(session: Engine): + + + """Inserts a sample data point and verifies it is in the database""" + entity = UserEntity(id=1, username="emmalynf", role=RoleEnum.ADMIN, email="efoster@unc.edu", program=[ProgramEnum.COMMUNITY, ProgramEnum.DOMESTIC, ProgramEnum.ECONOMIC], experience=10, group="group") + session.add(entity) + session.commit() + data = session.get(UserEntity, 1) + assert data.id == 1 + assert data.username == "emmalynf" + assert data.email == "efoster@unc.edu" + assert data.experience == 10 + assert data.role == RoleEnum.ADMIN + assert data.program == [ProgramEnum.COMMUNITY, ProgramEnum.DOMESTIC, ProgramEnum.ECONOMIC] + + \ No newline at end of file diff --git a/compass/.eslintrc.json b/compass/.eslintrc.json index bffb357..ea782d7 100644 --- a/compass/.eslintrc.json +++ b/compass/.eslintrc.json @@ -1,3 +1,3 @@ -{ - "extends": "next/core-web-vitals" -} +{ + "extends": "next/core-web-vitals" +} diff --git a/compass/.gitignore b/compass/.gitignore index 8f322f0..105a425 100644 --- a/compass/.gitignore +++ b/compass/.gitignore @@ -1,35 +1,35 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/compass/README.md b/compass/README.md index c403366..73ea1f0 100644 --- a/compass/README.md +++ b/compass/README.md @@ -1,36 +1,36 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/compass/app/admin/page.tsx b/compass/app/admin/page.tsx index ed6babe..e803aad 100644 --- a/compass/app/admin/page.tsx +++ b/compass/app/admin/page.tsx @@ -1,3 +1,4 @@ + "use client"; // import Table from "@/components/Table"; import { ColumnDef, createColumnHelper, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"; @@ -80,4 +81,17 @@ export default function Page() { ); + + +import { PageLayout } from "@/components/admin/PageLayout" + +import { BookmarkIcon } from "@heroicons/react/24/solid" + +export default function Page() { + return ( +
+ {/* icon + title */} + } /> +
+ ) } diff --git a/compass/app/auth/forgot_password/page.tsx b/compass/app/auth/forgot_password/page.tsx index 555f0cd..c8493b0 100644 --- a/compass/app/auth/forgot_password/page.tsx +++ b/compass/app/auth/forgot_password/page.tsx @@ -1,11 +1,10 @@ // pages/forgot-password.tsx "use client"; -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import Input from '@/components/Input'; import Button from '@/components/Button'; import InlineLink from '@/components/InlineLink'; -import Paper from '@/components/auth/Paper'; import ErrorBanner from '@/components/auth/ErrorBanner'; diff --git a/compass/app/auth/layout.tsx b/compass/app/auth/layout.tsx index 6a6fb96..1181fe6 100644 --- a/compass/app/auth/layout.tsx +++ b/compass/app/auth/layout.tsx @@ -1,6 +1,6 @@ import Paper from '@/components/auth/Paper'; - + export default function RootLayout({ // Layouts must accept a children prop. @@ -11,12 +11,12 @@ export default function RootLayout({ }) { return ( -
- {children} -
-

- © 2024 Compass Center -

-
+
+ {children} +
+

+ © 2024 Compass Center +

+ ) } \ No newline at end of file diff --git a/compass/app/auth/login/page.tsx b/compass/app/auth/login/page.tsx index 5055714..ccb8b2c 100644 --- a/compass/app/auth/login/page.tsx +++ b/compass/app/auth/login/page.tsx @@ -4,7 +4,6 @@ import Button from '@/components/Button'; import Input from '@/components/Input' import InlineLink from '@/components/InlineLink'; -import Paper from '@/components/auth/Paper'; import Image from 'next/image'; import { useState } from "react"; import PasswordInput from '@/components/auth/PasswordInput'; @@ -52,7 +51,7 @@ export default function Page() { height={91} /> -

Login

+

Login

diff --git a/compass/app/auth/new_password/page.tsx b/compass/app/auth/new_password/page.tsx index 3fb3e83..1d9ca52 100644 --- a/compass/app/auth/new_password/page.tsx +++ b/compass/app/auth/new_password/page.tsx @@ -2,8 +2,6 @@ "use client"; import { useState, useEffect } from 'react'; import Button from '@/components/Button'; - -import Paper from '@/components/auth/Paper'; import PasswordInput from '@/components/auth/PasswordInput'; import ErrorBanner from '@/components/auth/ErrorBanner'; diff --git a/compass/app/layout.tsx b/compass/app/layout.tsx index 2509089..e9ef9ca 100644 --- a/compass/app/layout.tsx +++ b/compass/app/layout.tsx @@ -1,20 +1,16 @@ -import '../styles/globals.css'; -import { Metadata } from 'next' - -export const metadata: Metadata = { - title: 'Login', -} - -export default function RootLayout({ - // Layouts must accept a children prop. - // This will be populated with nested layouts or pages - children, -}: { - children: React.ReactNode -}) { - return ( - - {children} - - ) +import '../styles/globals.css'; + + +export default function RootLayout({ + // Layouts must accept a children prop. + // This will be populated with nested layouts or pages + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) } \ No newline at end of file diff --git a/compass/app/pages/page/page.tsx b/compass/app/pages/page/page.tsx new file mode 100644 index 0000000..dce1122 --- /dev/null +++ b/compass/app/pages/page/page.tsx @@ -0,0 +1,26 @@ +// pages/index.tsx +"use client"; + + +import Drawer from '@/components/page/Drawer'; +// import { Metadata } from 'next' +import {ChangeEvent, useState} from "react"; + +// export const metadata: Metadata = { +// title: 'Login', +// } + +export default function Page() { + const [pageContent, setPageContent] = useState("") + + const handleDrawerContentChange = (newContent) => { + setPageContent(newContent); + }; + + return ( + <> +

Resources

+ {pageContent} + + ); +}; \ No newline at end of file diff --git a/compass/app/resource/layout.tsx b/compass/app/resource/layout.tsx new file mode 100644 index 0000000..bb7fddd --- /dev/null +++ b/compass/app/resource/layout.tsx @@ -0,0 +1,37 @@ +"use client" + +import Sidebar from '@/components/resource/Sidebar'; +import React, { useState } from 'react'; +import { ChevronDoubleRightIcon } from '@heroicons/react/24/outline'; + +export default function RootLayout({ + + children, +}: { + children: React.ReactNode +}) { + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + + return ( +
+ {/* button to open sidebar */} + + {/* sidebar */} +
+ +
+ {/* page ui */} +
+ {children} +
+
+ ) +} \ No newline at end of file diff --git a/compass/app/resource/page.tsx b/compass/app/resource/page.tsx new file mode 100644 index 0000000..2e4f4cd --- /dev/null +++ b/compass/app/resource/page.tsx @@ -0,0 +1,39 @@ +"use client" +import Callout from "@/components/resource/Callout"; +import Card from "@/components/resource/Card"; +import { LandingSearchBar } from "@/components/resource/LandingSearchBar"; +import { BookOpenIcon, BookmarkIcon, ClipboardIcon } from "@heroicons/react/24/solid"; +import Image from 'next/image'; + +export default function Page() { + return ( +
+ {/* icon + title */} +
+
+ Compass Center logo. +

Compass Center Advocate Landing Page

+
+ + Welcome! Below you will find a list of resources for the Compass Center's trained advocates. These materials serve to virtually provide a collection of advocacy, resource, and hotline manuals and information. + If you are an advocate looking for the contact information of a particular Compass Center employee, please directly contact your staff back-up or the person in charge of your training. + +
+
+ {/* link to different pages */} +
+ } text="Resources" /> + } text="Services" /> + } text="Training Manuals" /> +
+ {/* search bar */} + +
+
+ ) +} diff --git a/compass/components/InlineLink.tsx b/compass/components/InlineLink.tsx index f2e45ef..018a5c3 100644 --- a/compass/components/InlineLink.tsx +++ b/compass/components/InlineLink.tsx @@ -1,15 +1,20 @@ import React, { ReactNode } from 'react'; interface Link { - href?: string; - children: ReactNode; + onClick?: (event: React.MouseEvent) => void; + href?: string; + children: ReactNode; } -const InlineLink: React.FC = ({href = '#', children}) => { +const InlineLink: React.FC = ({href = '#', children, onClick}) => { return ( - - {children} - + + {children} + ) } diff --git a/compass/components/Input.tsx b/compass/components/Input.tsx index d7fafc7..b8eff67 100644 --- a/compass/components/Input.tsx +++ b/compass/components/Input.tsx @@ -1,37 +1,37 @@ -import React, { FunctionComponent, InputHTMLAttributes, ReactNode, ChangeEvent } from 'react'; - -type InputProps = InputHTMLAttributes & { - icon?: ReactNode; - title?: ReactNode; - type?:ReactNode; - placeholder?:ReactNode - valid?:boolean; - onChange: (event: ChangeEvent) => void; -}; - -const Input: FunctionComponent = ({ icon, type, title, placeholder, onChange, valid = true, ...rest }) => { - return ( -
- -
- ); -}; - -export default Input; +import React, { FunctionComponent, InputHTMLAttributes, ReactNode, ChangeEvent } from 'react'; + +type InputProps = InputHTMLAttributes & { + icon?: ReactNode; + title?: ReactNode; + type?:ReactNode; + placeholder?:ReactNode + valid?:boolean; + onChange: (event: ChangeEvent) => void; +}; + +const Input: FunctionComponent = ({ icon, type, title, placeholder, onChange, valid = true, ...rest }) => { + return ( +
+ +
+ ); +}; + +export default Input; diff --git a/compass/components/admin/PageLayout.tsx b/compass/components/admin/PageLayout.tsx new file mode 100644 index 0000000..f27a96a --- /dev/null +++ b/compass/components/admin/PageLayout.tsx @@ -0,0 +1,23 @@ +interface PageLayoutProps { + icon: React.ReactElement; + title: string; + // table: React.ReactElement +} + +export const PageLayout: React.FC = ({ icon, title, children }) => { + return ( +
+ {/* icon + title */} +
+
+ {icon} +

{title}

+
+
+ {/* data */} +
+ {children} +
+
+ ); +}; diff --git a/compass/components/auth/Paper.tsx b/compass/components/auth/Paper.tsx index 95b9ba0..de9d4b3 100644 --- a/compass/components/auth/Paper.tsx +++ b/compass/components/auth/Paper.tsx @@ -6,7 +6,7 @@ interface PageInterface { const Paper: React.FC = ({ children }) => { return ( -
+
{children}
); diff --git a/compass/components/page/Drawer.tsx b/compass/components/page/Drawer.tsx new file mode 100644 index 0000000..217fbd2 --- /dev/null +++ b/compass/components/page/Drawer.tsx @@ -0,0 +1,76 @@ +import { FunctionComponent, ReactNode } from 'react'; +import Button from '@/components/Button' +import React, { useState } from 'react'; +import {DATATYPE} from '@/utils/constants' +import InlineLink from '@/components/InlineLink' + + +type DrawerProps = { + title: string; + children: ReactNode; + onClick?: (event: React.MouseEvent) => void; + type?: "button" | "submit" | "reset"; // specify possible values for type + disabled?: boolean; + editableContent?: any; + onSave?: (content: any) => void; +}; + +const Drawer: FunctionComponent = ({ title, children, onSave, editableContent }) => { + const [isOpen, setIsOpen] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const [editContent, setEditContent] = useState(editableContent || ''); + + const toggleDrawer = () => setIsOpen(!isOpen); + const toggleEditing = () => setIsEditing(!isEditing); + const handleContentChange = (event: React.ChangeEvent) => { + setEditContent(event.target.value); + }; + + const drawerClassName = `fixed top-0 right-0 w-1/2 h-full bg-white shadow-xl transform ease-in-out duration-300 ${ + isOpen ? "translate-x-0" : "translate-x-full" + }`; + + const saveChanges = () => { + console.log(editContent); + if (onSave) { + onSave(editContent); + } + setIsEditing(false); + }; + + const addRow = () => { + + } + + return ( +
+ +
+
+

{title}

+
+ + +
+
+
+ {isEditing ? ( + <> + + Save + + ) : ( + children + )} +
+
+
+ ); +}; + +export default Drawer; \ No newline at end of file diff --git a/compass/components/page/Field.tsx b/compass/components/page/Field.tsx new file mode 100644 index 0000000..9ee44b1 --- /dev/null +++ b/compass/components/page/Field.tsx @@ -0,0 +1,4 @@ +// components/Field.tsx +import { FunctionComponent, ReactNode } from 'react'; +import Button from '@/components/Button' +import React, { useState } from 'react'; diff --git a/compass/components/resource/Callout.tsx b/compass/components/resource/Callout.tsx new file mode 100644 index 0000000..c51f3f2 --- /dev/null +++ b/compass/components/resource/Callout.tsx @@ -0,0 +1,15 @@ +import { ReactNode } from "react"; + +interface CalloutProps { + children: ReactNode; +} + +const Callout = ({ children }: CalloutProps) => { + return ( +
+ {children} +
+ ); +}; + +export default Callout; \ No newline at end of file diff --git a/compass/components/resource/Card.tsx b/compass/components/resource/Card.tsx new file mode 100644 index 0000000..140542b --- /dev/null +++ b/compass/components/resource/Card.tsx @@ -0,0 +1,20 @@ +import React, { ReactNode } from "react"; + + +interface TagProps { + text: string; + icon: React.ReactNode; +} + +const Card: React.FC = ({ text, icon }) => { + return ( +
+ + {icon} + + {text} +
+ ); +}; + +export default Card; \ No newline at end of file diff --git a/compass/components/resource/LandingSearchBar.tsx b/compass/components/resource/LandingSearchBar.tsx new file mode 100644 index 0000000..f52b6ba --- /dev/null +++ b/compass/components/resource/LandingSearchBar.tsx @@ -0,0 +1,48 @@ +import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/solid" +import React, { useState } from 'react'; +import Image from 'next/image'; + +export const LandingSearchBar: React.FC = () => { + const [searchTerm, setSearchTerm] = useState(''); + + const handleSearchChange = (event: React.ChangeEvent) => { + setSearchTerm(event.target.value); + }; + + const clearSearch = () => { + setSearchTerm(''); + }; + + return ( +
+ {/* searchbar */} +
+
+ +
+ {/* input */} + {searchTerm && ( + + )} +
+
+
+ {/* search results, for now since it's empty this is the default screen */} +
+ Landing illustration +

Need to find something? Use the links or the search bar above to get your results.

+
+
+ ); +}; diff --git a/compass/components/resource/Sidebar.tsx b/compass/components/resource/Sidebar.tsx new file mode 100644 index 0000000..97e59d4 --- /dev/null +++ b/compass/components/resource/Sidebar.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { HomeIcon, ChevronDoubleLeftIcon, BookmarkIcon, ClipboardIcon, BookOpenIcon } from '@heroicons/react/24/solid'; +import { SidebarItem } from './SidebarItem'; +import { UserProfile } from './UserProfile'; + +interface SidebarProps { + setIsSidebarOpen: React.Dispatch>; +} + +const Sidebar: React.FC = ({ setIsSidebarOpen }) => { + return ( +
+ {/* button to close sidebar */} +
+ +
+
+ + {/* user + logout button */} +
+ + +
+ {/* navigation menu */} +
+

Pages

+ +
+
+
+ ); +}; + + +export default Sidebar; \ No newline at end of file diff --git a/compass/components/resource/SidebarItem.tsx b/compass/components/resource/SidebarItem.tsx new file mode 100644 index 0000000..db7a4db --- /dev/null +++ b/compass/components/resource/SidebarItem.tsx @@ -0,0 +1,16 @@ + +interface SidebarItemProps { + icon: React.ReactElement; + text: string; +} + +export const SidebarItem: React.FC = ({ icon, text }) => { + return ( + + + {icon} + + {text} + + ); +}; \ No newline at end of file diff --git a/compass/components/resource/UserProfile.tsx b/compass/components/resource/UserProfile.tsx new file mode 100644 index 0000000..0471596 --- /dev/null +++ b/compass/components/resource/UserProfile.tsx @@ -0,0 +1,11 @@ +export const UserProfile = () => { + return ( +
+
+ Compass Center + cssgunc@gmail.com +
+ +
+ ) +} \ No newline at end of file diff --git a/compass/next.config.js b/compass/next.config.js index 767719f..9fff58d 100644 --- a/compass/next.config.js +++ b/compass/next.config.js @@ -1,4 +1,8 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = { + images: { + domains: ['notioly.com'] + }, +} module.exports = nextConfig diff --git a/compass/package-lock.json b/compass/package-lock.json index fd7ab14..1c26973 100644 --- a/compass/package-lock.json +++ b/compass/package-lock.json @@ -4395,3 +4395,4 @@ } } } + diff --git a/compass/package.json b/compass/package.json index 2fee5d7..4036d60 100644 --- a/compass/package.json +++ b/compass/package.json @@ -1,3 +1,4 @@ + { "name": "compass", "version": "0.1.0", diff --git a/compass/postcss.config.js b/compass/postcss.config.js index 33ad091..a03e681 100644 --- a/compass/postcss.config.js +++ b/compass/postcss.config.js @@ -1,6 +1,6 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/compass/public/landing_illustration.png b/compass/public/landing_illustration.png new file mode 100644 index 0000000..ac853ce Binary files /dev/null and b/compass/public/landing_illustration.png differ diff --git a/compass/styles/globals.css b/compass/styles/globals.css index bec3b2c..9fb976a 100644 --- a/compass/styles/globals.css +++ b/compass/styles/globals.css @@ -36,12 +36,61 @@ font-family: 'Inter'; font-style: normal; font-weight: 400; - src: url('/fonts/Inter-Regular.ttf') format('ttf'), - url('/fonts/Inter-Bold.ttf') format('ttf'), - url('/fonts/Inter-Black.ttf') format('ttf'), - url('/fonts/Inter-ExtraBold.ttf') format('ttf'), - url('/fonts/Inter-ExtraLight.ttf') format('ttf'), - url('/fonts/Inter-Medium.ttf') format('ttf'), - url('/fonts/Inter-SemiBold.ttf') format('ttf'), - url('/fonts/Inter-Thin.ttf') format('ttf'); - } \ No newline at end of file + src: url('/fonts/Inter-Regular.ttf') format('truetype'); +} + +/* Inter-Bold */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 700; + src: url('/fonts/Inter-Bold.ttf') format('truetype'); +} + +/* Inter-Black */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 900; + src: url('/fonts/Inter-Black.ttf') format('truetype'); +} + +/* Inter-ExtraBold */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 800; + src: url('/fonts/Inter-ExtraBold.ttf') format('truetype'); +} + +/* Inter-ExtraLight */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 200; + src: url('/fonts/Inter-ExtraLight.ttf') format('truetype'); +} + +/* Inter-Medium */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 500; + src: url('/fonts/Inter-Medium.ttf') format('truetype'); +} + +/* Inter-SemiBold */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 600; + src: url('/fonts/Inter-SemiBold.ttf') format('truetype'); +} + +/* Inter-Thin */ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 100; + src: url('/fonts/Inter-Thin.ttf') format('truetype'); +} \ No newline at end of file diff --git a/compass/tailwind.config.ts b/compass/tailwind.config.ts index abab24e..d0ea17c 100644 --- a/compass/tailwind.config.ts +++ b/compass/tailwind.config.ts @@ -13,8 +13,11 @@ const config: Config = { 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', }, fontFamily: { - sans: ['Inter', 'sans-serif'], + 'sans': ['Inter', 'sans-serif'], // Add 'Inter' to the fontFamily theme }, + fontWeight: { + 'medium': 500, // Ensure medium is correctly set to 500 + } }, }, plugins: [], diff --git a/compass/utils/classes/CollectionImpl.tsx b/compass/utils/classes/CollectionImpl.tsx index 8d8a46a..a30e625 100644 --- a/compass/utils/classes/CollectionImpl.tsx +++ b/compass/utils/classes/CollectionImpl.tsx @@ -1,16 +1,16 @@ -class CollectionImpl { - title: string; - icon: any; - data: any; - - constructor(title: string, icon: any) { - this.title = title; - this.icon = icon; - } - - // subject to change - setData(data: any){ - this.data = data; - } - +class CollectionImpl { + title: string; + icon: any; + data: any; + + constructor(title: string, icon: any) { + this.title = title; + this.icon = icon; + } + + // subject to change + setData(data: any){ + this.data = data; + } + } \ No newline at end of file diff --git a/compass/utils/constants.tsx b/compass/utils/constants.tsx index d715bb7..2792d3a 100644 --- a/compass/utils/constants.tsx +++ b/compass/utils/constants.tsx @@ -1,54 +1,54 @@ -import { ListBulletIcon, HashtagIcon, Bars3BottomLeftIcon, EnvelopeIcon, AtSymbolIcon, ClipboardIcon, ArrowsUpDownIcon, ChevronDoubleRightIcon, ChevronDoubleLeftIcon, ChevronRightIcon, ChevronLeftIcon, EyeIcon, EyeSlashIcon, UserIcon, BookOpenIcon, MagnifyingGlassIcon, LinkIcon } from '@heroicons/react/24/solid'; - -export const Icons = { - EmailInputIcon: EnvelopeIcon, - HidePasswordIcon: EyeSlashIcon, - UnhidePasswordIcon: EyeIcon, - UserIcon: UserIcon, - ResourceIcon: BookOpenIcon, - SearchIcon: MagnifyingGlassIcon, - ServiceIcon: ClipboardIcon, - CloseRightArrow: ChevronDoubleRightIcon, - CloseLeftArrow: ChevronDoubleLeftIcon, - LinkRightArrow:ChevronRightIcon, - LinkLeftArrow:ChevronLeftIcon, - SortIcon: ArrowsUpDownIcon, - EmailTableIcon:AtSymbolIcon, - LinkTableIcon: LinkIcon, - TextTableIcon: Bars3BottomLeftIcon, - NumberTableIcon: HashtagIcon, - MultiselectTableIcon: ListBulletIcon -}; - -export enum User { - ADMIN, - EMPLOYEE, - VOLUNTEER -} - -export enum COLLECTION { - RESOURCE, - SERVICE, - USER -} - -export enum PROGRAM { - DOMESTIC_VIOLENCE, - ECONOMIC_STABILITY, - COMMUNITY_EDUCATION -} - -export enum DATATYPE { - INTEGER, - STRING, - LINK, - EMAIL, - MULTISELECT, - SELECT -} - -// export const COLLECTION_MAP: {[key in COLLECTION]: CollectionImpl} = { -// [COLLECTION.RESOURCE]: new CollectionImpl('Resources', Icons.ResourceIcon), -// [COLLECTION.SERVICE]: new CollectionImpl('Services', Icons.ServiceIcon), -// [COLLECTION.USER]: new CollectionImpl('Users', Icons.UserIcon) +import { ListBulletIcon, HashtagIcon, Bars3BottomLeftIcon, EnvelopeIcon, AtSymbolIcon, ClipboardIcon, ArrowsUpDownIcon, ChevronDoubleRightIcon, ChevronDoubleLeftIcon, ChevronRightIcon, ChevronLeftIcon, EyeIcon, EyeSlashIcon, UserIcon, BookOpenIcon, MagnifyingGlassIcon, LinkIcon } from '@heroicons/react/24/solid'; + +export const Icons = { + EmailInputIcon: EnvelopeIcon, + HidePasswordIcon: EyeSlashIcon, + UnhidePasswordIcon: EyeIcon, + UserIcon: UserIcon, + ResourceIcon: BookOpenIcon, + SearchIcon: MagnifyingGlassIcon, + ServiceIcon: ClipboardIcon, + CloseRightArrow: ChevronDoubleRightIcon, + CloseLeftArrow: ChevronDoubleLeftIcon, + LinkRightArrow:ChevronRightIcon, + LinkLeftArrow:ChevronLeftIcon, + SortIcon: ArrowsUpDownIcon, + EmailTableIcon:AtSymbolIcon, + LinkTableIcon: LinkIcon, + TextTableIcon: Bars3BottomLeftIcon, + NumberTableIcon: HashtagIcon, + MultiselectTableIcon: ListBulletIcon +}; + +export enum User { + ADMIN, + EMPLOYEE, + VOLUNTEER +} + +export enum COLLECTION { + RESOURCE, + SERVICE, + USER +} + +export enum PROGRAM { + DOMESTIC_VIOLENCE, + ECONOMIC_STABILITY, + COMMUNITY_EDUCATION +} + +export enum DATATYPE { + INTEGER, + STRING, + LINK, + EMAIL, + MULTISELECT, + SELECT +} + +// export const COLLECTION_MAP: {[key in COLLECTION]: CollectionImpl} = { +// [COLLECTION.RESOURCE]: new CollectionImpl('Resources', Icons.ResourceIcon), +// [COLLECTION.SERVICE]: new CollectionImpl('Services', Icons.ServiceIcon), +// [COLLECTION.USER]: new CollectionImpl('Users', Icons.UserIcon) // } \ No newline at end of file