diff --git a/.codex/config.toml b/.codex/config.toml index eb47db0..6a0aa42 100644 --- a/.codex/config.toml +++ b/.codex/config.toml @@ -5,5 +5,8 @@ command = "C:\\Users\\jamius19\\miniconda3\\envs\\awsmcp\\Scripts\\awslabs.opena args = ["--spec-url", "http://localhost:8080/v3/api-docs"] enabled = true +[mcp_servers.flightdrift_openapi.tools.getOrganizations] +approval_mode = "approve" + [sandbox_workspace_write] network_access = true diff --git a/AGENTS.md b/AGENTS.md index ec6c6a2..7f93855 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,4 +8,7 @@ Check `AGENTS.md` file within each application dir (i.e., `flightdrift-frontend/ Keep your changes as small as possible and always ask before making a big change. -When you create/modify any endpoint, update the relavant yml file(s) in the `api-collection\Flightdrift` OpenCollection directory. \ No newline at end of file +When you create/modify any endpoint, update the relavant yml file(s) in the `api-collection\Flightdrift` OpenCollection directory. + +## Tools +1. Check if `rg` is available, if so use it, otherwise use `grep`. \ No newline at end of file diff --git a/api-collection/Flightdrift/Auth/Sign In.yml b/api-collection/Flightdrift/Auth/Sign In.yml index 535ffe1..6277026 100644 --- a/api-collection/Flightdrift/Auth/Sign In.yml +++ b/api-collection/Flightdrift/Auth/Sign In.yml @@ -20,7 +20,7 @@ runtime: code: |- var jsonData = res.getBody(); - bru.set("auth_token", jsonData.data.token); + bru.setEnvVar("auth_token", jsonData.data.token); settings: encodeUrl: true @@ -73,6 +73,12 @@ examples: "success": true, "message": "Signed in successfully", "data": { - "token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJqYW1pdXMxOSIsImlhdCI6MTc3OTkwMjgxMiwiZXhwIjoxODY2MzAyODEyfQ.Y0Rca6ESQvV16n_VJxP4rzxVERvRaSzonuOFTfyD0Ytz1H4cMEnQ-crukQ0i0l_Bwf72m_aCUfqSamdABNB6Vg" + "token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJqYW1pdXMxOSIsImlhdCI6MTc3OTkwMjgxMiwiZXhwIjoxODY2MzAyODEyfQ.Y0Rca6ESQvV16n_VJxP4rzxVERvRaSzonuOFTfyD0Ytz1H4cMEnQ-crukQ0i0l_Bwf72m_aCUfqSamdABNB6Vg", + "userInfo": { + "id": "0c71345a-0a0f-42a5-88dc-43616f65d5e3", + "name": "Jamius Siam", + "email": "jamius@example.com", + "username": "jamius19" + } } } diff --git a/api-collection/Flightdrift/Auth/Signup.yml b/api-collection/Flightdrift/Auth/Signup.yml index 942e769..c2324be 100644 --- a/api-collection/Flightdrift/Auth/Signup.yml +++ b/api-collection/Flightdrift/Auth/Signup.yml @@ -11,7 +11,7 @@ http: data: |- { "name": "Jamius Siam", - "email": "jamiussiam@gmail.com", + "email": "jamius@example.com", "username": "jamius19", "password": "123" } @@ -32,7 +32,7 @@ examples: data: |- { "name": "Jamius Siam", - "email": "jamiussiam@gmail.com", + "email": "jamius@example.com", "username": "jamius19", "password": "123" } @@ -69,9 +69,9 @@ examples: "success": true, "message": "Account created successfully", "data": { - "id": "019e6a79-5e97-7b4d-9f53-b7d0e4a3f96c", + "id": "019e73b4-33b7-74fd-ad65-3bd068e2e46c", "name": "Jamius Siam", - "email": "jamiussiam@gmail.com", + "email": "jamius@example.com", "username": "jamius19" } } diff --git a/api-collection/Flightdrift/Project/Create Project.yml b/api-collection/Flightdrift/Project/Create Project.yml index 31f8620..488e85c 100644 --- a/api-collection/Flightdrift/Project/Create Project.yml +++ b/api-collection/Flightdrift/Project/Create Project.yml @@ -14,6 +14,7 @@ http: data: |- { "name": "Flightdrift", + "code": "ARTEMIS", "iconUrl": "https://example.com/project.png" } auth: inherit @@ -42,6 +43,7 @@ examples: data: |- { "name": "Flightdrift", + "code": "ARTEMIS", "iconUrl": "https://example.com/project.png" } response: @@ -60,6 +62,7 @@ examples: "project": { "id": "019e6a80-e269-7ddc-9e6b-d25eccfd192d", "name": "Flightdrift", + "code": "ARTEMIS", "iconUrl": "https://example.com/project.png", "organizationId": "019e6a80-6593-7416-8876-dec6b99510ab" } diff --git a/api-collection/Flightdrift/Project/Edit Project.yml b/api-collection/Flightdrift/Project/Edit Project.yml index 7af765c..f745449 100644 --- a/api-collection/Flightdrift/Project/Edit Project.yml +++ b/api-collection/Flightdrift/Project/Edit Project.yml @@ -14,6 +14,7 @@ http: data: |- { "name": "Flightdrift HQ", + "code": "ARTEMIS", "iconUrl": "https://example.com/project-updated.png" } auth: inherit @@ -34,6 +35,7 @@ examples: data: |- { "name": "Flightdrift HQ", + "code": "ARTEMIS", "iconUrl": "https://example.com/project-updated.png" } response: @@ -52,6 +54,7 @@ examples: "project": { "id": "019e6a80-e269-7ddc-9e6b-d25eccfd192d", "name": "Flightdrift HQ", + "code": "ARTEMIS", "iconUrl": "https://example.com/project-updated.png", "organizationId": "019e6a80-6593-7416-8876-dec6b99510ab" } diff --git a/api-collection/Flightdrift/Project/Get Projects.yml b/api-collection/Flightdrift/Project/Get Projects.yml index a2f870e..9a2c8e2 100644 --- a/api-collection/Flightdrift/Project/Get Projects.yml +++ b/api-collection/Flightdrift/Project/Get Projects.yml @@ -36,6 +36,7 @@ examples: { "id": "019e6a80-e269-7ddc-9e6b-d25eccfd192d", "name": "Flightdrift", + "code": "ARTEMIS", "iconUrl": "https://example.com/project.png", "organizationId": "019e6a80-6593-7416-8876-dec6b99510ab" } diff --git a/api-collection/Flightdrift/environments/Flightdrift-Dev.yml b/api-collection/Flightdrift/environments/Flightdrift-Dev.yml new file mode 100644 index 0000000..0d08bc8 --- /dev/null +++ b/api-collection/Flightdrift/environments/Flightdrift-Dev.yml @@ -0,0 +1 @@ +name: Flightdrift-Dev diff --git a/api-collection/Flightdrift/opencollection.yml b/api-collection/Flightdrift/opencollection.yml index 7d521c0..a8adc85 100644 --- a/api-collection/Flightdrift/opencollection.yml +++ b/api-collection/Flightdrift/opencollection.yml @@ -27,8 +27,6 @@ request: value: 019e6a80-e269-7ddc-9e6b-d25eccfd192d - name: item_id value: 019b7a23-58a7-72f0-bdea-b39d8ab32191 - - name: auth_token - value: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJqYW1pdXMxOSIsImlhdCI6MTc3OTkwMjgxMiwiZXhwIjoxODY2MzAyODEyfQ.Y0Rca6ESQvV16n_VJxP4rzxVERvRaSzonuOFTfyD0Ytz1H4cMEnQ-crukQ0i0l_Bwf72m_aCUfqSamdABNB6Vg bundled: false extensions: bruno: diff --git a/db/migration/1. account.sql b/db/migration/1. account.sql index 6fa9c05..4a07c7a 100644 --- a/db/migration/1. account.sql +++ b/db/migration/1. account.sql @@ -14,6 +14,7 @@ CREATE TABLE account updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); +ALTER TABLE account OWNER TO flightdrift; CREATE TABLE blacklisted_auth_token ( @@ -23,3 +24,5 @@ CREATE TABLE blacklisted_auth_token created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); + +ALTER TABLE blacklisted_auth_token OWNER TO flightdrift; diff --git a/db/migration/2. organization.sql b/db/migration/2. organization.sql index 7879b9f..9477809 100644 --- a/db/migration/2. organization.sql +++ b/db/migration/2. organization.sql @@ -13,6 +13,8 @@ CREATE TABLE organization updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); +ALTER TABLE organization OWNER TO flightdrift; + CREATE TABLE account_organization_mapping ( id UUID PRIMARY KEY DEFAULT uuidv7(), @@ -23,3 +25,5 @@ CREATE TABLE account_organization_mapping created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); + +ALTER TABLE account_organization_mapping OWNER TO flightdrift; diff --git a/db/migration/3. project.sql b/db/migration/3. project.sql index 5df83ff..f77fa9b 100644 --- a/db/migration/3. project.sql +++ b/db/migration/3. project.sql @@ -6,6 +6,7 @@ CREATE TABLE project ( id UUID PRIMARY KEY DEFAULT uuidv7(), name VARCHAR(50) NOT NULL, + code VARCHAR(10) NOT NULL, icon_url VARCHAR(1024), organization_id UUID REFERENCES organization (id) NOT NULL, version BIGINT DEFAULT 0, @@ -13,4 +14,6 @@ CREATE TABLE project updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); +ALTER TABLE project OWNER TO flightdrift; + CREATE INDEX idx_project_organization_id ON project (organization_id); diff --git a/db/migration/4. item.sql b/db/migration/4. item.sql index 5f0994d..be3cfa9 100644 --- a/db/migration/4. item.sql +++ b/db/migration/4. item.sql @@ -18,4 +18,6 @@ CREATE TABLE item ) ); +ALTER TABLE item OWNER TO flightdrift; + CREATE INDEX idx_item_project_id ON item (project_id); diff --git a/db/migration/5. flag.sql b/db/migration/5. flag.sql index fba70f3..bec1cda 100644 --- a/db/migration/5. flag.sql +++ b/db/migration/5. flag.sql @@ -11,3 +11,5 @@ CREATE TABLE flag created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); + +ALTER TABLE flag OWNER TO flightdrift; diff --git a/db/migration/6. setting.sql b/db/migration/6. setting.sql index 216219a..ff7ebdc 100644 --- a/db/migration/6. setting.sql +++ b/db/migration/6. setting.sql @@ -11,3 +11,5 @@ CREATE TABLE setting created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); + +ALTER TABLE setting OWNER TO flightdrift; diff --git a/flightdrift-backend/AGENTS.md b/flightdrift-backend/AGENTS.md index 37c5add..a1270be 100644 --- a/flightdrift-backend/AGENTS.md +++ b/flightdrift-backend/AGENTS.md @@ -2,12 +2,11 @@ This is a Java 25 Spring Boot 4 project for a kanban-inspired project management ## Rules for Development 1. Use Java 25 LTS features. -2. Do not try to compile and test unless I ask you to. -3. Use the `dto.com.flightdrift.flightdrift.ApiResponse` class for API responses. -4. Add new lines between each instance variables of a class. -5. Use Lombok and Jakarta persistence annotations where applicable. -6. Use Lombok's `@Builder` when creating entites. -7. DB related sql files can be found under `db/migration` - -## Tools -1. Check if `rg` is available, if so use it, otherwise use `grep`. \ No newline at end of file +2. Compile to check for errors without running tests. +3. Do not run tests for now. +4. Use the `dto.com.flightdrift.flightdrift.ApiResponse` class for API responses. +5. Update the OpenCollection files at `api-collection/Flightdrift` when adding/updating endpoints. +6. Add new lines between each instance variable of a class. +7. Use Lombok and Jakarta persistence annotations where applicable. +8. Use Lombok's `@Builder` when creating entities. +9. DB related SQL files can be found under `db/migration` \ No newline at end of file diff --git a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/config/SecurityConfig.java b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/config/SecurityConfig.java index 758e24e..974e955 100644 --- a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/config/SecurityConfig.java +++ b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/config/SecurityConfig.java @@ -14,6 +14,8 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /* * Author: Jamius Siam @@ -55,4 +57,14 @@ public PasswordEncoder passwordEncoder() { public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); } + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**").allowedOrigins("*"); + } + }; + } } \ No newline at end of file diff --git a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/controller/AuthController.java b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/controller/AuthController.java index e2867f4..4756693 100644 --- a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/controller/AuthController.java +++ b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/controller/AuthController.java @@ -15,6 +15,7 @@ import com.flightdrift.flightdrift.dto.auth.InvitationTokenResponse; import com.flightdrift.flightdrift.dto.auth.SignupResponse; import com.flightdrift.flightdrift.dto.auth.TokenResponse; +import com.flightdrift.flightdrift.dto.auth.UserInfoResponse; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; @@ -91,11 +92,22 @@ public ResponseEntity> signin(@Valid @RequestBody SigninRequest s UserDetails userDetails = userDetailsService.loadUserByUsername(signinRequest.username()); String token = jwtUtil.generateToken(userDetails); + Account account = accountRepository + .findByUsername(userDetails.getUsername()) + .orElseThrow(() -> new IllegalStateException("Authenticated account not found")); return ResponseEntity.ok(new ApiResponse<>( true, "Signed in successfully", - new TokenResponse(token) + new TokenResponse( + token, + new UserInfoResponse( + account.getId(), + account.getName(), + account.getEmail(), + account.getUsername() + ) + ) )); } diff --git a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/auth/TokenResponse.java b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/auth/TokenResponse.java index b331e65..ffc5893 100644 --- a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/auth/TokenResponse.java +++ b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/auth/TokenResponse.java @@ -4,5 +4,8 @@ * Author: Jamius Siam * Since: 06/05/2026 */ -public record TokenResponse(String token) { +public record TokenResponse( + String token, + UserInfoResponse userInfo +) { } diff --git a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/auth/UserInfoResponse.java b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/auth/UserInfoResponse.java new file mode 100644 index 0000000..ddd7297 --- /dev/null +++ b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/auth/UserInfoResponse.java @@ -0,0 +1,15 @@ +package com.flightdrift.flightdrift.dto.auth; + +import java.util.UUID; + +/* + * Author: Jamius Siam + * Since: 29/05/2026 + */ +public record UserInfoResponse( + UUID id, + String name, + String email, + String username +) { +} diff --git a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/project/CreateProjectRequest.java b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/project/CreateProjectRequest.java index b38ae24..dcedc3f 100644 --- a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/project/CreateProjectRequest.java +++ b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/project/CreateProjectRequest.java @@ -12,6 +12,10 @@ public record CreateProjectRequest( @Size(max = 50, message = "Name must not exceed 50 characters") String name, + @NotBlank(message = "Code is required") + @Size(max = 10, message = "Code must not exceed 10 characters") + String code, + @Size(max = 1024, message = "Icon URL must not exceed 1024 characters") String iconUrl ) { diff --git a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/project/EditProjectRequest.java b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/project/EditProjectRequest.java index cbc91ce..7ab4b29 100644 --- a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/project/EditProjectRequest.java +++ b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/project/EditProjectRequest.java @@ -12,6 +12,10 @@ public record EditProjectRequest( @Size(max = 50, message = "Name must not exceed 50 characters") String name, + @NotBlank(message = "Code is required") + @Size(max = 10, message = "Code must not exceed 10 characters") + String code, + @Size(max = 1024, message = "Icon URL must not exceed 1024 characters") String iconUrl ) { diff --git a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/project/ProjectResponse.java b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/project/ProjectResponse.java index ffed2ad..d6e089d 100644 --- a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/project/ProjectResponse.java +++ b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/dto/project/ProjectResponse.java @@ -11,6 +11,7 @@ public record ProjectResponse( UUID id, String name, + String code, String iconUrl, UUID organizationId ) { @@ -18,6 +19,7 @@ public static ProjectResponse fromEntity(Project project) { return new ProjectResponse( project.getId(), project.getName(), + project.getCode(), project.getIconUrl(), project.getOrganization().getId() ); diff --git a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/entity/Project.java b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/entity/Project.java index 1e42c79..b52d2fd 100644 --- a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/entity/Project.java +++ b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/entity/Project.java @@ -36,6 +36,9 @@ public class Project extends Auditable { @Column(nullable = false, length = 50) private String name; + @Column(nullable = false, length = 10) + private String code; + @Column(name = "icon_url", length = 1024) private String iconUrl; diff --git a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/exception/GlobalExceptionHandler.java b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..1107667 --- /dev/null +++ b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/exception/GlobalExceptionHandler.java @@ -0,0 +1,37 @@ +package com.flightdrift.flightdrift.exception; + +import com.flightdrift.flightdrift.dto.ApiResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.server.ResponseStatusException; + +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; + +/* + * Author: Jamius Siam + * Since: 30/05/2026 + */ +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(ResponseStatusException.class) + public ResponseEntity> handleResponseStatusException(ResponseStatusException exception) { + String message = exception.getReason() == null ? exception.getMessage() : exception.getReason(); + + return ResponseEntity + .status(exception.getStatusCode()) + .body(new ApiResponse<>(false, message, null)); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleException(Exception exception) { + log.error("Unhandled exception", exception); + + return ResponseEntity + .status(INTERNAL_SERVER_ERROR) + .body(new ApiResponse<>(false, "Internal server error", null)); + } +} diff --git a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/service/ProjectService.java b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/service/ProjectService.java index 4e0e1d7..8bcae8e 100644 --- a/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/service/ProjectService.java +++ b/flightdrift-backend/src/main/java/com/flightdrift/flightdrift/service/ProjectService.java @@ -47,6 +47,7 @@ public ProjectResponse createProject(String username, UUID organizationId, Creat Project project = Project.builder() .name(request.name()) + .code(request.code()) .iconUrl(request.iconUrl()) .organization(organization) .build(); @@ -81,6 +82,7 @@ public ProjectResponse editProject(String username, UUID projectId, EditProjectR } project.setName(request.name()); + project.setCode(request.code()); project.setIconUrl(request.iconUrl()); project = projectRepository.save(project); diff --git a/flightdrift-frontend/.env b/flightdrift-frontend/.env new file mode 100644 index 0000000..a8cf54a --- /dev/null +++ b/flightdrift-frontend/.env @@ -0,0 +1 @@ +VITE_API_BASE_URL=http://localhost:8080 diff --git a/flightdrift-frontend/.gitignore b/flightdrift-frontend/.gitignore index 07e023a..d024b94 100644 --- a/flightdrift-frontend/.gitignore +++ b/flightdrift-frontend/.gitignore @@ -23,3 +23,4 @@ dist-ssr *.sln *.sw? .tanstack +routeTree.gen.ts diff --git a/flightdrift-frontend/AGENTS.md b/flightdrift-frontend/AGENTS.md index 4d457de..895b3b0 100644 --- a/flightdrift-frontend/AGENTS.md +++ b/flightdrift-frontend/AGENTS.md @@ -16,4 +16,8 @@ Here are some rules for the project: 8. Use explicit return types. 9. Don't exceed 120 characters per line, try to keep it to 80. 10. Don't install NPM packages without explicit permission from the user. - 11. Don't run vite or browser to check the visuals for now. \ No newline at end of file + 11. Don't run vite or browser to check the visuals for now. + 12. Move reused types in the `@types` folder. + 13. If people usually use a specific library for a specific task, prompt the user if they want to use it. (e.g., + form validation with Zod) + 14. If variables can be moved outside the component, do so. \ No newline at end of file diff --git a/flightdrift-frontend/eslint.config.js b/flightdrift-frontend/eslint.config.js index c127865..ab6d674 100644 --- a/flightdrift-frontend/eslint.config.js +++ b/flightdrift-frontend/eslint.config.js @@ -8,8 +8,10 @@ import reactHooks from "eslint-plugin-react-hooks"; import reactRefresh from "eslint-plugin-react-refresh"; import tseslint from "typescript-eslint"; import { defineConfig, globalIgnores } from "eslint/config"; +import pluginQuery from "@tanstack/eslint-plugin-query"; export default defineConfig([ + ...pluginQuery.configs["flat/recommended"], globalIgnores(["dist"]), { files: ["**/*.{ts,tsx}"], diff --git a/flightdrift-frontend/package-lock.json b/flightdrift-frontend/package-lock.json index c9cf838..66c7d5d 100644 --- a/flightdrift-frontend/package-lock.json +++ b/flightdrift-frontend/package-lock.json @@ -11,10 +11,14 @@ "@fontsource-variable/inter": "^5.2.8", "@fontsource-variable/raleway": "^5.2.8", "@tailwindcss/vite": "^4.3.0", + "@tanstack/react-form": "^1.33.0", + "@tanstack/react-query": "^5.100.14", "@tanstack/react-router": "^1.169.2", "@tanstack/react-router-devtools": "^1.166.13", + "axios": "^1.16.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "jwt-decode": "^4.0.0", "lucide-react": "^1.17.0", "radix-ui": "^1.4.3", "react": "^19.2.5", @@ -22,10 +26,13 @@ "shadcn": "^4.8.2", "tailwind-merge": "^3.6.0", "tailwindcss": "^4.3.0", - "tw-animate-css": "^1.4.0" + "tw-animate-css": "^1.4.0", + "zod": "^4.4.3", + "zustand": "^5.0.14" }, "devDependencies": { "@eslint/js": "^10.0.1", + "@tanstack/eslint-plugin-query": "^5.100.14", "@tanstack/router-cli": "^1.166.42", "@tanstack/router-plugin": "^1.167.34", "@types/node": "^24.12.2", @@ -3269,6 +3276,70 @@ "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, + "node_modules/@tanstack/devtools-event-client": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@tanstack/devtools-event-client/-/devtools-event-client-0.4.3.tgz", + "integrity": "sha512-OZI6QyULw0FI0wjgmeYzCIfbgPsOEzwJtCpa69XrfLMtNXLGnz3d/dIabk7frg0TmHo+Ah49w5I4KC7Tufwsvw==", + "license": "MIT", + "bin": { + "intent": "bin/intent.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/eslint-plugin-query": { + "version": "5.100.14", + "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.100.14.tgz", + "integrity": "sha512-NbpiBCmeHTRuVHeV5+U+1bzmxyTW5Dzp2sCeE6Hx+ZJTJWFK9dsm8VZmRc7LQP9/ZORsF620PvgUk67AwiBo4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.58.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": "^5.4.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@tanstack/form-core": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/@tanstack/form-core/-/form-core-1.33.0.tgz", + "integrity": "sha512-AV4Pw9Dk4orFsuPBcDssfWMJFs+yMYBae7zZ4oTqrCf4ftNGQKxvrQRZeqKHG6A4TkiLeSvf2kzIjcVkrW7E6w==", + "license": "MIT", + "dependencies": { + "@tanstack/devtools-event-client": "^0.4.1", + "@tanstack/pacer-lite": "^0.1.1", + "@tanstack/store": "^0.11.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/form-core/node_modules/@tanstack/store": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.11.0.tgz", + "integrity": "sha512-WlzzCt3xi0G6pCAJu1U+2jiECwabETDpQDi3hfkFZvJii9AuZqEKbOiVarX1/bWhTNjU486yQtJCCasi/0q+Cw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/history": { "version": "1.161.6", "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.161.6.tgz", @@ -3282,6 +3353,95 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tanstack/pacer-lite": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@tanstack/pacer-lite/-/pacer-lite-0.1.1.tgz", + "integrity": "sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.100.14", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.14.tgz", + "integrity": "sha512-5X41dGpxgeaHISCRW2oYwcSycZeULZzAunaudXT9ov1KOTj9xwt0CH6hbwqP1/z74ZWF7rYFnDpyYH07XFcZew==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-form": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-form/-/react-form-1.33.0.tgz", + "integrity": "sha512-unaee+VS4MvKo+s1dmgGUXI4902VeAhuaUbKsQbhFe3MceOpB3JpAUGCDpyzjQPXVFkFY0COKfLrUNX2XZYW4g==", + "license": "MIT", + "dependencies": { + "@tanstack/form-core": "1.33.0", + "@tanstack/react-store": "^0.11.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@tanstack/react-start": { + "optional": true + } + } + }, + "node_modules/@tanstack/react-form/node_modules/@tanstack/react-store": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.11.0.tgz", + "integrity": "sha512-tX4YXh3PDkmpvGQWkWqKpzs/MSqbtuwY9dWdWhtV9Q50PmO+jOkUKIWIX4G85dwt7lxdHLXsiaEKPdKmC8F41w==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "0.11.0", + "use-sync-external-store": "^1.6.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/react-form/node_modules/@tanstack/store": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.11.0.tgz", + "integrity": "sha512-WlzzCt3xi0G6pCAJu1U+2jiECwabETDpQDi3hfkFZvJii9AuZqEKbOiVarX1/bWhTNjU486yQtJCCasi/0q+Cw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.100.14", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.14.tgz", + "integrity": "sha512-oOr6aRdSFEwWhzxEkD/9ZcItM3+LjBSkeVmadWKwUssAHTsqd/7bOjWrX4AbvEkoEhgAxzN0Xk6H/aYzXiYBAw==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.100.14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@tanstack/react-router": { "version": "1.169.2", "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.169.2.tgz", @@ -4144,6 +4304,49 @@ "node": ">=4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/babel-dead-code-elimination": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.12.tgz", @@ -4569,6 +4772,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "14.0.3", "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", @@ -4801,6 +5016,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4991,6 +5215,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -5574,6 +5813,63 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -5825,6 +6121,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", @@ -6328,6 +6639,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -7740,6 +8060,15 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -9318,9 +9647,9 @@ } }, "node_modules/zod": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.2.tgz", - "integrity": "sha512-IynmDyxsEsb9RKzO3J9+4SxXnl2FTFSzNBaKKaMV6tsSk0rw9gYw9gs+JFCq/qk2LCZ78KDwyj+Z289TijSkUw==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -9347,6 +9676,35 @@ "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } + }, + "node_modules/zustand": { + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", + "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/flightdrift-frontend/package.json b/flightdrift-frontend/package.json index 2264105..33c52bc 100644 --- a/flightdrift-frontend/package.json +++ b/flightdrift-frontend/package.json @@ -18,10 +18,14 @@ "@fontsource-variable/inter": "^5.2.8", "@fontsource-variable/raleway": "^5.2.8", "@tailwindcss/vite": "^4.3.0", + "@tanstack/react-form": "^1.33.0", + "@tanstack/react-query": "^5.100.14", "@tanstack/react-router": "^1.169.2", "@tanstack/react-router-devtools": "^1.166.13", + "axios": "^1.16.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "jwt-decode": "^4.0.0", "lucide-react": "^1.17.0", "radix-ui": "^1.4.3", "react": "^19.2.5", @@ -29,10 +33,13 @@ "shadcn": "^4.8.2", "tailwind-merge": "^3.6.0", "tailwindcss": "^4.3.0", - "tw-animate-css": "^1.4.0" + "tw-animate-css": "^1.4.0", + "zod": "^4.4.3", + "zustand": "^5.0.14" }, "devDependencies": { "@eslint/js": "^10.0.1", + "@tanstack/eslint-plugin-query": "^5.100.14", "@tanstack/router-cli": "^1.166.42", "@tanstack/router-plugin": "^1.167.34", "@types/node": "^24.12.2", diff --git a/flightdrift-frontend/src/@types/auth.ts b/flightdrift-frontend/src/@types/auth.ts new file mode 100644 index 0000000..ae6fe7a --- /dev/null +++ b/flightdrift-frontend/src/@types/auth.ts @@ -0,0 +1,36 @@ +/* + * Author: Jamius Siam + * Since: 29/05/2026 + */ +export type Account = { + id: string; + name: string; + email: string; + username: string; +}; + +export type ApiResponse = { + success: boolean; + message: string; + data: TData; +}; + +export type SigninRequest = { + username: string; + password: string; +}; + +export type SignupRequest = { + name: string; + email: string; + username: string; + password: string; + invitationCode?: string; +}; + +export type TokenResponse = { + token: string; + userInfo: Account; +}; + +export type SignupResponse = Account; diff --git a/flightdrift-frontend/src/@types/item.ts b/flightdrift-frontend/src/@types/item.ts new file mode 100644 index 0000000..e38e133 --- /dev/null +++ b/flightdrift-frontend/src/@types/item.ts @@ -0,0 +1,25 @@ +/* + * Author: Jamius Siam + * Since: 29/05/2026 + */ +export type BoardItemTag = { + name: string; +}; + +export type BoardItem = { + code: string; + title: string; + assignee: { + name: string; + avatarUrl: string; + }; + status: string; + priority: string; + dateRange: string; + tags: BoardItemTag[]; +}; + +export type Board = { + title: string; + items: BoardItem[]; +}; diff --git a/flightdrift-frontend/src/components/alerts/error-alert.tsx b/flightdrift-frontend/src/components/alerts/error-alert.tsx new file mode 100644 index 0000000..03594c8 --- /dev/null +++ b/flightdrift-frontend/src/components/alerts/error-alert.tsx @@ -0,0 +1,27 @@ +/* + * Author: Jamius Siam + * Since: 29/05/2026 + */ +import type { JSX } from "react"; + +type ErrorAlertProps = { + message?: string; + show: boolean; +}; + +const ErrorAlert = ({ message, show }: ErrorAlertProps): JSX.Element | null => { + if (!show || !message) { + return null; + } + + return ( +

