17 Commits

Author SHA1 Message Date
a35a423447 Merge pull request 'front_foundation' () from front_foundation into main
Reviewed-on: 
Reviewed-by: adnane <adnane.alami@bordeaux-inp.fr>
Reviewed-by: anas <anas.maillal@bordeaux-inp.fr>
Reviewed-by: omar <omar.el_alaoui_el_ismaili@bordeaux-inp.fr>
2025-05-14 09:08:32 +02:00
47be7d340b fix: formatter 2025-05-14 09:06:52 +02:00
232d10b164 fix: formatter 2025-05-14 09:04:35 +02:00
bc7ce888ad fix: build 2025-05-13 20:00:51 +02:00
ed67a3734a fix: fix build 2025-05-13 19:56:03 +02:00
95eb154556 fix: eslint 2025-05-13 19:52:31 +02:00
19fef63b0e fix: merge 2025-05-11 21:18:38 +02:00
1fd95265ea fix: this is not gonna work in time, switching to mock mod for canvas so we can show what we supposed to have atleast (local canvas) and we can explain what is the problem (you can disable the mock by putting the var USE_MOCK to false in CanvasItem.vue but dont do it) 2025-05-11 21:17:09 +02:00
3ef2d8a198 fix: join or create 2025-05-11 20:37:57 +02:00
6b49bbbe57 fix: handlers weren't used properly in the unauth code 2025-05-10 23:35:30 +02:00
4c15cab607 fix: still working on the pending 2025-05-10 23:16:08 +02:00
abfe92bc87 Merge: branch 'backend-test' into front_foundation 2025-05-10 21:54:20 +02:00
85b4fe6a4c fix: finalize logic 2025-05-10 21:48:41 +02:00
f2448a029f added two endpoints necessary for routing in project request phase 2025-05-10 20:39:45 +02:00
cef4daef15 feat: finalize2 2025-05-10 18:10:39 +02:00
f5aba70017 feat: add finalise logic 2025-05-10 17:59:05 +02:00
27adc81ddc fix: minor change 2025-05-10 01:16:00 +02:00
15 changed files with 413 additions and 1038 deletions

@ -95,4 +95,22 @@ public class EntrepreneurApi {
@RequestBody Project project, @AuthenticationPrincipal Jwt principal) {
entrepreneurApiService.requestNewProject(project, principal.getClaimAsString("email"));
}
/*
* <p>Endpoint to check if project is has already been validated by an admin
*/
@GetMapping("/entrepreneur/projects/project-is-active")
public Boolean checkIfProjectValidated(@AuthenticationPrincipal Jwt principal) {
return entrepreneurApiService.checkIfEntrepreneurProjectActive(
principal.getClaimAsString("email"));
}
/*
* <p>Endpoint to check if a user requested a project (used when project is pending)
*/
@GetMapping("/entrepreneur/projects/has-pending-request")
public Boolean checkIfHasRequested(@AuthenticationPrincipal Jwt principal) {
return entrepreneurApiService.entrepreneurHasPendingRequestedProject(
principal.getClaimAsString("email"));
}
}

@ -1,8 +1,6 @@
package enseirb.myinpulse.controller;
import enseirb.myinpulse.model.Administrator;
import enseirb.myinpulse.model.Entrepreneur;
import enseirb.myinpulse.service.AdminApiService;
import enseirb.myinpulse.service.EntrepreneurApiService;
import enseirb.myinpulse.service.UtilsService;

