diff --git a/backend/app/models/projects.py b/backend/app/models/projects.py index 153651b..4297fa5 100644 --- a/backend/app/models/projects.py +++ b/backend/app/models/projects.py @@ -16,7 +16,9 @@ class Project(base): ceo_id = Column(Integer, ForeignKey("users.id"), nullable=True, server_default="1") is_opensource = Column(Boolean, nullable=True, default=True, server_default="TRUE") is_dead = Column(Boolean, nullable=True, default=False, server_default="FALSE") - tags = relationship("Tag", secondary="project_tags", back_populates="projects") + tags = relationship( + "Tag", secondary="project_tags", back_populates="projects", lazy="selectin" + ) def __repr__(self): return f"" diff --git a/backend/app/routes/api/project/__tests__/test_service.py b/backend/app/routes/api/project/__tests__/test_service.py index f21551d..e4c8700 100644 --- a/backend/app/routes/api/project/__tests__/test_service.py +++ b/backend/app/routes/api/project/__tests__/test_service.py @@ -11,15 +11,19 @@ def mock_data_access(): data_access = AsyncMock() return data_access +@pytest.fixture +def mock_ai_agent(): + ai_agent = AsyncMock() + return ai_agent @pytest.fixture -def project_service(mock_data_access): - return ProjectService(data_access=mock_data_access) +def project_service(mock_data_access, mock_ai_agent): + return ProjectService(data_access=mock_data_access, ai_agent=mock_ai_agent) @pytest.fixture def user(): - return User(username="testuser", role="VIEWER") + return User(username="testuser", role="VIEWER", id=1) @pytest.fixture diff --git a/backend/app/routes/api/project/data_access.py b/backend/app/routes/api/project/data_access.py index f32799d..eb23667 100644 --- a/backend/app/routes/api/project/data_access.py +++ b/backend/app/routes/api/project/data_access.py @@ -70,6 +70,7 @@ async def create_project( project.ceo_id = ceo_id self.db_session.add(project) await self.db_session.flush() + await self.db_session.refresh(project, ["tags"]) return ProjectSchema.model_validate(project) async def get_project_applications( @@ -159,5 +160,20 @@ async def delete_application(self, project_id: int, user_id: int) -> bool: res = await self.db_session.execute(query) return res.rowcount > 0 + async def update_project( + self, project_id: int, brief_description: str, description: str | None = None + ) -> ProjectSchema: + query = ( + update(Project) + .where(Project.id == project_id) + .values(brief_description=brief_description, description=description) + .returning(Project) + ) + res = await self.db_session.execute(query) + project = res.scalars().first() + if not project: + raise ValueError(f"Project with ID {project_id} not found.") + return ProjectSchema.model_validate(project) + ProjectsDataAccessDep = Annotated[ProjectsDataAccess, Depends(ProjectsDataAccess)] diff --git a/backend/app/routes/api/project/router.py b/backend/app/routes/api/project/router.py index fb08152..73633e5 100644 --- a/backend/app/routes/api/project/router.py +++ b/backend/app/routes/api/project/router.py @@ -8,6 +8,9 @@ ApproveApplicationSchema, ProjectMemberSchema, ActionResponse, + UpdateProjectRequest, + EnhanceDescriptionRequest, + EnhanceDescriptionResponse, ) from routes.api.project.service import ProjectServiceDep @@ -97,3 +100,21 @@ async def delete_project( user: AuthUserDep, ) -> ActionResponse: return await service.delete_project(project_id, user) + + +@router.post("/{project_id}/update") +async def update_project( + project_id: int, + data: UpdateProjectRequest, + service: ProjectServiceDep, + user: AuthUserDep, +) -> ProjectSchema: + return await service.update_project(project_id, data, user) + +@router.post('/enhance-description') +async def enhance_project_description( + data: EnhanceDescriptionRequest, + service: ProjectServiceDep, + user: AuthUserDep, +) -> EnhanceDescriptionResponse: + return await service.enhance_project_description(data) \ No newline at end of file diff --git a/backend/app/routes/api/project/schemas.py b/backend/app/routes/api/project/schemas.py index b0b6ec0..6c4f440 100644 --- a/backend/app/routes/api/project/schemas.py +++ b/backend/app/routes/api/project/schemas.py @@ -57,3 +57,15 @@ class ProjectMemberSchema(BaseModel): class Config: from_attributes = True + + +class UpdateProjectRequest(BaseModel): + brief_description: str + description: Optional[str] = None + + +class EnhanceDescriptionRequest(BaseModel): + project_description: str + +class EnhanceDescriptionResponse(BaseModel): + enhanced_description: str \ No newline at end of file diff --git a/backend/app/routes/api/project/service.py b/backend/app/routes/api/project/service.py index c1c5808..a04ed7a 100644 --- a/backend/app/routes/api/project/service.py +++ b/backend/app/routes/api/project/service.py @@ -1,8 +1,6 @@ -from email.message import Message from typing import Annotated from fastapi import Depends from fastapi.exceptions import HTTPException -from pyexpat.errors import messages from models.users import UserRole from routes.api.project.data_access import ProjectsDataAccessDep @@ -13,13 +11,18 @@ ApplicationSchema, ApproveApplicationSchema, ActionResponse, + UpdateProjectRequest, + EnhanceDescriptionResponse, + EnhanceDescriptionRequest, ) from schemas.user import UserInDB +from services.ai_agent import AiAgentDep class ProjectService: - def __init__(self, data_access: ProjectsDataAccessDep): + def __init__(self, data_access: ProjectsDataAccessDep, ai_agent: AiAgentDep): self.data_access = data_access + self.ai_agent = ai_agent async def get_project_by_id(self, project_id: int) -> ProjectSchema: data = await self.data_access.get_project_by_id(project_id) @@ -145,7 +148,7 @@ async def delete_project(self, project_id: int, user: UserInDB) -> ActionRespons ) if project.ceo_id != user.id: raise HTTPException( - status_code=403, detail=f"Only the project CEO can delete." + status_code=403, detail="Only the project CEO can delete." ) res = await self.data_access.delete_project(project_id) msg = "Project was successfully deleted" if res else "Project not found" @@ -173,5 +176,32 @@ async def delete_application(self, project_id, user: UserInDB) -> ActionResponse ) return ActionResponse(message=msg, success=res) + async def update_project( + self, + project_id: int, + data: UpdateProjectRequest, + user: UserInDB, + ) -> ProjectSchema: + project = await self.data_access.get_project_by_id(project_id) + if project is None: + raise HTTPException( + status_code=404, detail=f"Project with ID {project_id} not found." + ) + if project.ceo_id != user.id: + raise HTTPException( + status_code=403, detail="Only the project CEO can update the project." + ) + return await self.data_access.update_project( + project_id, data.brief_description, data.description + ) + + async def enhance_project_description( + self, data: EnhanceDescriptionRequest, + ) -> EnhanceDescriptionResponse: + res = await self.ai_agent.enhance_project_description(data.project_description) + return EnhanceDescriptionResponse( + enhanced_description=res + ) + ProjectServiceDep = Annotated[ProjectService, Depends(ProjectService)] diff --git a/backend/app/routes/api/tag/data_access.py b/backend/app/routes/api/tag/data_access.py index cee4f14..cc96908 100644 --- a/backend/app/routes/api/tag/data_access.py +++ b/backend/app/routes/api/tag/data_access.py @@ -4,6 +4,7 @@ from sqlalchemy.dialects.postgresql import insert from typing import Annotated from fastapi import Depends +from sqlalchemy import delete, select class TagDataAccess: @@ -34,5 +35,17 @@ async def add_tags_to_project( return [TagSchema.model_validate(tag) for tag in tags] + async def remove_tags_from_project(self, project_id: int, tags: list[str]) -> int: + tags_subquery = select(Tag.id).where(Tag.name.in_(tags)).scalar_subquery() + + delete_stmt = delete(ProjectTag).where( + ProjectTag.project_id == project_id, + ProjectTag.tag_id.in_(tags_subquery), + ) + + res = await self.db_session.execute(delete_stmt) + + return res.rowcount + TagDataAccessDep = Annotated[TagDataAccess, Depends(TagDataAccess)] diff --git a/backend/app/routes/api/tag/router.py b/backend/app/routes/api/tag/router.py index 9435be4..44fc62d 100644 --- a/backend/app/routes/api/tag/router.py +++ b/backend/app/routes/api/tag/router.py @@ -3,6 +3,7 @@ from schemas.agent_response import GeneratedTagsResponse from dependencies.auth import AuthUserDep from routes.api.tag.schemas import TagSchema +from schemas.generic import GenericResponse router = APIRouter(prefix="/tags", tags=["tags"]) @@ -19,3 +20,10 @@ async def add_project_tags( project_id: int, service: TagServiceDep, user: AuthUserDep, tags: list[TagSchema] ) -> list[TagSchema]: return await service.add_project_tags(project_id, tags, user) + + +@router.delete("/remove/{project_id}") +async def remove_project_tags( + project_id: int, service: TagServiceDep, user: AuthUserDep, tags: list[TagSchema] +) -> GenericResponse: + return await service.remove_project_tags(project_id, tags, user) diff --git a/backend/app/routes/api/tag/service.py b/backend/app/routes/api/tag/service.py index a429e93..9b70faa 100644 --- a/backend/app/routes/api/tag/service.py +++ b/backend/app/routes/api/tag/service.py @@ -1,4 +1,5 @@ from routes.api.project.data_access import ProjectsDataAccessDep +from schemas.generic import GenericResponse from services.ai_agent import AiAgentDep from schemas.agent_response import GeneratedTagsResponse from fastapi import HTTPException, Depends @@ -40,5 +41,27 @@ async def add_project_tags( return await self.tag_data_access.add_tags_to_project(project_id, tags) + async def remove_project_tags( + self, project_id: int, tags: list[TagSchema], user: UserInDB + ) -> GenericResponse: + project = await self.data_access.get_project_by_id(project_id) + if project is None: + raise HTTPException(status_code=404, detail="Project not found") + if user.id != project.ceo_id: + raise HTTPException( + status_code=403, detail="Only CEO can remove tags from the project" + ) + + res = await self.tag_data_access.remove_tags_from_project( + project_id, [tag.name for tag in tags] + ) + + return GenericResponse( + success=res > 0, + message=f"Removed {res} tags from the project" + if res > 0 + else "No tags removed", + ) + TagServiceDep = Annotated[TagService, Depends(TagService)] diff --git a/backend/app/routes/api/user/data_access.py b/backend/app/routes/api/user/data_access.py index 80d8b7c..069e7e6 100644 --- a/backend/app/routes/api/user/data_access.py +++ b/backend/app/routes/api/user/data_access.py @@ -16,6 +16,10 @@ async def get_user_by_username(self, username: str) -> User | None: ) return res.scalars().first() + async def get_user_by_id(self, user_id: int) -> User | None: + res = await self.db_session.execute(select(User).where(User.id == user_id)) + return res.scalars().first() + async def set_user_role(self, username: str, role: UserRole) -> None: await self.db_session.execute( update(User).where(User.username == username).values(role=role) diff --git a/backend/app/routes/api/user/router.py b/backend/app/routes/api/user/router.py index 1fb84fb..7595e0b 100644 --- a/backend/app/routes/api/user/router.py +++ b/backend/app/routes/api/user/router.py @@ -22,3 +22,12 @@ async def get_me( current_user=Depends(get_current_user), ) -> User: return current_user + + +@router.get("/get-by-id/{user_id}") +async def get_user_by_id( + user_id: int, + service: UserServiceDep, + current_user=Depends(get_current_user), +) -> User: + return await service.get_user_by_id(user_id, current_user) diff --git a/backend/app/routes/api/user/service.py b/backend/app/routes/api/user/service.py index e4a6ad8..ce40d7d 100644 --- a/backend/app/routes/api/user/service.py +++ b/backend/app/routes/api/user/service.py @@ -1,7 +1,6 @@ from typing import Annotated from fastapi import Depends, HTTPException -from models.users import UserRole from routes.api.user.data_access import UserDataAccessDep, UserDataAccess from routes.api.user.schemas import SetUserRoleSchema from schemas.user import User @@ -28,5 +27,14 @@ async def set_user_role(self, user_role: SetUserRoleSchema, current_user: User): "message": f"User '{user.username}' role set to {user_role.role.value}." } + async def get_user_by_id(self, user_id: int, current_user: User) -> User: + user = await self.user_repository.get_user_by_id(user_id) + if user is None: + raise HTTPException( + status_code=404, + detail=f"User with ID {user_id} not found.", + ) + return user + UserServiceDep = Annotated[UserService, Depends(UserService)] diff --git a/backend/app/schemas/generic.py b/backend/app/schemas/generic.py new file mode 100644 index 0000000..cbc6c9a --- /dev/null +++ b/backend/app/schemas/generic.py @@ -0,0 +1,8 @@ +from typing import Optional + +from pydantic import Field, BaseModel + + +class GenericResponse(BaseModel): + success: bool = Field(default=True) + message: Optional[str] = Field(default=None) diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py index 991a94c..43946cc 100644 --- a/backend/app/schemas/user.py +++ b/backend/app/schemas/user.py @@ -5,6 +5,7 @@ class User(BaseModel): + id: int username: str role: UserRole diff --git a/backend/app/services/ai_agent.py b/backend/app/services/ai_agent.py index 76cc6b6..24a7ef0 100644 --- a/backend/app/services/ai_agent.py +++ b/backend/app/services/ai_agent.py @@ -23,12 +23,25 @@ async def get_tags_by_description( tag_generator_agent = Agent( self.model, output_type=GeneratedTagsResponse, - system_prompt=f"You need to come up with a name for a tag based on the project description. You should provide 15 options. Write tags in English only", + system_prompt=f"You need to come up with a name for a tag based on the project description. Write tags in English only", ) result = await tag_generator_agent.run(user_prompt=project_description) return result.output + async def enhance_project_description( + self, project_description: str + ) -> str: + description_enhancer_agent = Agent( + self.model, + output_type=str, + system_prompt=f"You need to enhance the given project description. Write in English only. Use markdown syntax for formatting.", + ) + + result = await description_enhancer_agent.run(user_prompt=project_description) + + return result.output + AiAgentDep = Annotated[AiAgent, Depends(AiAgent)] diff --git a/frontend/jsrepo.json b/frontend/jsrepo.json new file mode 100644 index 0000000..5f68b11 --- /dev/null +++ b/frontend/jsrepo.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://unpkg.com/jsrepo@2.4.3/schemas/project-config.json", + "repos": ["@ieedan/shadcn-svelte-extras"], + "includeTests": false, + "includeDocs": false, + "watermark": true, + "formatter": "prettier", + "configFiles": {}, + "paths": { + "*": "$lib/blocks", + "ui": "$lib/components/ui", + "hooks": "$lib/hooks", + "actions": "$lib/actions", + "utils": "$lib/utils" + } +} diff --git a/frontend/package.json b/frontend/package.json index eb38cd1..482e876 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -29,6 +29,7 @@ "@tailwindcss/vite": "^4.0.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/svelte": "^5.2.8", + "@types/node": "^24.0.15", "@vitest/coverage-v8": "^3.2.4", "@vitest/ui": "^3.2.4", "bits-ui": "^2.8.8", @@ -65,6 +66,7 @@ "@tanstack/svelte-query": "^5.81.2", "cobe": "^0.6.4", "jwt-decode": "^4.0.0", - "openapi-fetch": "^0.14.0" + "openapi-fetch": "^0.14.0", + "svelte-markdown": "^0.4.1" } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index e459fdc..6b98fd2 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: dependencies: '@sveltejs/adapter-static': specifier: ^3.0.8 - version: 3.0.8(@sveltejs/kit@2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1))) + version: 3.0.8(@sveltejs/kit@2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1))) '@tanstack/svelte-query': specifier: ^5.81.2 version: 5.81.2(svelte@5.33.19) @@ -23,6 +23,9 @@ importers: openapi-fetch: specifier: ^0.14.0 version: 0.14.0 + svelte-markdown: + specifier: ^0.4.1 + version: 0.4.1(svelte@5.33.19) devDependencies: '@eslint/compat': specifier: ^1.2.5 @@ -38,25 +41,28 @@ importers: version: 0.515.0(svelte@5.33.19) '@sveltejs/adapter-auto': specifier: ^6.0.0 - version: 6.0.1(@sveltejs/kit@2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1))) + version: 6.0.1(@sveltejs/kit@2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1))) '@sveltejs/kit': specifier: ^2.16.0 - version: 2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)) + version: 2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)) '@sveltejs/vite-plugin-svelte': specifier: ^5.0.0 - version: 5.1.0(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)) + version: 5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)) '@tailwindcss/vite': specifier: ^4.0.0 - version: 4.1.8(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)) + version: 4.1.8(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)) '@testing-library/jest-dom': specifier: ^6.6.3 version: 6.6.3 '@testing-library/svelte': specifier: ^5.2.8 - version: 5.2.8(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1))(vitest@3.2.4(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)) + version: 5.2.8(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.15)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)) + '@types/node': + specifier: ^24.0.15 + version: 24.0.15 '@vitest/coverage-v8': specifier: ^3.2.4 - version: 3.2.4(vitest@3.2.4(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.15)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)) '@vitest/ui': specifier: ^3.2.4 version: 3.2.4(vitest@3.2.4) @@ -125,10 +131,10 @@ importers: version: 8.34.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) vite: specifier: ^6.2.6 - version: 6.3.5(jiti@2.4.2)(lightningcss@1.30.1) + version: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1) vitest: specifier: ^3.2.4 - version: 3.2.4(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.15)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1) packages: @@ -762,6 +768,9 @@ packages: '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -774,6 +783,15 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/marked@5.0.2': + resolution: {integrity: sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@24.0.15': + resolution: {integrity: sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==} + '@typescript-eslint/eslint-plugin@8.34.0': resolution: {integrity: sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1510,6 +1528,11 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + marked@5.1.2: + resolution: {integrity: sha512-ahRPGXJpjMjwSOlBoTMZAK7ATXkli5qCPxZ21TG44rx1KEo44bii4ekgTDQPNRQ4Kh7JMb9Ub1PVk1NxRSsorg==} + engines: {node: '>= 16'} + hasBin: true + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1913,6 +1936,11 @@ packages: svelte: optional: true + svelte-markdown@0.4.1: + resolution: {integrity: sha512-pOlLY6EruKJaWI9my/2bKX8PdTeP5CM0s4VMmwmC2prlOkjAf+AOmTM4wW/l19Y6WZ87YmP8+ZCJCCwBChWjYw==} + peerDependencies: + svelte: ^4.0.0 + svelte-sonner@1.0.5: resolution: {integrity: sha512-9dpGPFqKb/QWudYqGnEz93vuY+NgCEvyNvxoCLMVGw6sDN/3oVeKV1xiEirW2E1N3vJEyj5imSBNOGltQHA7mg==} peerDependencies: @@ -2044,6 +2072,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + uri-js-replace@1.0.1: resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} @@ -2568,18 +2599,18 @@ snapshots: dependencies: acorn: 8.15.0 - '@sveltejs/adapter-auto@6.0.1(@sveltejs/kit@2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)))': + '@sveltejs/adapter-auto@6.0.1(@sveltejs/kit@2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)))': dependencies: - '@sveltejs/kit': 2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)) + '@sveltejs/kit': 2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)) - '@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)))': + '@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)))': dependencies: - '@sveltejs/kit': 2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)) + '@sveltejs/kit': 2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)) - '@sveltejs/kit@2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1))': + '@sveltejs/kit@2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1))': dependencies: '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 5.1.0(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)) + '@sveltejs/vite-plugin-svelte': 5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)) '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 0.6.0 @@ -2592,28 +2623,28 @@ snapshots: set-cookie-parser: 2.7.1 sirv: 3.0.1 svelte: 5.33.19 - vite: 6.3.5(jiti@2.4.2)(lightningcss@1.30.1) - vitefu: 1.0.6(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)) + vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1) + vitefu: 1.0.6(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)) - '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1))': + '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 5.1.0(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)) + '@sveltejs/vite-plugin-svelte': 5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)) debug: 4.4.1(supports-color@10.0.0) svelte: 5.33.19 - vite: 6.3.5(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1))': + '@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)) + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)) debug: 4.4.1(supports-color@10.0.0) deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.17 svelte: 5.33.19 - vite: 6.3.5(jiti@2.4.2)(lightningcss@1.30.1) - vitefu: 1.0.6(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)) + vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1) + vitefu: 1.0.6(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)) transitivePeerDependencies: - supports-color @@ -2685,12 +2716,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.8 '@tailwindcss/oxide-win32-x64-msvc': 4.1.8 - '@tailwindcss/vite@4.1.8(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1))': + '@tailwindcss/vite@4.1.8(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1))': dependencies: '@tailwindcss/node': 4.1.8 '@tailwindcss/oxide': 4.1.8 tailwindcss: 4.1.8 - vite: 6.3.5(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1) '@tanstack/query-core@5.81.2': {} @@ -2720,13 +2751,13 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 - '@testing-library/svelte@5.2.8(svelte@5.33.19)(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1))(vitest@3.2.4(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1))': + '@testing-library/svelte@5.2.8(svelte@5.33.19)(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.15)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1))': dependencies: '@testing-library/dom': 10.4.0 svelte: 5.33.19 optionalDependencies: - vite: 6.3.5(jiti@2.4.2)(lightningcss@1.30.1) - vitest: 3.2.4(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.15)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1) '@types/aria-query@5.0.4': {} @@ -2736,6 +2767,11 @@ snapshots: '@types/cookie@0.6.0': {} + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + optional: true + '@types/deep-eql@4.0.2': {} '@types/estree@1.0.7': {} @@ -2744,6 +2780,15 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/marked@5.0.2': {} + + '@types/ms@2.1.0': + optional: true + + '@types/node@24.0.15': + dependencies: + undici-types: 7.8.0 + '@typescript-eslint/eslint-plugin@8.34.0(@typescript-eslint/parser@8.34.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -2836,7 +2881,7 @@ snapshots: '@typescript-eslint/types': 8.34.0 eslint-visitor-keys: 4.2.1 - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1))': + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.15)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -2851,7 +2896,7 @@ snapshots: std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.15)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1) transitivePeerDependencies: - supports-color @@ -2863,13 +2908,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1))': + '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.3.5(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1) '@vitest/pretty-format@3.2.4': dependencies: @@ -2900,7 +2945,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.14 tinyrainbow: 2.0.0 - vitest: 3.2.4(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.15)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1) '@vitest/utils@3.2.4': dependencies: @@ -3527,6 +3572,8 @@ snapshots: dependencies: semver: 7.7.2 + marked@5.1.2: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -3858,6 +3905,12 @@ snapshots: optionalDependencies: svelte: 5.33.19 + svelte-markdown@0.4.1(svelte@5.33.19): + dependencies: + '@types/marked': 5.0.2 + marked: 5.1.2 + svelte: 5.33.19 + svelte-sonner@1.0.5(svelte@5.33.19): dependencies: runed: 0.28.0(svelte@5.33.19) @@ -3987,6 +4040,8 @@ snapshots: typescript@5.8.3: {} + undici-types@7.8.0: {} + uri-js-replace@1.0.1: {} uri-js@4.4.1: @@ -3995,13 +4050,13 @@ snapshots: util-deprecate@1.0.2: {} - vite-node@3.2.4(jiti@2.4.2)(lightningcss@1.30.1): + vite-node@3.2.4(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1): dependencies: cac: 6.7.14 debug: 4.4.1(supports-color@10.0.0) es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.3.5(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1) transitivePeerDependencies: - '@types/node' - jiti @@ -4016,7 +4071,7 @@ snapshots: - tsx - yaml - vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1): + vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1): dependencies: esbuild: 0.25.5 fdir: 6.4.6(picomatch@4.0.2) @@ -4025,19 +4080,20 @@ snapshots: rollup: 4.43.0 tinyglobby: 0.2.14 optionalDependencies: + '@types/node': 24.0.15 fsevents: 2.3.3 jiti: 2.4.2 lightningcss: 1.30.1 - vitefu@1.0.6(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)): + vitefu@1.0.6(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)): optionalDependencies: - vite: 6.3.5(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1) - vitest@3.2.4(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.15)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)) + '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -4055,10 +4111,12 @@ snapshots: tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.3.5(jiti@2.4.2)(lightningcss@1.30.1) - vite-node: 3.2.4(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1) + vite-node: 3.2.4(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1) why-is-node-running: 2.3.0 optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 24.0.15 '@vitest/ui': 3.2.4(vitest@3.2.4) jsdom: 26.1.0 transitivePeerDependencies: diff --git a/frontend/src/app.css b/frontend/src/app.css index 43cee97..95c4775 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -4,6 +4,8 @@ @custom-variant dark (&:is(.dark *)); +@config "../tailwind.config.ts"; + :root { --radius: 0.625rem; --background: oklch(1 0 0); @@ -37,6 +39,11 @@ --sidebar-accent-foreground: oklch(0.21 0.006 285.885); --sidebar-border: oklch(0.92 0.004 286.32); --sidebar-ring: oklch(0.705 0.015 286.067); + --color-1: 0 100% 63%; + --color-2: 270 100% 63%; + --color-3: 210 100% 63%; + --color-4: 195 100% 63%; + --color-5: 90 100% 63%; } .dark { @@ -71,6 +78,11 @@ --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.552 0.016 285.938); + --color-1: 0 100% 63%; + --color-2: 270 100% 63%; + --color-3: 210 100% 63%; + --color-4: 195 100% 63%; + --color-5: 90 100% 63%; } @theme inline { @@ -115,6 +127,7 @@ * { @apply border-border outline-ring/50; } + body { @apply bg-background text-foreground; } @@ -127,16 +140,29 @@ -webkit-box-orient: vertical; -webkit-line-clamp: 1; } + .line-clamp-2 { overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; } + .line-clamp-3 { overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; } + + /* Hide scrollbar for Chrome, Safari and Opera */ + .no-scrollbar::-webkit-scrollbar { + display: none; + } + + /* Hide scrollbar for IE, Edge and Firefox */ + .no-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } } diff --git a/frontend/src/lib/api/v1.d.ts b/frontend/src/lib/api/v1.d.ts index cad8302..bcf045e 100644 --- a/frontend/src/lib/api/v1.d.ts +++ b/frontend/src/lib/api/v1.d.ts @@ -39,6 +39,23 @@ export interface paths { patch?: never; trace?: never; }; + '/api/projects/all-applications': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get User Applications */ + get: operations['get_user_applications_api_projects_all_applications_get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; '/api/projects/{project_id}': { parameters: { query?: never; @@ -50,7 +67,8 @@ export interface paths { get: operations['get_project_by_id_api_projects__project_id__get']; put?: never; post?: never; - delete?: never; + /** Delete Project */ + delete: operations['delete_project_api_projects__project_id__delete']; options?: never; head?: never; patch?: never; @@ -124,6 +142,57 @@ export interface paths { patch: operations['approve_application_api_projects__project_id__applications_approve_patch']; trace?: never; }; + '/api/projects/{project_id}/applications/cancel': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Delete Application */ + delete: operations['delete_application_api_projects__project_id__applications_cancel_delete']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/projects/{project_id}/update': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Update Project */ + post: operations['update_project_api_projects__project_id__update_post']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/projects/enhance-description': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Enhance Project Description */ + post: operations['enhance_project_description_api_projects_enhance_description_post']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; '/api/auth/token': { parameters: { query?: never; @@ -192,10 +261,85 @@ export interface paths { patch?: never; trace?: never; }; + '/api/user/get-by-id/{user_id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get User By Id */ + get: operations['get_user_by_id_api_user_get_by_id__user_id__get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/tags/generate/{project_id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Tags By Description */ + get: operations['get_tags_by_description_api_tags_generate__project_id__get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/tags/add/{project_id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Add Project Tags */ + post: operations['add_project_tags_api_tags_add__project_id__post']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/tags/remove/{project_id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Remove Project Tags */ + delete: operations['remove_project_tags_api_tags_remove__project_id__delete']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; } export type webhooks = Record; export interface components { schemas: { + /** ActionResponse */ + ActionResponse: { + /** Message */ + message: string; + /** Success */ + success: boolean; + }; /** ApplicationSchema */ ApplicationSchema: { /** Project Id */ @@ -209,6 +353,8 @@ export interface components { * Format: date-time */ created_at: string; + /** Feedback */ + feedback: string | null; }; /** ApproveApplicationSchema */ ApproveApplicationSchema: { @@ -216,6 +362,8 @@ export interface components { is_approved: boolean; /** User Id */ user_id: number; + /** Feedback */ + feedback: string | null; }; /** Body_login_for_access_token_api_auth_token_post */ Body_login_for_access_token_api_auth_token_post: { @@ -235,6 +383,31 @@ export interface components { /** Client Secret */ client_secret?: string | null; }; + /** EnhanceDescriptionRequest */ + EnhanceDescriptionRequest: { + /** Project Description */ + project_description: string; + }; + /** EnhanceDescriptionResponse */ + EnhanceDescriptionResponse: { + /** Enhanced Description */ + enhanced_description: string; + }; + /** GeneratedTagsResponse */ + GeneratedTagsResponse: { + /** Tags */ + tags: string[]; + }; + /** GenericResponse */ + GenericResponse: { + /** + * Success + * @default true + */ + success: boolean; + /** Message */ + message?: string | null; + }; /** HTTPValidationError */ HTTPValidationError: { /** Detail */ @@ -244,6 +417,8 @@ export interface components { NewProjectSchema: { /** Title */ title: string; + /** Brief Description */ + brief_description: string; /** Description */ description: string | null; /** Is Public */ @@ -264,6 +439,8 @@ export interface components { ProjectSchema: { /** Title */ title: string; + /** Brief Description */ + brief_description: string; /** Description */ description: string | null; /** Is Public */ @@ -281,11 +458,23 @@ export interface components { created_at: string; /** Ceo Id */ ceo_id: number | null; + /** Tags */ + tags?: components['schemas']['Tag'][] | null; }; /** SetUserRoleSchema */ SetUserRoleSchema: { role: components['schemas']['UserRole']; }; + /** Tag */ + Tag: { + /** Name */ + name: string; + }; + /** TagSchema */ + TagSchema: { + /** Name */ + name: string; + }; /** Token */ Token: { /** Access Token */ @@ -293,8 +482,17 @@ export interface components { /** Token Type */ token_type: string; }; + /** UpdateProjectRequest */ + UpdateProjectRequest: { + /** Brief Description */ + brief_description: string; + /** Description */ + description?: string | null; + }; /** User */ User: { + /** Id */ + id: number; /** Username */ username: string; role: components['schemas']['UserRole']; @@ -402,6 +600,26 @@ export interface operations { }; }; }; + get_user_applications_api_projects_all_applications_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ApplicationSchema'][]; + }; + }; + }; + }; get_project_by_id_api_projects__project_id__get: { parameters: { query?: never; @@ -433,6 +651,37 @@ export interface operations { }; }; }; + delete_project_api_projects__project_id__delete: { + parameters: { + query?: never; + header?: never; + path: { + project_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ActionResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; apply_to_project_api_projects__project_id__apply_post: { parameters: { query?: never; @@ -561,6 +810,105 @@ export interface operations { }; }; }; + delete_application_api_projects__project_id__applications_cancel_delete: { + parameters: { + query?: never; + header?: never; + path: { + project_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ActionResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + update_project_api_projects__project_id__update_post: { + parameters: { + query?: never; + header?: never; + path: { + project_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['UpdateProjectRequest']; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ProjectSchema']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + enhance_project_description_api_projects_enhance_description_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['EnhanceDescriptionRequest']; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['EnhanceDescriptionResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; login_for_access_token_api_auth_token_post: { parameters: { query?: never; @@ -682,4 +1030,136 @@ export interface operations { }; }; }; + get_user_by_id_api_user_get_by_id__user_id__get: { + parameters: { + query?: never; + header?: never; + path: { + user_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['User']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + get_tags_by_description_api_tags_generate__project_id__get: { + parameters: { + query?: never; + header?: never; + path: { + project_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['GeneratedTagsResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + add_project_tags_api_tags_add__project_id__post: { + parameters: { + query?: never; + header?: never; + path: { + project_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['TagSchema'][]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['TagSchema'][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + remove_project_tags_api_tags_remove__project_id__delete: { + parameters: { + query?: never; + header?: never; + path: { + project_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['TagSchema'][]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['GenericResponse']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; } diff --git a/frontend/src/lib/components/RainbowButton.svelte b/frontend/src/lib/components/RainbowButton.svelte new file mode 100644 index 0000000..30c04e5 --- /dev/null +++ b/frontend/src/lib/components/RainbowButton.svelte @@ -0,0 +1,51 @@ + + +{#if href} + + Default + +{:else} + +{/if} diff --git a/frontend/src/lib/components/landing/Globe.svelte b/frontend/src/lib/components/landing/Globe.svelte index 9555cec..7b59f79 100644 --- a/frontend/src/lib/components/landing/Globe.svelte +++ b/frontend/src/lib/components/landing/Globe.svelte @@ -61,7 +61,18 @@ { location: [19.4326, -99.1332], size: 0.04 }, { location: [40.7128, -74.006], size: 0.1 }, { location: [34.6937, 135.5022], size: 0.05 }, - { location: [41.0082, 28.9784], size: 0.06 } + { location: [41.0082, 28.9784], size: 0.06 }, + { location: [51.5074, -0.1278], size: 0.1 }, + { location: [55.7558, 37.6173], size: 0.05 }, + { location: [48.8566, 2.3522], size: 0.08 }, + { location: [52.52, 13.405], size: 0.07 }, + { location: [35.6762, 139.6503], size: 0.1 }, + { location: [37.7749, -122.4194], size: 0.1 }, + { location: [55.6761, 12.5683], size: 0.05 }, + { location: [59.9139, 10.7522], size: 0.05 }, + { location: [60.1695, 24.9354], size: 0.05 }, + { location: [64.1355, -21.8954], size: 0.05 }, + { location: [55.7513, 48.732], size: 0.5 } ], onRender: onRender }); diff --git a/frontend/src/lib/components/landing/Marquee.svelte b/frontend/src/lib/components/landing/Marquee.svelte new file mode 100644 index 0000000..38901c3 --- /dev/null +++ b/frontend/src/lib/components/landing/Marquee.svelte @@ -0,0 +1,36 @@ + + +
+ + {#each { length: repeat } as _, i (i)} +
+ Default +
+ {/each} +
diff --git a/frontend/src/lib/components/landing/Orbit.svelte b/frontend/src/lib/components/landing/Orbit.svelte new file mode 100644 index 0000000..9789989 --- /dev/null +++ b/frontend/src/lib/components/landing/Orbit.svelte @@ -0,0 +1,53 @@ + + +
+ + ProjectOR + + + + + + + + + + + + + + + + + +
diff --git a/frontend/src/lib/components/landing/OrbitingCircles.svelte b/frontend/src/lib/components/landing/OrbitingCircles.svelte new file mode 100644 index 0000000..088bc84 --- /dev/null +++ b/frontend/src/lib/components/landing/OrbitingCircles.svelte @@ -0,0 +1,40 @@ + + +{#if path} + + + +
+ +
+{/if} diff --git a/frontend/src/lib/components/landing/Reviewcard.svelte b/frontend/src/lib/components/landing/Reviewcard.svelte new file mode 100644 index 0000000..3b4e29f --- /dev/null +++ b/frontend/src/lib/components/landing/Reviewcard.svelte @@ -0,0 +1,30 @@ + + +
+
+ +
+ +
+ {name} +
+

{username}

+
+
+
{body}
+
diff --git a/frontend/src/lib/components/landing/Reviews.svelte b/frontend/src/lib/components/landing/Reviews.svelte new file mode 100644 index 0000000..2699ad7 --- /dev/null +++ b/frontend/src/lib/components/landing/Reviews.svelte @@ -0,0 +1,67 @@ + + +
+ + {#each firstRow as item (item.username)} + + {/each} + + + {#each secondRow as item (item.username)} + + {/each} + +
+
+
diff --git a/frontend/src/lib/components/md/Blockquote.svelte b/frontend/src/lib/components/md/Blockquote.svelte new file mode 100644 index 0000000..f72617f --- /dev/null +++ b/frontend/src/lib/components/md/Blockquote.svelte @@ -0,0 +1,3 @@ +
+ +
diff --git a/frontend/src/lib/components/md/CodeSpan.svelte b/frontend/src/lib/components/md/CodeSpan.svelte new file mode 100644 index 0000000..76b8ed5 --- /dev/null +++ b/frontend/src/lib/components/md/CodeSpan.svelte @@ -0,0 +1,7 @@ + + + + {raw.replace(/`/g, '')} + diff --git a/frontend/src/lib/components/md/Heading.svelte b/frontend/src/lib/components/md/Heading.svelte new file mode 100644 index 0000000..0b53cce --- /dev/null +++ b/frontend/src/lib/components/md/Heading.svelte @@ -0,0 +1,37 @@ + + +{#if depth === 1} +

+ +

+{:else if depth === 2} +

+ +

+{:else if depth === 3} +

+ +

+{:else if depth === 4} +

+ +

+{:else if depth === 5} +
+ +
+{:else if depth === 6} +
+ +
+{:else} + {raw} +{/if} diff --git a/frontend/src/lib/components/md/Link.svelte b/frontend/src/lib/components/md/Link.svelte new file mode 100644 index 0000000..bc6b39e --- /dev/null +++ b/frontend/src/lib/components/md/Link.svelte @@ -0,0 +1,8 @@ + + + + + diff --git a/frontend/src/lib/components/md/List.svelte b/frontend/src/lib/components/md/List.svelte new file mode 100644 index 0000000..9a0eef0 --- /dev/null +++ b/frontend/src/lib/components/md/List.svelte @@ -0,0 +1,10 @@ + + +{#if ordered} +
+{:else} +
+{/if} diff --git a/frontend/src/lib/components/md/ListItem.svelte b/frontend/src/lib/components/md/ListItem.svelte new file mode 100644 index 0000000..bc52876 --- /dev/null +++ b/frontend/src/lib/components/md/ListItem.svelte @@ -0,0 +1 @@ +
  • diff --git a/frontend/src/lib/components/md/Markdown.svelte b/frontend/src/lib/components/md/Markdown.svelte new file mode 100644 index 0000000..0526d77 --- /dev/null +++ b/frontend/src/lib/components/md/Markdown.svelte @@ -0,0 +1,31 @@ + + + diff --git a/frontend/src/lib/components/md/Paragraph.svelte b/frontend/src/lib/components/md/Paragraph.svelte new file mode 100644 index 0000000..59f4aad --- /dev/null +++ b/frontend/src/lib/components/md/Paragraph.svelte @@ -0,0 +1 @@ +

    diff --git a/frontend/src/lib/components/md/Table.svelte b/frontend/src/lib/components/md/Table.svelte new file mode 100644 index 0000000..8dc3192 --- /dev/null +++ b/frontend/src/lib/components/md/Table.svelte @@ -0,0 +1,3 @@ +
    +
    +
    diff --git a/frontend/src/lib/components/md/TableBody.svelte b/frontend/src/lib/components/md/TableBody.svelte new file mode 100644 index 0000000..3ecda87 --- /dev/null +++ b/frontend/src/lib/components/md/TableBody.svelte @@ -0,0 +1 @@ + diff --git a/frontend/src/lib/components/md/TableCell.svelte b/frontend/src/lib/components/md/TableCell.svelte new file mode 100644 index 0000000..322b9bb --- /dev/null +++ b/frontend/src/lib/components/md/TableCell.svelte @@ -0,0 +1,18 @@ + + +{#if header} + +{:else} + +{/if} diff --git a/frontend/src/lib/components/md/TableHead.svelte b/frontend/src/lib/components/md/TableHead.svelte new file mode 100644 index 0000000..659bbbf --- /dev/null +++ b/frontend/src/lib/components/md/TableHead.svelte @@ -0,0 +1 @@ + diff --git a/frontend/src/lib/components/md/TableRow.svelte b/frontend/src/lib/components/md/TableRow.svelte new file mode 100644 index 0000000..187b478 --- /dev/null +++ b/frontend/src/lib/components/md/TableRow.svelte @@ -0,0 +1 @@ + diff --git a/frontend/src/lib/components/ui/command/command-dialog.svelte b/frontend/src/lib/components/ui/command/command-dialog.svelte new file mode 100644 index 0000000..c4d3587 --- /dev/null +++ b/frontend/src/lib/components/ui/command/command-dialog.svelte @@ -0,0 +1,40 @@ + + + + + {title} + {description} + + + + + diff --git a/frontend/src/lib/components/ui/command/command-empty.svelte b/frontend/src/lib/components/ui/command/command-empty.svelte new file mode 100644 index 0000000..c2ce165 --- /dev/null +++ b/frontend/src/lib/components/ui/command/command-empty.svelte @@ -0,0 +1,17 @@ + + + diff --git a/frontend/src/lib/components/ui/command/command-group.svelte b/frontend/src/lib/components/ui/command/command-group.svelte new file mode 100644 index 0000000..4097222 --- /dev/null +++ b/frontend/src/lib/components/ui/command/command-group.svelte @@ -0,0 +1,30 @@ + + + + {#if heading} + + {heading} + + {/if} + + diff --git a/frontend/src/lib/components/ui/command/command-input.svelte b/frontend/src/lib/components/ui/command/command-input.svelte new file mode 100644 index 0000000..3acb041 --- /dev/null +++ b/frontend/src/lib/components/ui/command/command-input.svelte @@ -0,0 +1,26 @@ + + +
    + + +
    diff --git a/frontend/src/lib/components/ui/command/command-item.svelte b/frontend/src/lib/components/ui/command/command-item.svelte new file mode 100644 index 0000000..063adb7 --- /dev/null +++ b/frontend/src/lib/components/ui/command/command-item.svelte @@ -0,0 +1,20 @@ + + + diff --git a/frontend/src/lib/components/ui/command/command-link-item.svelte b/frontend/src/lib/components/ui/command/command-link-item.svelte new file mode 100644 index 0000000..4edde50 --- /dev/null +++ b/frontend/src/lib/components/ui/command/command-link-item.svelte @@ -0,0 +1,20 @@ + + + diff --git a/frontend/src/lib/components/ui/command/command-list.svelte b/frontend/src/lib/components/ui/command/command-list.svelte new file mode 100644 index 0000000..8ff87f0 --- /dev/null +++ b/frontend/src/lib/components/ui/command/command-list.svelte @@ -0,0 +1,17 @@ + + + diff --git a/frontend/src/lib/components/ui/command/command-separator.svelte b/frontend/src/lib/components/ui/command/command-separator.svelte new file mode 100644 index 0000000..d75e794 --- /dev/null +++ b/frontend/src/lib/components/ui/command/command-separator.svelte @@ -0,0 +1,17 @@ + + + diff --git a/frontend/src/lib/components/ui/command/command-shortcut.svelte b/frontend/src/lib/components/ui/command/command-shortcut.svelte new file mode 100644 index 0000000..0c47933 --- /dev/null +++ b/frontend/src/lib/components/ui/command/command-shortcut.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/frontend/src/lib/components/ui/command/command.svelte b/frontend/src/lib/components/ui/command/command.svelte new file mode 100644 index 0000000..adff6e0 --- /dev/null +++ b/frontend/src/lib/components/ui/command/command.svelte @@ -0,0 +1,22 @@ + + + diff --git a/frontend/src/lib/components/ui/command/index.ts b/frontend/src/lib/components/ui/command/index.ts new file mode 100644 index 0000000..dee2364 --- /dev/null +++ b/frontend/src/lib/components/ui/command/index.ts @@ -0,0 +1,40 @@ +import { Command as CommandPrimitive } from 'bits-ui'; + +import Root from './command.svelte'; +import Dialog from './command-dialog.svelte'; +import Empty from './command-empty.svelte'; +import Group from './command-group.svelte'; +import Item from './command-item.svelte'; +import Input from './command-input.svelte'; +import List from './command-list.svelte'; +import Separator from './command-separator.svelte'; +import Shortcut from './command-shortcut.svelte'; +import LinkItem from './command-link-item.svelte'; + +const Loading = CommandPrimitive.Loading; + +export { + Root, + Dialog, + Empty, + Group, + Item, + LinkItem, + Input, + List, + Separator, + Shortcut, + Loading, + // + Root as Command, + Dialog as CommandDialog, + Empty as CommandEmpty, + Group as CommandGroup, + Item as CommandItem, + LinkItem as CommandLinkItem, + Input as CommandInput, + List as CommandList, + Separator as CommandSeparator, + Shortcut as CommandShortcut, + Loading as CommandLoading +}; diff --git a/frontend/src/lib/components/ui/popover/index.ts b/frontend/src/lib/components/ui/popover/index.ts new file mode 100644 index 0000000..0dcc3cc --- /dev/null +++ b/frontend/src/lib/components/ui/popover/index.ts @@ -0,0 +1,17 @@ +import { Popover as PopoverPrimitive } from 'bits-ui'; +import Content from './popover-content.svelte'; +import Trigger from './popover-trigger.svelte'; +const Root = PopoverPrimitive.Root; +const Close = PopoverPrimitive.Close; + +export { + Root, + Content, + Trigger, + Close, + // + Root as Popover, + Content as PopoverContent, + Trigger as PopoverTrigger, + Close as PopoverClose +}; diff --git a/frontend/src/lib/components/ui/popover/popover-content.svelte b/frontend/src/lib/components/ui/popover/popover-content.svelte new file mode 100644 index 0000000..620f392 --- /dev/null +++ b/frontend/src/lib/components/ui/popover/popover-content.svelte @@ -0,0 +1,29 @@ + + + + + diff --git a/frontend/src/lib/components/ui/popover/popover-trigger.svelte b/frontend/src/lib/components/ui/popover/popover-trigger.svelte new file mode 100644 index 0000000..f21b1b3 --- /dev/null +++ b/frontend/src/lib/components/ui/popover/popover-trigger.svelte @@ -0,0 +1,17 @@ + + + diff --git a/frontend/src/lib/components/ui/select/index.ts b/frontend/src/lib/components/ui/select/index.ts new file mode 100644 index 0000000..bfa73d9 --- /dev/null +++ b/frontend/src/lib/components/ui/select/index.ts @@ -0,0 +1,37 @@ +import { Select as SelectPrimitive } from 'bits-ui'; + +import Group from './select-group.svelte'; +import Label from './select-label.svelte'; +import Item from './select-item.svelte'; +import Content from './select-content.svelte'; +import Trigger from './select-trigger.svelte'; +import Separator from './select-separator.svelte'; +import ScrollDownButton from './select-scroll-down-button.svelte'; +import ScrollUpButton from './select-scroll-up-button.svelte'; +import GroupHeading from './select-group-heading.svelte'; + +const Root = SelectPrimitive.Root; + +export { + Root, + Group, + Label, + Item, + Content, + Trigger, + Separator, + ScrollDownButton, + ScrollUpButton, + GroupHeading, + // + Root as Select, + Group as SelectGroup, + Label as SelectLabel, + Item as SelectItem, + Content as SelectContent, + Trigger as SelectTrigger, + Separator as SelectSeparator, + ScrollDownButton as SelectScrollDownButton, + ScrollUpButton as SelectScrollUpButton, + GroupHeading as SelectGroupHeading +}; diff --git a/frontend/src/lib/components/ui/select/select-content.svelte b/frontend/src/lib/components/ui/select/select-content.svelte new file mode 100644 index 0000000..ca9d1b5 --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-content.svelte @@ -0,0 +1,40 @@ + + + + + + + {@render children?.()} + + + + diff --git a/frontend/src/lib/components/ui/select/select-group-heading.svelte b/frontend/src/lib/components/ui/select/select-group-heading.svelte new file mode 100644 index 0000000..3ff7003 --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-group-heading.svelte @@ -0,0 +1,21 @@ + + + + {@render children?.()} + diff --git a/frontend/src/lib/components/ui/select/select-group.svelte b/frontend/src/lib/components/ui/select/select-group.svelte new file mode 100644 index 0000000..2520795 --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-group.svelte @@ -0,0 +1,7 @@ + + + diff --git a/frontend/src/lib/components/ui/select/select-item.svelte b/frontend/src/lib/components/ui/select/select-item.svelte new file mode 100644 index 0000000..06e48be --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-item.svelte @@ -0,0 +1,38 @@ + + + + {#snippet children({ selected, highlighted })} + + {#if selected} + + {/if} + + {#if childrenProp} + {@render childrenProp({ selected, highlighted })} + {:else} + {label || value} + {/if} + {/snippet} + diff --git a/frontend/src/lib/components/ui/select/select-label.svelte b/frontend/src/lib/components/ui/select/select-label.svelte new file mode 100644 index 0000000..6c76ced --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-label.svelte @@ -0,0 +1,20 @@ + + +
    + {@render children?.()} +
    diff --git a/frontend/src/lib/components/ui/select/select-scroll-down-button.svelte b/frontend/src/lib/components/ui/select/select-scroll-down-button.svelte new file mode 100644 index 0000000..3502148 --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-scroll-down-button.svelte @@ -0,0 +1,20 @@ + + + + + diff --git a/frontend/src/lib/components/ui/select/select-scroll-up-button.svelte b/frontend/src/lib/components/ui/select/select-scroll-up-button.svelte new file mode 100644 index 0000000..3d35e04 --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-scroll-up-button.svelte @@ -0,0 +1,20 @@ + + + + + diff --git a/frontend/src/lib/components/ui/select/select-separator.svelte b/frontend/src/lib/components/ui/select/select-separator.svelte new file mode 100644 index 0000000..53d87f2 --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-separator.svelte @@ -0,0 +1,17 @@ + + + diff --git a/frontend/src/lib/components/ui/select/select-trigger.svelte b/frontend/src/lib/components/ui/select/select-trigger.svelte new file mode 100644 index 0000000..f14abe0 --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-trigger.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + + diff --git a/frontend/src/lib/components/ui/tabs/index.ts b/frontend/src/lib/components/ui/tabs/index.ts new file mode 100644 index 0000000..d2a7939 --- /dev/null +++ b/frontend/src/lib/components/ui/tabs/index.ts @@ -0,0 +1,16 @@ +import Root from './tabs.svelte'; +import Content from './tabs-content.svelte'; +import List from './tabs-list.svelte'; +import Trigger from './tabs-trigger.svelte'; + +export { + Root, + Content, + List, + Trigger, + // + Root as Tabs, + Content as TabsContent, + List as TabsList, + Trigger as TabsTrigger +}; diff --git a/frontend/src/lib/components/ui/tabs/tabs-content.svelte b/frontend/src/lib/components/ui/tabs/tabs-content.svelte new file mode 100644 index 0000000..92044c8 --- /dev/null +++ b/frontend/src/lib/components/ui/tabs/tabs-content.svelte @@ -0,0 +1,17 @@ + + + diff --git a/frontend/src/lib/components/ui/tabs/tabs-list.svelte b/frontend/src/lib/components/ui/tabs/tabs-list.svelte new file mode 100644 index 0000000..e875fe4 --- /dev/null +++ b/frontend/src/lib/components/ui/tabs/tabs-list.svelte @@ -0,0 +1,16 @@ + + + diff --git a/frontend/src/lib/components/ui/tabs/tabs-trigger.svelte b/frontend/src/lib/components/ui/tabs/tabs-trigger.svelte new file mode 100644 index 0000000..8dac6c5 --- /dev/null +++ b/frontend/src/lib/components/ui/tabs/tabs-trigger.svelte @@ -0,0 +1,20 @@ + + + diff --git a/frontend/src/lib/components/ui/tabs/tabs.svelte b/frontend/src/lib/components/ui/tabs/tabs.svelte new file mode 100644 index 0000000..b275bda --- /dev/null +++ b/frontend/src/lib/components/ui/tabs/tabs.svelte @@ -0,0 +1,19 @@ + + + diff --git a/frontend/src/lib/components/ui/tags-input/index.ts b/frontend/src/lib/components/ui/tags-input/index.ts new file mode 100644 index 0000000..25beab6 --- /dev/null +++ b/frontend/src/lib/components/ui/tags-input/index.ts @@ -0,0 +1,9 @@ +/* + Installed from @ieedan/shadcn-svelte-extras +*/ + +import TagsInput from './tags-input.svelte'; + +export { TagsInput }; + +export type * from './types'; diff --git a/frontend/src/lib/components/ui/tags-input/tags-input-tag.svelte b/frontend/src/lib/components/ui/tags-input/tags-input-tag.svelte new file mode 100644 index 0000000..6b0f4b3 --- /dev/null +++ b/frontend/src/lib/components/ui/tags-input/tags-input-tag.svelte @@ -0,0 +1,28 @@ + + + + +
    + + {value} + + +
    diff --git a/frontend/src/lib/components/ui/tags-input/tags-input.svelte b/frontend/src/lib/components/ui/tags-input/tags-input.svelte new file mode 100644 index 0000000..9990a01 --- /dev/null +++ b/frontend/src/lib/components/ui/tags-input/tags-input.svelte @@ -0,0 +1,210 @@ + + + + +
    + {#each value as tag, i (tag)} + + {/each} + +
    diff --git a/frontend/src/lib/components/ui/tags-input/types.ts b/frontend/src/lib/components/ui/tags-input/types.ts new file mode 100644 index 0000000..0b43a16 --- /dev/null +++ b/frontend/src/lib/components/ui/tags-input/types.ts @@ -0,0 +1,12 @@ +/* + Installed from @ieedan/shadcn-svelte-extras +*/ + +import type { HTMLInputAttributes } from 'svelte/elements'; + +export type TagsInputPropsWithoutHTML = { + value?: string[]; + validate?: (val: string, tags: string[]) => string | undefined; +}; + +export type TagsInputProps = TagsInputPropsWithoutHTML & Omit; diff --git a/frontend/src/lib/components/ui/tooltip/index.ts b/frontend/src/lib/components/ui/tooltip/index.ts new file mode 100644 index 0000000..273d831 --- /dev/null +++ b/frontend/src/lib/components/ui/tooltip/index.ts @@ -0,0 +1,21 @@ +import { Tooltip as TooltipPrimitive } from 'bits-ui'; +import Trigger from './tooltip-trigger.svelte'; +import Content from './tooltip-content.svelte'; + +const Root = TooltipPrimitive.Root; +const Provider = TooltipPrimitive.Provider; +const Portal = TooltipPrimitive.Portal; + +export { + Root, + Trigger, + Content, + Provider, + Portal, + // + Root as Tooltip, + Content as TooltipContent, + Trigger as TooltipTrigger, + Provider as TooltipProvider, + Portal as TooltipPortal +}; diff --git a/frontend/src/lib/components/ui/tooltip/tooltip-content.svelte b/frontend/src/lib/components/ui/tooltip/tooltip-content.svelte new file mode 100644 index 0000000..60d68f5 --- /dev/null +++ b/frontend/src/lib/components/ui/tooltip/tooltip-content.svelte @@ -0,0 +1,47 @@ + + + + + {@render children?.()} + + {#snippet child({ props })} +
    + {/snippet} +
    +
    +
    diff --git a/frontend/src/lib/components/ui/tooltip/tooltip-trigger.svelte b/frontend/src/lib/components/ui/tooltip/tooltip-trigger.svelte new file mode 100644 index 0000000..5631d1b --- /dev/null +++ b/frontend/src/lib/components/ui/tooltip/tooltip-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/frontend/src/lib/constants/userRoles.ts b/frontend/src/lib/constants/userRoles.ts new file mode 100644 index 0000000..aaf6290 --- /dev/null +++ b/frontend/src/lib/constants/userRoles.ts @@ -0,0 +1,31 @@ +import { View, CodeXml, Crown, CircleDollarSign, type Icon } from '@lucide/svelte'; +import type { Component } from 'svelte'; + +export const USER_ROLES: { + [key: string]: { + label: string; + description: string; + icon: Component; + }; +} = { + VIEWER: { + label: 'Viewer', + description: 'Can view projects and data, but cannot make changes.', + icon: View + }, + FOUNDER: { + label: 'Founder', + description: 'Can create and manage projects', + icon: Crown + }, + DEVELOPER: { + label: 'Developer', + description: 'Can contribute to projects and manage code.', + icon: CodeXml + }, + INVESTOR: { + label: 'Investor', + description: 'Can financially support projects.', + icon: CircleDollarSign + } +}; diff --git a/frontend/src/lib/utils/utils.ts b/frontend/src/lib/utils/utils.ts new file mode 100644 index 0000000..85bb323 --- /dev/null +++ b/frontend/src/lib/utils/utils.ts @@ -0,0 +1,17 @@ +/* + Installed from @ieedan/shadcn-svelte-extras +*/ + +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChild = T extends { child?: any } ? Omit : T; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChildren = T extends { children?: any } ? Omit : T; +export type WithoutChildrenOrChild = WithoutChildren>; +export type WithElementRef = T & { ref?: U | null }; diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index c8e5cc3..fad8025 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -2,7 +2,6 @@ import { Button } from '@/components/ui/button'; import * as Card from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; - import placeholder from '@/assets/placeholder.svg'; import { Lightbulb, Code, @@ -18,6 +17,8 @@ Github } from '@lucide/svelte'; import GlobeCard from '@/components/landing/GlobeCard.svelte'; + import Orbit from '@/components/landing/Orbit.svelte'; + import Reviews from '@/components/landing/Reviews.svelte';
    @@ -84,13 +85,6 @@
    - - - - - - -
    For Everyone -

    +

    Built for every role in the startup ecosystem

    @@ -264,13 +258,7 @@
    - Team Collaboration +
    @@ -313,16 +301,12 @@ - +
    - Startup Graveyard Analytics +
    diff --git a/frontend/src/routes/app/(components)/ChooseRoleDialog.svelte b/frontend/src/routes/app/(components)/ChooseRoleDialog.svelte index c866635..e48a334 100644 --- a/frontend/src/routes/app/(components)/ChooseRoleDialog.svelte +++ b/frontend/src/routes/app/(components)/ChooseRoleDialog.svelte @@ -46,7 +46,8 @@ } ]; - let { open = $bindable(false) }: { open: boolean } = $props(); + let { open = $bindable(false), dismissable = false }: { open: boolean; dismissable?: boolean } = + $props(); let selectedRole: UserRole | null = $state(null); const setRoleMutation = createMutation({ @@ -63,7 +64,7 @@ - + Choose Your Role diff --git a/frontend/src/routes/app/(components)/CreateProjectDialog.svelte b/frontend/src/routes/app/(components)/CreateProjectDialog.svelte index 2b79615..5ae77df 100644 --- a/frontend/src/routes/app/(components)/CreateProjectDialog.svelte +++ b/frontend/src/routes/app/(components)/CreateProjectDialog.svelte @@ -9,6 +9,7 @@ import { toast } from 'svelte-sonner'; import { createProject } from './dataLoaders'; import { LoaderCircle } from '@lucide/svelte'; + import { goto } from '$app/navigation'; let name = $state(''); let description = $state(''); @@ -32,7 +33,7 @@ isOpensource: boolean; isDead: boolean; }) => await createProject(title, description, isOpensource, isDead), - onSuccess: () => { + onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ['projects'] }); toast.success('Project created successfully'); open = false; @@ -40,6 +41,9 @@ description = ''; isOpensource = false; isDead = false; + goto(`/app/projects/${data.id}`, { + replaceState: true + }); }, onError: (error) => { toast.error(`Failed to create project: ${error.message}`); @@ -59,12 +63,13 @@
    - +