+ {message} +

+ ); +}; + +export default ErrorAlert; diff --git a/flightdrift-frontend/src/components/alerts/success-alert.tsx b/flightdrift-frontend/src/components/alerts/success-alert.tsx new file mode 100644 index 0000000..5b864bf --- /dev/null +++ b/flightdrift-frontend/src/components/alerts/success-alert.tsx @@ -0,0 +1,26 @@ +/* + * Author: Jamius Siam + * Since: 29/05/2026 + */ +import type { JSX } from "react"; + +type SuccessAlertProps = { + message?: string; + show: boolean; +}; + +const SuccessAlert = ({ message, show }: SuccessAlertProps): JSX.Element | null => { + if (!show || !message) { + return null; + } + + return ( +

+ {message} +

+ ); +}; + +export default SuccessAlert; diff --git a/flightdrift-frontend/src/components/item/boards.tsx b/flightdrift-frontend/src/components/items/boards.tsx similarity index 93% rename from flightdrift-frontend/src/components/item/boards.tsx rename to flightdrift-frontend/src/components/items/boards.tsx index 812b6cd..f4f2465 100644 --- a/flightdrift-frontend/src/components/item/boards.tsx +++ b/flightdrift-frontend/src/components/items/boards.tsx @@ -5,28 +5,7 @@ import { Button } from "@/components/ui/button.tsx"; import { CalendarClock, Circle, Minimize2, Plus, Signal, Tag } from "lucide-react"; import type { JSX, ReactNode } from "react"; - -type BoardItemTag = { - name: string; -}; - -type BoardItem = { - code: string; - title: string; - assignee: { - name: string; - avatarUrl: string; - }; - status: string; - priority: string; - dateRange: string; - tags: BoardItemTag[]; -}; - -export type Board = { - title: string; - items: BoardItem[]; -}; +import type { Board, BoardItem } from "@/@types/item.ts"; type BoardsProps = { boards: Board[]; diff --git a/flightdrift-frontend/src/components/item/breadcrumb.tsx b/flightdrift-frontend/src/components/items/breadcrumb.tsx similarity index 100% rename from flightdrift-frontend/src/components/item/breadcrumb.tsx rename to flightdrift-frontend/src/components/items/breadcrumb.tsx diff --git a/flightdrift-frontend/src/components/item/horizontal-rule.tsx b/flightdrift-frontend/src/components/items/horizontal-rule.tsx similarity index 100% rename from flightdrift-frontend/src/components/item/horizontal-rule.tsx rename to flightdrift-frontend/src/components/items/horizontal-rule.tsx diff --git a/flightdrift-frontend/src/components/loader/loader.tsx b/flightdrift-frontend/src/components/loader/loader.tsx new file mode 100644 index 0000000..59e7ac9 --- /dev/null +++ b/flightdrift-frontend/src/components/loader/loader.tsx @@ -0,0 +1,23 @@ +/* + * Author: Jamius Siam + * Since: 29/05/2026 + */ +import { cn } from "@/lib/utils.ts"; +import { LoaderCircle } from "lucide-react"; +import type { JSX } from "react"; + +type LoaderProps = { + className?: string; +}; + +const Loader = ({ className }: LoaderProps): JSX.Element => { + return ( +