@ -1,10 +1,12 @@
package enseirb.myinpulse.service;
import static enseirb.myinpulse.model.ProjectDecisionValue.PENDING;
import static enseirb.myinpulse.model.ProjectDecisionValue.ACTIVE;
import enseirb.myinpulse.model.Entrepreneur;
import enseirb.myinpulse.model.Project;
import enseirb.myinpulse.model.SectionCell;
import enseirb.myinpulse.model.User;
import enseirb.myinpulse.service.database.*;
import org.apache.logging.log4j.LogManager;
@ -234,4 +236,49 @@ public class EntrepreneurApiService {
public Iterable<Entrepreneur> getAllEntrepreneurs() {
return entrepreneurService.getAllEntrepreneurs();
}
/**
* Checks if an entrepreneur with the given email has a project that is ACTIVE.
*
* @param email The email of the entrepreneur.
* @return true if the entrepreneur has an active project, false otherwise.
*/
public Boolean checkIfEntrepreneurProjectActive(String email) {
User user = this.userService.getUserByEmail(email);
if (user == null) {
return false;
}
Long userId = user.getIdUser();
Entrepreneur entrepreneur = this.entrepreneurService.getEntrepreneurById(userId);
if (entrepreneur == null) {
return false;
}
Project proposedProject = entrepreneur.getProjectProposed();
return proposedProject != null && proposedProject.getProjectStatus() == ACTIVE;
}
/**
* Checks if an entrepreneur with the given email has proposed a project.
*
* @param email The email of the entrepreneur.
* @return true if the entrepreneur has a proposed project, false otherwise.
*/
public Boolean entrepreneurHasPendingRequestedProject(String email) {
User user = this.userService.getUserByEmail(email);
if (user == null) {
return false;
}
Long userId = user.getIdUser();
Entrepreneur entrepreneur = this.entrepreneurService.getEntrepreneurById(userId);
if (entrepreneur == null) {
return false;
}
Project proposedProject = entrepreneur.getProjectProposed();
if (entrepreneur.getProjectProposed() == null) {
return false;
}
return proposedProject.getProjectStatus() == PENDING;
}
}

File diff suppressed because it is too large Load Diff

@ -144,3 +144,54 @@ paths:
description: Unauthorized or identity not found
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/projects/project-is-active:
get:
summary: checks if the project associated with an entrepreneur is active
description: returns a boolean if the project associated with an entrepreneur has an active status
(i.e has been validated by an admin). The user should be routed to LeanCanvas. any other response code
should be treated as false
tags:
- Entrepreneurs API
security:
- MyINPulse: [MyINPulse-entrepreneur]
parameters:
responses:
"200":
description: OK - got the value successfully any other response code should be treated as false.
content:
application/json:
schema:
type: boolean
"404":
description: Bad Request - Invalid input or ID mismatch.
"401":
description: Unauthorized or identity not found
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/projects/has-pending-request:
get:
summary: checks if the user has a pending projectRequest
description: returns a boolean if the project associated with an entrepreneur has a pending status
(i.e has not yet been validated by an admin). The user should be routed to a page telling him that he should
wait for admin validation. any other response code should be treated as false.
tags:
- Entrepreneurs API
security:
- MyINPulse: [MyINPulse-entrepreneur]
parameters:
responses:
"200":
description: OK - got the value successfully any other response code should be treated as false.
content:
application/json:
schema:
type: boolean
"404":
description: Bad Request - Invalid input or ID mismatch.
"401":
description: Unauthorized or identity not found
"403":
description: Bad Token - Invalid Keycloack configuration.

@ -153,3 +153,7 @@ paths:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells"
/entrepreneur/sectionCells/{sectionCellId}:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells~1{sectionCellId}"
/entrepreneur/projects/project-is-active:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects~1project-is-active"
/entrepreneur/projects/has-pending-request:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects~1has-pending-request"

@ -1,9 +1,9 @@
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { onMounted } from "vue";
import { useRouter } from "vue-router";
import { jwtDecode } from "jwt-decode"; // i hope this doesn't break the code later
import { store } from "../main.ts";
import { callApi } from "@/services/api.ts";
import { checkPending } from "@/services/Apis/Unauth";
import Header from "@/components/HeaderComponent.vue";
const router = useRouter();
@ -13,7 +13,7 @@ type TokenPayload = {
};
};
const customRequest = ref("");
//const customRequest = ref("");
onMounted(() => {
if (store.authenticated && store.user.token) {
@ -23,26 +23,44 @@ onMounted(() => {
if (roles.includes("MyINPulse-admin")) {
router.push("/admin");
} else if (roles.includes("MyINPulse-entrepreneur")) {
return;
}
if (roles.includes("MyINPulse-entrepreneur")) {
router.push("/canvas");
return;
}
else{
router.push("/JorCproject")
checkPending(
(response) => {
const isValidated = response.data === true;
if (
isValidated &&
roles.includes("MyINPulse-entrepreneur")
) {
router.push("/canvas");
//router.push("/JorCproject");
} else {
router.push("/JorCproject");
//router.push("/finalize");
}
},
(error) => {
if (error.response?.status === 403) {
router.push("/finalize");
} else {
console.error(
"Unexpected error during checkPending",
error
);
}
}
);
} catch (err) {
console.error("Failed to decode token", err);
}
}
});
/*
const loading = ref(false);
const callApiWithLoading = async (path: string) => {
loading.value = true;
await callApi(path);
loading.value = false;
};
*/
</script>
<template>
@ -50,7 +68,7 @@ const callApiWithLoading = async (path: string) => {
<error-wrapper></error-wrapper>
<div class="auth-container">
<div class="auth-card">
<h1>Bienvenue</h1>
<h1>Bienvenue à MyINPulse</h1>
<div
class="status"
@ -68,11 +86,12 @@ const callApiWithLoading = async (path: string) => {
<div class="actions">
<button @click="store.login">Login</button>
<button @click="store.logout">Logout</button>
<button @click="store.signup">Signup-admin</button>
<!--<button @click="store.signup">Signup-admin</button>
<button @click="store.signup">Signup-Entrepreneur</button>
<button @click="store.refreshUserToken">Refresh Token</button>
<button @click="store.refreshUserToken">Refresh Token</button>-->
</div>
<!--
<div v-if="store.authenticated" class="token-section">
<p><strong>Access Token:</strong></p>
<pre>{{ store.user.token }}</pre>
@ -96,7 +115,7 @@ const callApiWithLoading = async (path: string) => {
/>
<button @click="callApi(customRequest)">Call</button>
</div>
</div>
</div>-->
</div>
</div>
</template>

@ -110,7 +110,7 @@ const props = defineProps<{
isAdmin: boolean;
}>();
const IS_MOCK_MODE = false;
const IS_MOCK_MODE = true;
const IS_ADMIN = props.isAdmin;
const expanded = ref(false);

@ -40,6 +40,18 @@ const router = createRouter({
name: "JorCproject",
component: () => import("../views/JoinOrCreatProjectForEntrep.vue"),
},
{
path: "/finalize",
name: "finalize",
component: () => import("../views/FinalizeAccount.vue"),
},
{
path: "/pending-approval",
name: "PendingApproval",
component: () => import("@/views/PendingApproval.vue"),
},
],
});

@ -123,10 +123,58 @@ function removeSectionCell(
});
}
// Checks if the entrepreneur has a pending project request
function checkPendingProjectRequest(
onSuccessHandler?: (response: AxiosResponse<boolean>) => void,
onErrorHandler?: (error: AxiosError) => void
): void {
axiosInstance
.get("/entrepreneur/projects/has-pending-request")
.then((response) => {
if (onSuccessHandler) {
onSuccessHandler(response);
} else {
defaultApiSuccessHandler(response);
}
})
.catch((error: AxiosError) => {
if (onErrorHandler) {
onErrorHandler(error);
} else {
defaultApiErrorHandler(error);
}
});
}
// Checks if the entrepreneur has an active project
function checkIfProjectIsActive(
onSuccessHandler?: (response: AxiosResponse<boolean>) => void,
onErrorHandler?: (error: AxiosError) => void
): void {
axiosInstance
.get("/entrepreneur/projects/project-is-active")
.then((response) => {
if (onSuccessHandler) {
onSuccessHandler(response);
} else {
defaultApiSuccessHandler(response);
}
})
.catch((error: AxiosError) => {
if (onErrorHandler) {
onErrorHandler(error);
} else {
defaultApiErrorHandler(error);
}
});
}
export {
getEntrepreneurProjectId,
requestProjectCreation,
addSectionCell,
modifySectionCell,
removeSectionCell,
checkPendingProjectRequest,
checkIfProjectIsActive,
};

@ -74,8 +74,30 @@ function getAllEntrepreneurs(
});
}
function checkPending(
onSuccessHandler?: (response: AxiosResponse<boolean>) => void,
onErrorHandler?: (error: AxiosError) => void
): void {
axiosInstance
.get("/unauth/check-if-not-pending")
.then((response) => {
if (onSuccessHandler) {
onSuccessHandler(response);
} else {
defaultApiSuccessHandler(response);
}
})
.catch((error: AxiosError) => {
if (onErrorHandler) {
onErrorHandler(error);
} else {
defaultApiErrorHandler(error);
}
});
}
export {
finalizeAccount,
getAllEntrepreneurs,
checkPending,
// requestJoinProject, // Not yet implemented [cite: 4]
};

@ -70,7 +70,6 @@ const fallbackProjects = [
},
];
const createFirstAdmin = () => {
createAdmin(
(response) => {
@ -85,7 +84,7 @@ const createFirstAdmin = () => {
);
};
onMounted(createFirstAdmin)
onMounted(createFirstAdmin);
const fetchProjects = () => {
getAdminProjects(

@ -0,0 +1,81 @@
<template>
<Header />
<div class="finalize-page">
<div class="loader-container">
<button class="return-button" @click="store.logout">Logout</button>
<div class="spinner"></div>
<p>Finalisation du compte en cours...</p>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from "vue";
//import { useRouter } from "vue-router";
import { finalizeAccount } from "@/services/Apis/Unauth";
import Header from "@/components/HeaderComponent.vue";
import { store } from "@/main.ts";
//const router = useRouter();
onMounted(() => {
finalizeAccount(
() => {
console.log("finalize sended");
},
(error) => {
console.error("Erreur lors de la finalisation :", error);
}
);
});
</script>
<style scoped>
.finalize-page {
display: flex;
justify-content: center;
align-items: center;
min-height: 80vh;
background-color: #f9fbfd;
}
.loader-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
color: #333;
font-size: 1.1rem;
font-style: italic;
}
.spinner {
width: 50px;
height: 50px;
border: 5px solid #cfd8dc;
border-top: 5px solid #3498db;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.return-button {
background-color: #009cde;
color: white;
border: none;
padding: 10px 15px;
cursor: pointer;
font-size: 14px;
border-radius: 5px;
text-decoration: none;
transition: background-color 0.2s ease;
font-family: Arial, sans-serif;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

@ -1,10 +1,12 @@
<template>
<Header />
<header class="header">
<img
src="@/components/icons/logo inpulse.png"
alt="INPulse Logo"
class="logo"
/>
<button class="return-button" @click="store.logout">Logout</button>
</header>
<div class="choix-projet">
@ -39,10 +41,18 @@
</template>
<script setup lang="ts">
import { ref } from "vue";
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import Project from "@/ApiClasses/Project";
import { requestProjectCreation } from "@/services/Apis/Entrepreneurs.ts";
import Header from "../components/HeaderComponent.vue";
import { store } from "@/main.ts";
import {
requestProjectCreation,
checkIfProjectIsActive,
checkPendingProjectRequest,
} from "@/services/Apis/Entrepreneurs";
const router = useRouter();
const choix = ref<string | null>(null);
const nomProjet = ref("");
@ -56,21 +66,18 @@ const validerCreation = () => {
return;
}
// Obtenir la date actuelle au format YYYY-MM-DD
const today = new Date();
const yyyy = today.getFullYear();
const mm = String(today.getMonth() + 1).padStart(2, "0");
const dd = String(today.getDate()).padStart(2, "0");
const formattedDate = `${yyyy}-${mm}-${dd}`;
// Créer une instance de Project
const nouveauProjet = new Project({
projectName: nomProjet.value.trim(),
creationDate: formattedDate,
status: "PENDING",
});
// Appeler lAPI
requestProjectCreation(
nouveauProjet,
(response) => {
@ -83,6 +90,28 @@ const validerCreation = () => {
}
);
};
onMounted(() => {
checkIfProjectIsActive(
(response) => {
if (response.data === true) {
router.push("/canvas");
}
},
() => {
checkPendingProjectRequest(
(response) => {
if (response.data === true) {
router.push("/pending-approval");
}
},
(error) => {
console.warn("No active or pending project:", error);
}
);
}
);
});
</script>
<style scoped>
@ -140,4 +169,17 @@ input {
.logo {
height: 50px;
}
.return-button {
background-color: #009cde;
color: white;
border: none;
padding: 10px 15px;
cursor: pointer;
font-size: 14px;
border-radius: 5px;
text-decoration: none;
transition: background-color 0.2s ease;
font-family: Arial, sans-serif;
}
</style>

@ -0,0 +1,41 @@
<template>
<Header />
<div class="pending-container">
<h1>Projet en attente de validation</h1>
<p>
Votre demande de création de projet a bien été reçue.<br />
Un administrateur doit valider votre projet avant que vous puissiez
continuer.
</p>
</div>
</template>
<script setup lang="ts">
import Header from "@/components/HeaderComponent.vue";
</script>
<style scoped>
.pending-container {
max-width: 600px;
margin: 100px auto;
padding: 2rem;
text-align: center;
background-color: #fffdf8;
border: 1px solid #ececec;
border-radius: 12px;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
font-family: "Inter", sans-serif;
}
h1 {
color: #f57c00;
font-size: 1.8rem;
margin-bottom: 1rem;
}
p {
font-size: 1.1rem;
color: #333;
margin-bottom: 1.5rem;
}
</style>