From dbf06d8c64201366410a87f65591db22458a66f7 Mon Sep 17 00:00:00 2001 From: Pierre Tellier <piair338@gmail.com> Date: Fri, 7 Feb 2025 23:06:59 +0100 Subject: [PATCH 1/7] feat: configured authentification --- Makefile | 8 +- .../myinpulse/MyinpulseApplication.java | 43 ++------ .../WebSecurityCustomConfiguration.java | 50 ++++++++++ .../security/KeycloakJwtRolesConverter.java | 98 +++++++++++++++++++ .../src/main/resources/application.properties | 3 +- front/MyINPulse-front/src/helpers.ts | 26 ----- 6 files changed, 161 insertions(+), 67 deletions(-) create mode 100644 MyINPulse-back/src/main/java/enseirb/myinpulse/config/WebSecurityCustomConfiguration.java create mode 100644 MyINPulse-back/src/main/java/enseirb/myinpulse/security/KeycloakJwtRolesConverter.java delete mode 100644 front/MyINPulse-front/src/helpers.ts diff --git a/Makefile b/Makefile index a5606be..6944ef4 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ help: clean: @cp config/prod.docker-compose.yaml docker-compose.yaml - @docker compose down 2> /dev/null + @docker compose down @rm -f docker-compose.yaml @rm -f .env @rm -f front/MyINPulse-front/.env @@ -21,14 +21,14 @@ dev-front: clean vite @cp config/frontdev.front.env front/MyINPulse-front/.env @cp config/frontdev.main.env .env @cp config/frontdev.docker-compose.yaml docker-compose.yaml - @docker compose up -d + @docker compose up -d --build @cd ./front/MyINPulse-front/ && npm run dev prod: clean @cp config/prod.front.env front/MyINPulse-front/.env @cp config/prod.main.env .env @cp config/frontdev.docker-compose.yaml docker-compose.yaml - @docker compose up -d + @docker compose up -d --build @@ -36,6 +36,6 @@ dev-back: @cp config/backdev.front.env front/MyINPulse-front/.env @cp config/backdev.main.env .env @cp config/backdev.docker-compose.yaml docker-compose.yaml - @docker compose up -d + @docker compose up -d --build @echo "cd MyINPulse-back" @echo "./gradlew bootRun --args='--server.port=8081'" \ No newline at end of file diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/MyinpulseApplication.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/MyinpulseApplication.java index b4b5e7b..23c2f28 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/MyinpulseApplication.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/MyinpulseApplication.java @@ -1,22 +1,22 @@ package enseirb.myinpulse; +import enseirb.myinpulse.security.KeycloakJwtRolesConverter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; -import org.springframework.security.config.Customizer; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.builders.WebSecurity; -import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer; +import org.springframework.security.oauth2.jwt.*; import org.springframework.security.web.SecurityFilterChain; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import java.util.Arrays; +import java.util.*; +import java.util.stream.Collectors; -import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope; +import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole; @SpringBootApplication public class MyinpulseApplication { @@ -25,34 +25,5 @@ public class MyinpulseApplication { SpringApplication.run(MyinpulseApplication.class, args); } - // CORS configuration - // TODO: make sure to only accept our own domains - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList("*")); - configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS")); - configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", - "x-auth-token")); // Do not remove, this fixes the CORS errors when unauthenticated - //configuration.setExposedHeaders(Arrays.asList("x-auth-token")); - UrlBasedCorsConfigurationSource source = new - UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", configuration); - - return source; - } - - // TODO: add unauthenticated endpoint with server status - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/random2").access(hasScope("contacts")) - .requestMatchers("/getUserInfo").access(hasScope("messages")) - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults())); - return http.build(); - } } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/config/WebSecurityCustomConfiguration.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/config/WebSecurityCustomConfiguration.java new file mode 100644 index 0000000..14c46b3 --- /dev/null +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/config/WebSecurityCustomConfiguration.java @@ -0,0 +1,50 @@ +package enseirb.myinpulse.config; + +import enseirb.myinpulse.security.KeycloakJwtRolesConverter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.Arrays; +import java.util.List; + +import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole; + +@Configuration +public class WebSecurityCustomConfiguration { + // CORS configuration + // TODO: make sure to only accept our own domains + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(List.of("*")); + configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS")); + configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", + "x-auth-token")); // Do not remove, this fixes the CORS errors when unauthenticated + UrlBasedCorsConfigurationSource source = new + UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + + return source; + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/random2").access(hasRole("REALM_MyINPulse-entrepreneur")) + .requestMatchers("/random").access(hasRole("REALM_MyINPulse-admin")) + .requestMatchers("/random3").permitAll() + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2 -> oauth2 + .jwt(jwt -> jwt. + jwtAuthenticationConverter(new KeycloakJwtRolesConverter()))); + return http.build(); + + } +} \ No newline at end of file diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/security/KeycloakJwtRolesConverter.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/security/KeycloakJwtRolesConverter.java new file mode 100644 index 0000000..fafbef5 --- /dev/null +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/security/KeycloakJwtRolesConverter.java @@ -0,0 +1,98 @@ +package enseirb.myinpulse.security; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toSet; + + +public class KeycloakJwtRolesConverter implements Converter<Jwt, AbstractAuthenticationToken> { + /** + * Prefix used for realm level roles. + */ + public static final String PREFIX_REALM_ROLE = "ROLE_REALM_"; + /** + * Prefix used in combination with the resource (client) name for resource level roles. + */ + public static final String PREFIX_RESOURCE_ROLE = "ROLE_"; + + /** + * Name of the claim containing the realm level roles + */ + private static final String CLAIM_REALM_ACCESS = "realm_access"; + /** + * Name of the claim containing the resources (clients) the user has access to. + */ + private static final String CLAIM_RESOURCE_ACCESS = "resource_access"; + /** + * Name of the claim containing roles. (Applicable to realm and resource level.) + */ + private static final String CLAIM_ROLES = "roles"; + + @Override + public AbstractAuthenticationToken convert(Jwt source) + { + return new JwtAuthenticationToken(source, Stream.concat(new JwtGrantedAuthoritiesConverter().convert(source) + .stream(), TEMPORARNAME(source).stream()) + .collect(toSet())); + } + + /** + * Extracts the realm and resource level roles from a JWT token distinguishing between them using prefixes. + */ + public Collection<GrantedAuthority> TEMPORARNAME(Jwt jwt) { + // Collection that will hold the extracted roles + Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>(); + + // Realm roles + // Get the part of the access token that holds the roles assigned on realm level + Map<String, Collection<String>> realmAccess = jwt.getClaim(CLAIM_REALM_ACCESS); + + // Verify that the claim exists and is not empty + if (realmAccess != null && !realmAccess.isEmpty()) { + // From the realm_access claim get the roles + Collection<String> roles = realmAccess.get(CLAIM_ROLES); + // Check if any roles are present + if (roles != null && !roles.isEmpty()) { + // Iterate of the roles and add them to the granted authorities + Collection<GrantedAuthority> realmRoles = roles.stream() + // Prefix all realm roles with "ROLE_realm_" + .map(role -> new SimpleGrantedAuthority(PREFIX_REALM_ROLE + role)) + .collect(Collectors.toList()); + grantedAuthorities.addAll(realmRoles); + } + } + + // Resource (client) roles + // A user might have access to multiple resources all containing their own roles. Therefore, it is a map of + // resource each possibly containing a "roles" property. + Map<String, Map<String, Collection<String>>> resourceAccess = jwt.getClaim(CLAIM_RESOURCE_ACCESS); + + // Check if resources are assigned + if (resourceAccess != null && !resourceAccess.isEmpty()) { + // Iterate of all the resources + resourceAccess.forEach((resource, resourceClaims) -> { + // Iterate of the "roles" claim inside the resource claims + resourceClaims.get(CLAIM_ROLES).forEach( + // Add the role to the granted authority prefixed with ROLE_ and the name of the resource + role -> grantedAuthorities.add(new SimpleGrantedAuthority(PREFIX_RESOURCE_ROLE + resource + "_" + role)) + ); + }); + } + + return grantedAuthorities; + } + + +} diff --git a/MyINPulse-back/src/main/resources/application.properties b/MyINPulse-back/src/main/resources/application.properties index 9264594..6d6825f 100644 --- a/MyINPulse-back/src/main/resources/application.properties +++ b/MyINPulse-back/src/main/resources/application.properties @@ -1,3 +1,4 @@ spring.application.name=myinpulse spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:7080/realms/test/protocol/openid-connect/certs -spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/test \ No newline at end of file +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/test +logging.level.org.springframework.security=DEBUG diff --git a/front/MyINPulse-front/src/helpers.ts b/front/MyINPulse-front/src/helpers.ts deleted file mode 100644 index 0bd894a..0000000 --- a/front/MyINPulse-front/src/helpers.ts +++ /dev/null @@ -1,26 +0,0 @@ -import axios from "axios"; - -const backendUrl = "http://localhost:8080/" - -// TODO: spawn a error modal -function defaultApiErrorHandler(err: String){ - console.log(err) -} - -function defaultApiSuccessHandler(response: () => void){ - console.log(response) -} -/* -function callApi(endpoint: string, onSuccessHandler: () => void, onErrorHandler: (err: String) => void): void { - console.log("callApi: "+ endpoint) - axios.get(backendUrl + endpoint).then( - onSuccessHandler == null ? defaultApiSuccessHandler : onSuccessHandler - ).catch( - (err) => { - onErrorHandler == null ? defaultApiErrorHandler(err): onErrorHandler(err); - } - ) -} - -export {callApi} - */ \ No newline at end of file From 3de90295bd7902cb011dc4f3cb2b398f0dc481a3 Mon Sep 17 00:00:00 2001 From: Pierre Tellier <piair338@gmail.com> Date: Sat, 8 Feb 2025 12:08:02 +0100 Subject: [PATCH 2/7] fix: changed configs --- config/backdev.front.env | 1 + config/frontdev.front.env | 3 +- config/prod.front.env | 1 + .../src/components/HelloWorld.vue | 41 -------- .../src/components/TheWelcome.vue | 94 ------------------- .../src/components/WelcomeItem.vue | 87 ----------------- .../src/components/errorModal.vue | 94 +++++++++++++++++++ .../src/components/icons/IconCommunity.vue | 7 -- .../components/icons/IconDocumentation.vue | 7 -- .../src/components/icons/IconEcosystem.vue | 7 -- .../src/components/icons/IconSupport.vue | 7 -- .../src/components/icons/IconTooling.vue | 19 ---- front/MyINPulse-front/src/main.ts | 74 +++++---------- .../src/router/{index.ts => router.ts} | 0 front/MyINPulse-front/src/services/api.ts | 56 +++++++++-- .../src/services/popupDisplayer.ts | 17 ++++ .../MyINPulse-front/src/views/errorModal.vue | 32 ------- .../src/views/errorWrapper.vue | 11 +++ .../src/views/{Home.vue => test.vue} | 28 ++++-- 19 files changed, 218 insertions(+), 368 deletions(-) delete mode 100644 front/MyINPulse-front/src/components/HelloWorld.vue delete mode 100644 front/MyINPulse-front/src/components/TheWelcome.vue delete mode 100644 front/MyINPulse-front/src/components/WelcomeItem.vue create mode 100644 front/MyINPulse-front/src/components/errorModal.vue delete mode 100644 front/MyINPulse-front/src/components/icons/IconCommunity.vue delete mode 100644 front/MyINPulse-front/src/components/icons/IconDocumentation.vue delete mode 100644 front/MyINPulse-front/src/components/icons/IconEcosystem.vue delete mode 100644 front/MyINPulse-front/src/components/icons/IconSupport.vue delete mode 100644 front/MyINPulse-front/src/components/icons/IconTooling.vue rename front/MyINPulse-front/src/router/{index.ts => router.ts} (100%) create mode 100644 front/MyINPulse-front/src/services/popupDisplayer.ts delete mode 100644 front/MyINPulse-front/src/views/errorModal.vue create mode 100644 front/MyINPulse-front/src/views/errorWrapper.vue rename front/MyINPulse-front/src/views/{Home.vue => test.vue} (58%) diff --git a/config/backdev.front.env b/config/backdev.front.env index 9db91ea..27cf54e 100644 --- a/config/backdev.front.env +++ b/config/backdev.front.env @@ -2,3 +2,4 @@ VITE_KEYCLOAK_URL=http://localhost:7080 VITE_KEYCLOAK_CLIENT_ID=myinpulse VITE_KEYCLOAK_REALM=test VITE_APP_URL=http://localhost:8080 +VITE_BACKEND_URL=http://localhost:8081/ diff --git a/config/frontdev.front.env b/config/frontdev.front.env index d6b7016..5eba221 100644 --- a/config/frontdev.front.env +++ b/config/frontdev.front.env @@ -1,4 +1,5 @@ VITE_KEYCLOAK_URL=http://localhost:7080 -VITE_KEYCLOAK_CLIENT_ID=myinpulse +VITE_KEYCLOAK_CLIENT_ID=myinpulse-dev VITE_KEYCLOAK_REALM=test VITE_APP_URL=http://localhost:5173 +VITE_BACKEND_URL=http://localhost:8081/ diff --git a/config/prod.front.env b/config/prod.front.env index d5007dc..cb42a37 100644 --- a/config/prod.front.env +++ b/config/prod.front.env @@ -2,3 +2,4 @@ VITE_KEYCLOAK_URL=https://0549cd63f912d5dc9b31278d6f.eirb.fr VITE_KEYCLOAK_CLIENT_ID=myinpulse-eirb VITE_KEYCLOAK_REALM=test VITE_APP_URL=https://0549cd63f912d5dc9b31278d6f.piair.dev +VITE_BACKEND_URL=http://TODO/ diff --git a/front/MyINPulse-front/src/components/HelloWorld.vue b/front/MyINPulse-front/src/components/HelloWorld.vue deleted file mode 100644 index d174cf8..0000000 --- a/front/MyINPulse-front/src/components/HelloWorld.vue +++ /dev/null @@ -1,41 +0,0 @@ -<script setup lang="ts"> -defineProps<{ - msg: string -}>() -</script> - -<template> - <div class="greetings"> - <h1 class="green">{{ msg }}</h1> - <h3> - You’ve successfully created a project with - <a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> + - <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next? - </h3> - </div> -</template> - -<style scoped> -h1 { - font-weight: 500; - font-size: 2.6rem; - position: relative; - top: -10px; -} - -h3 { - font-size: 1.2rem; -} - -.greetings h1, -.greetings h3 { - text-align: center; -} - -@media (min-width: 1024px) { - .greetings h1, - .greetings h3 { - text-align: left; - } -} -</style> diff --git a/front/MyINPulse-front/src/components/TheWelcome.vue b/front/MyINPulse-front/src/components/TheWelcome.vue deleted file mode 100644 index 674b490..0000000 --- a/front/MyINPulse-front/src/components/TheWelcome.vue +++ /dev/null @@ -1,94 +0,0 @@ -<script setup lang="ts"> -import WelcomeItem from './WelcomeItem.vue' -import DocumentationIcon from './icons/IconDocumentation.vue' -import ToolingIcon from './icons/IconTooling.vue' -import EcosystemIcon from './icons/IconEcosystem.vue' -import CommunityIcon from './icons/IconCommunity.vue' -import SupportIcon from './icons/IconSupport.vue' - -const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md') -</script> - -<template> - <WelcomeItem> - <template #icon> - <DocumentationIcon /> - </template> - <template #heading>Documentation</template> - - Vue’s - <a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a> - provides you with all information you need to get started. - </WelcomeItem> - - <WelcomeItem> - <template #icon> - <ToolingIcon /> - </template> - <template #heading>Tooling</template> - - This project is served and bundled with - <a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The - recommended IDE setup is - <a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> - + - <a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If - you need to test your components and web pages, check out - <a href="https://vitest.dev/" target="_blank" rel="noopener">Vite</a> - and - <a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> - / - <a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>. - - <br /> - - More instructions are available in - <a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a - >. - </WelcomeItem> - - <WelcomeItem> - <template #icon> - <EcosystemIcon /> - </template> - <template #heading>Ecosystem</template> - - Get official tools and libraries for your project: - <a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>, - <a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>, - <a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and - <a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If - you need more resources, we suggest paying - <a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a> - a visit. - </WelcomeItem> - - <WelcomeItem> - <template #icon> - <CommunityIcon /> - </template> - <template #heading>Community</template> - - Got stuck? Ask your question on - <a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a> - (our official Discord server), or - <a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener" - >StackOverflow</a - >. You should also follow the official - <a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a> - Bluesky account or the - <a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a> - X account for latest news in the Vue world. - </WelcomeItem> - - <WelcomeItem> - <template #icon> - <SupportIcon /> - </template> - <template #heading>Support Vue</template> - - As an independent project, Vue relies on community backing for its sustainability. You can help - us by - <a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>. - </WelcomeItem> -</template> diff --git a/front/MyINPulse-front/src/components/WelcomeItem.vue b/front/MyINPulse-front/src/components/WelcomeItem.vue deleted file mode 100644 index 6d7086a..0000000 --- a/front/MyINPulse-front/src/components/WelcomeItem.vue +++ /dev/null @@ -1,87 +0,0 @@ -<template> - <div class="item"> - <i> - <slot name="icon"></slot> - </i> - <div class="details"> - <h3> - <slot name="heading"></slot> - </h3> - <slot></slot> - </div> - </div> -</template> - -<style scoped> -.item { - margin-top: 2rem; - display: flex; - position: relative; -} - -.details { - flex: 1; - margin-left: 1rem; -} - -i { - display: flex; - place-items: center; - place-content: center; - width: 32px; - height: 32px; - - color: var(--color-text); -} - -h3 { - font-size: 1.2rem; - font-weight: 500; - margin-bottom: 0.4rem; - color: var(--color-heading); -} - -@media (min-width: 1024px) { - .item { - margin-top: 0; - padding: 0.4rem 0 1rem calc(var(--section-gap) / 2); - } - - i { - top: calc(50% - 25px); - left: -26px; - position: absolute; - border: 1px solid var(--color-border); - background: var(--color-background); - border-radius: 8px; - width: 50px; - height: 50px; - } - - .item:before { - content: ' '; - border-left: 1px solid var(--color-border); - position: absolute; - left: 0; - bottom: calc(50% + 25px); - height: calc(50% - 25px); - } - - .item:after { - content: ' '; - border-left: 1px solid var(--color-border); - position: absolute; - left: 0; - top: calc(50% + 25px); - height: calc(50% - 25px); - } - - .item:first-of-type:before { - display: none; - } - - .item:last-of-type:after { - display: none; - } -} -</style> diff --git a/front/MyINPulse-front/src/components/errorModal.vue b/front/MyINPulse-front/src/components/errorModal.vue new file mode 100644 index 0000000..0d7dd57 --- /dev/null +++ b/front/MyINPulse-front/src/components/errorModal.vue @@ -0,0 +1,94 @@ +<script setup lang="ts"> +const props = defineProps(['data']); +</script> + +<template> + <div :class='["red", "yellow", "blue", "green"][data.type]' class="error-modal"> + <p>{{["Erreur :(", "Warning :|", "Info :)", "Succes ;)"][data.type]}}</p> + <p>{{data.errorMessage}}</p> + <div class="loading" :class='["red-loader", "yellow-loader", "blue-loader", "green-loader"][data.type]'></div> + </div> +</template> + +<style scoped> + .error-modal{ + margin-bottom: 1em; + padding: 1em; + border-radius: 1em; + text-align: center; + overflow: hidden; + position: relative; + animation: disappear 5s linear forwards; + } + + .red{ + background-color: #ee6055; + color: white; + } + .red-loader { + background-color: #fa8383; + } + + .yellow{ + background-color: #FF9D23; + color: white; + } + .yellow-loader{ + background-color: #ffbf81; + } + + .blue { + background-color: #809bce; + color: white; + } + .blue-loader{ + background-color: #95b8d1; + } + + .green { + background-color: green; + color: white; + } + .green-loader { + background-color: darkgreen; + } + + .loading { + box-sizing: border-box; + position: absolute; + padding: 0; + bottom: 0; + left: 0; + height: 1em; + width: 0; + animation: loading 4s linear forwards; + } + + /* Animation for the loading bar */ + @keyframes loading { + 0% { + width: 100%; + } + 100% { + width: 0; + } + } + + @keyframes disappear { + 0% { + height: 100px; + padding: 1em; + margin: 1em; + } + 80% { + height: 100px; + padding: 1em; + margin: 1em; + } + 100% { + height: 0; + margin: 0; + padding: 0; + } + } +</style> \ No newline at end of file diff --git a/front/MyINPulse-front/src/components/icons/IconCommunity.vue b/front/MyINPulse-front/src/components/icons/IconCommunity.vue deleted file mode 100644 index 2dc8b05..0000000 --- a/front/MyINPulse-front/src/components/icons/IconCommunity.vue +++ /dev/null @@ -1,7 +0,0 @@ -<template> - <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor"> - <path - d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z" - /> - </svg> -</template> diff --git a/front/MyINPulse-front/src/components/icons/IconDocumentation.vue b/front/MyINPulse-front/src/components/icons/IconDocumentation.vue deleted file mode 100644 index 6d4791c..0000000 --- a/front/MyINPulse-front/src/components/icons/IconDocumentation.vue +++ /dev/null @@ -1,7 +0,0 @@ -<template> - <svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor"> - <path - d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z" - /> - </svg> -</template> diff --git a/front/MyINPulse-front/src/components/icons/IconEcosystem.vue b/front/MyINPulse-front/src/components/icons/IconEcosystem.vue deleted file mode 100644 index c3a4f07..0000000 --- a/front/MyINPulse-front/src/components/icons/IconEcosystem.vue +++ /dev/null @@ -1,7 +0,0 @@ -<template> - <svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor"> - <path - d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z" - /> - </svg> -</template> diff --git a/front/MyINPulse-front/src/components/icons/IconSupport.vue b/front/MyINPulse-front/src/components/icons/IconSupport.vue deleted file mode 100644 index 7452834..0000000 --- a/front/MyINPulse-front/src/components/icons/IconSupport.vue +++ /dev/null @@ -1,7 +0,0 @@ -<template> - <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor"> - <path - d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z" - /> - </svg> -</template> diff --git a/front/MyINPulse-front/src/components/icons/IconTooling.vue b/front/MyINPulse-front/src/components/icons/IconTooling.vue deleted file mode 100644 index 660598d..0000000 --- a/front/MyINPulse-front/src/components/icons/IconTooling.vue +++ /dev/null @@ -1,19 +0,0 @@ -<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license--> -<template> - <svg - xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - aria-hidden="true" - role="img" - class="iconify iconify--mdi" - width="24" - height="24" - preserveAspectRatio="xMidYMid meet" - viewBox="0 0 24 24" - > - <path - d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z" - fill="currentColor" - ></path> - </svg> -</template> diff --git a/front/MyINPulse-front/src/main.ts b/front/MyINPulse-front/src/main.ts index e33d5c1..198447d 100644 --- a/front/MyINPulse-front/src/main.ts +++ b/front/MyINPulse-front/src/main.ts @@ -1,69 +1,37 @@ import { createApp } from 'vue' import App from './App.vue' import router from './router' -//import VueKeyCloak from '@dsb-norge/vue-keycloak-js' -//import { useKeycloak } from '@dsb-norge/vue-keycloak-js' -//import axios from 'axios' import {createPinia} from "pinia"; import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; import AuthStorePlugin from './plugins/authStore'; import keycloakService from './services/keycloak'; +import axios from "axios"; +import {useAuthStore} from "@/stores/authStore.ts"; +let store: any; keycloakService.CallInit(() => { + try { + const app = createApp(App) - const app = createApp(App) + // Setup pinia store, allowing user to keep logged in status after refresh + const pinia = createPinia(); + pinia.use(piniaPluginPersistedstate); + app.use(pinia); + app.use(AuthStorePlugin, { pinia }); + store = useAuthStore(); + app.use(router) - -// Setup pinia store, allowing user to keep logged in status after refresh - const pinia = createPinia(); - pinia.use(piniaPluginPersistedstate); - - app.use(pinia); - app.use(AuthStorePlugin, { pinia }); - app.use(router) - app.mount('#app'); + app.mount('#app'); + } catch (e) { + console.error("Error while initiating Keycloak.") + console.error(e) + createApp(App).mount('#app'); + } }) -createApp(App).mount('#app'); - -// TODO: fix the comment -/* -function tokenInterceptor () { - axios.interceptors.request.use(config => { - const keycloak = useKeycloak() - if (keycloak.authenticated) { - // Note that this is a simple example. - // you should be careful not to leak tokens to third parties. - // in this example the token is added to all usage of axios. - config.headers.Authorization = `Bearer ${keycloak.token}` - } - return config - }, error => { - console.error("tokenInterceptor: Rejected") - return Promise.reject(error) - }) -} -*/ - -/* -app.use(VueKeyCloak,{ - onReady: (keycloak) => { - console.log("Ready !") - tokenInterceptor() - }, - init: { - onLoad: 'login-required', - checkLoginIframe: false, - - }, - - config: { - realm: 'test', - url: 'http://localhost:7080', - clientId: 'myinpulse' - } -} ); -*/ + + +export {store}; \ No newline at end of file diff --git a/front/MyINPulse-front/src/router/index.ts b/front/MyINPulse-front/src/router/router.ts similarity index 100% rename from front/MyINPulse-front/src/router/index.ts rename to front/MyINPulse-front/src/router/router.ts diff --git a/front/MyINPulse-front/src/services/api.ts b/front/MyINPulse-front/src/services/api.ts index eb75333..d29273a 100644 --- a/front/MyINPulse-front/src/services/api.ts +++ b/front/MyINPulse-front/src/services/api.ts @@ -1,13 +1,55 @@ -// file: src/services/api.js - import axios from "axios"; +import {store} from "@/main.ts"; -// Creating an instance for axios to be used by the token interceptor service -const instance = axios.create({ - baseURL: `${import.meta.env.VITE_BE_API_URL}/api`, +const axiosInstance = axios.create({ + baseURL: import.meta.env.VITE_BACKEND_URL, headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, }); -export default instance; \ No newline at end of file +axiosInstance.interceptors.response.use( + response => response, // Directly return successful responses. + async error => { + const originalRequest = error.config; + if (error.response.status === 401 && !originalRequest._retry && store.authenticated) { + originalRequest._retry = true; // Mark the request as retried to avoid infinite loops. + try { + await store.refreshUserToken(); + // Update the authorization header with the new access token. + axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${store.user.token}`; + return axiosInstance(originalRequest); // Retry the original request with the new access token. + } catch (refreshError) { + // Handle refresh token errors by clearing stored tokens and redirecting to the login page. + console.error('Token refresh failed:', refreshError); + localStorage.removeItem('accessToken'); + localStorage.removeItem('refreshToken'); + window.location.href = '/login'; + return Promise.reject(refreshError); + } + } + return Promise.reject(error); // For all other errors, return the error as is. + } +); + +// TODO: spawn a error modal +function defaultApiErrorHandler(err: String){ + console.log(err) +} + +function defaultApiSuccessHandler(response: () => void){ + console.log(response) +} +function callApi(endpoint: string, onSuccessHandler?: any, onErrorHandler?: any): void { + axiosInstance.get(endpoint).then( + onSuccessHandler == null ? defaultApiSuccessHandler : onSuccessHandler + ).catch( + (err) => { + onErrorHandler == null ? defaultApiErrorHandler(err): onErrorHandler(err); + throw err; + } + ) +} + + +export {callApi} \ No newline at end of file diff --git a/front/MyINPulse-front/src/services/popupDisplayer.ts b/front/MyINPulse-front/src/services/popupDisplayer.ts new file mode 100644 index 0000000..61d02b9 --- /dev/null +++ b/front/MyINPulse-front/src/services/popupDisplayer.ts @@ -0,0 +1,17 @@ +import {ref} from "vue"; +enum errorType {Error, Warning} + +function addNewError(errorMessage: string, timeout?: number, type?: errorType){ + if (timeout == null){ + timeout = 5000; + } + if (type == null){ + type = Error; + } + errorList.value.push({errorMessage: errorMessage, timeout: timeout, type: type}) + setTimeout(() => errorList.value.pop(errorMessage), 5000) +} + +const errorList = ref([]) + +export {addNewError, errorList} \ No newline at end of file diff --git a/front/MyINPulse-front/src/views/errorModal.vue b/front/MyINPulse-front/src/views/errorModal.vue deleted file mode 100644 index 92722f1..0000000 --- a/front/MyINPulse-front/src/views/errorModal.vue +++ /dev/null @@ -1,32 +0,0 @@ -<script lang="ts"> -import {defineComponent} from 'vue' - -export default defineComponent({ - name: "errorModal", - props: { - error: String - }, - data(){ - return { - } - } -}) -</script> - -<template> -<div class="error"> - <p>Erreur :(</p> - <p>{{error}}</p> -</div> -</template> - -<style scoped> - .error{ - background-color: darkred; - color: white; - padding: 1em; - border-radius: 0.5em; - text-align: center; - position: absolute; - } -</style> \ No newline at end of file diff --git a/front/MyINPulse-front/src/views/errorWrapper.vue b/front/MyINPulse-front/src/views/errorWrapper.vue new file mode 100644 index 0000000..96c0baf --- /dev/null +++ b/front/MyINPulse-front/src/views/errorWrapper.vue @@ -0,0 +1,11 @@ +<script setup lang="ts"> + +</script> + +<template> + +</template> + +<style scoped> + +</style> \ No newline at end of file diff --git a/front/MyINPulse-front/src/views/Home.vue b/front/MyINPulse-front/src/views/test.vue similarity index 58% rename from front/MyINPulse-front/src/views/Home.vue rename to front/MyINPulse-front/src/views/test.vue index 7fbe869..e879aef 100644 --- a/front/MyINPulse-front/src/views/Home.vue +++ b/front/MyINPulse-front/src/views/test.vue @@ -1,7 +1,16 @@ <script setup lang="ts"> -import {useAuthStore} from "@/stores/authStore"; +import {store} from "../main.ts"; +import {callApi} from "@/services/api.ts"; +import ErrorModal from "@/views/errorModal.vue"; +import {errorList} from "@/services/popupDisplayer.ts"; +import TempModal from "@/views/temp-modal.vue"; +import ErrorWrapper from "@/App.vue"; +function addResToTable(id: any){ + return (req: any) => { + console.log(req) + } +} -const store = useAuthStore() </script> @@ -26,19 +35,26 @@ const store = useAuthStore() <td>{{store.user.refreshToken}}</td> </tr> <tr> - <td>Unauthenticated API call</td> - <td><button>call</button></td> + <td>Entrepreneur API call</td> + <td><button @click="callApi('random')">call</button></td> <td>res</td> <td></td> </tr> <tr> - <td>Authenticated API call</td> - <td><button>call</button></td> + <td>Admin API call</td> + <td><button @click="callApi('random2')">call</button></td> <td>res</td> <td></td> </tr> + <tr> + <td>Unauth API call</td> + <td><button @click="callApi('random3')">call</button></td> + <td>res</td> + <td id="3"></td> + </tr> </tbody> </table> + <temp-modal></temp-modal> </template> <style scoped> From dfaa97346fced779b7d6fe5f4bea7346282d3ce1 Mon Sep 17 00:00:00 2001 From: Pierre Tellier <piair338@gmail.com> Date: Sat, 8 Feb 2025 12:09:18 +0100 Subject: [PATCH 3/7] fix: git + docker maintenance --- .gitignore | 3 ++- front/Dockerfile | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3e189ed..170ba0d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .env .idea -keycloak/CAS/target \ No newline at end of file +keycloak/CAS/target +docker-compose.yaml diff --git a/front/Dockerfile b/front/Dockerfile index 14faeb6..2a8d3c0 100644 --- a/front/Dockerfile +++ b/front/Dockerfile @@ -36,4 +36,4 @@ RUN chmod +x /start.sh EXPOSE 80 # Set the entry point to the shell script -ENTRYPOINT ["/start.sh"] \ No newline at end of file +ENTRYPOINT ["/start.sh"] From b228b78e17dcf2463e433c1d03e8512ae325a26f Mon Sep 17 00:00:00 2001 From: Pierre Tellier <piair338@gmail.com> Date: Sat, 8 Feb 2025 12:11:20 +0100 Subject: [PATCH 4/7] feat: add popop with awfuls colors and backend api call --- front/MyINPulse-front/src/App.vue | 10 ++++++---- front/MyINPulse-front/src/main.ts | 3 +-- front/MyINPulse-front/src/router/router.ts | 10 +--------- front/MyINPulse-front/src/services/api.ts | 5 +++-- .../src/services/popupDisplayer.ts | 15 +++++++++------ front/MyINPulse-front/src/views/errorWrapper.vue | 13 ++++++++++++- front/MyINPulse-front/src/views/test.vue | 4 ++-- 7 files changed, 34 insertions(+), 26 deletions(-) diff --git a/front/MyINPulse-front/src/App.vue b/front/MyINPulse-front/src/App.vue index 71701b5..8b3eee1 100644 --- a/front/MyINPulse-front/src/App.vue +++ b/front/MyINPulse-front/src/App.vue @@ -1,13 +1,11 @@ <script setup lang="ts"> import { RouterLink, RouterView } from 'vue-router' -import HelloWorld from './components/HelloWorld.vue' -import Header from './components/Header.vue'; -import ProjectComp from './components/Project-comp.vue'; +import ErrorWrapper from "@/views/errorWrapper.vue"; </script> <template> - <RouterView /> <Header /> + <error-wrapper></error-wrapper> <div id="main"> <ProjectComp v-for="(project, index) in projects" @@ -15,12 +13,16 @@ import ProjectComp from './components/Project-comp.vue'; :projectName="project.name" /> </div> + <RouterView /> </template> <style scoped> </style> <script lang="ts"> +import Header from "@/components/Header.vue"; +import ProjectComp from "@/components/Project-comp.vue"; + export default { name: 'App', components: { diff --git a/front/MyINPulse-front/src/main.ts b/front/MyINPulse-front/src/main.ts index 198447d..60f3509 100644 --- a/front/MyINPulse-front/src/main.ts +++ b/front/MyINPulse-front/src/main.ts @@ -1,11 +1,10 @@ import { createApp } from 'vue' import App from './App.vue' -import router from './router' +import router from './router/router.ts' import {createPinia} from "pinia"; import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; import AuthStorePlugin from './plugins/authStore'; import keycloakService from './services/keycloak'; -import axios from "axios"; import {useAuthStore} from "@/stores/authStore.ts"; let store: any; diff --git a/front/MyINPulse-front/src/router/router.ts b/front/MyINPulse-front/src/router/router.ts index e9697d9..70bb58a 100644 --- a/front/MyINPulse-front/src/router/router.ts +++ b/front/MyINPulse-front/src/router/router.ts @@ -3,21 +3,13 @@ import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ - { - path: '/about', - name: 'about', - // route level code-splitting - // this generates a separate chunk (About.[hash].js) for this route - // which is lazy-loaded when the route is visited. - component: () => import('../views/AboutView.vue'), - }, { path: '/test', name: 'test', // route level code-splitting // this generates a separate chunk (About.[hash].js) for this route // which is lazy-loaded when the route is visited. - component: () => import('../views/Home.vue'), + component: () => import('../views/test.vue'), }, ], }) diff --git a/front/MyINPulse-front/src/services/api.ts b/front/MyINPulse-front/src/services/api.ts index d29273a..71e3194 100644 --- a/front/MyINPulse-front/src/services/api.ts +++ b/front/MyINPulse-front/src/services/api.ts @@ -1,5 +1,6 @@ import axios from "axios"; import {store} from "@/main.ts"; +import {addNewMessage, color} from "@/services/popupDisplayer.ts"; const axiosInstance = axios.create({ baseURL: import.meta.env.VITE_BACKEND_URL, @@ -34,11 +35,11 @@ axiosInstance.interceptors.response.use( // TODO: spawn a error modal function defaultApiErrorHandler(err: String){ - console.log(err) + addNewMessage(err, color.Red); } function defaultApiSuccessHandler(response: () => void){ - console.log(response) + addNewMessage(response.data, color.green) } function callApi(endpoint: string, onSuccessHandler?: any, onErrorHandler?: any): void { axiosInstance.get(endpoint).then( diff --git a/front/MyINPulse-front/src/services/popupDisplayer.ts b/front/MyINPulse-front/src/services/popupDisplayer.ts index 61d02b9..77113f9 100644 --- a/front/MyINPulse-front/src/services/popupDisplayer.ts +++ b/front/MyINPulse-front/src/services/popupDisplayer.ts @@ -1,17 +1,20 @@ import {ref} from "vue"; -enum errorType {Error, Warning} -function addNewError(errorMessage: string, timeout?: number, type?: errorType){ +enum color {Red, Yellow, Blue, green} + +function addNewMessage(errorMessage: string, type?: color, timeout?: number){ if (timeout == null){ timeout = 5000; } if (type == null){ - type = Error; + type = color.Red; } - errorList.value.push({errorMessage: errorMessage, timeout: timeout, type: type}) - setTimeout(() => errorList.value.pop(errorMessage), 5000) + + const data = {errorMessage: errorMessage, timeout: timeout, type: type, uid: Math.random()*100000}; + errorList.value.push(data) + setTimeout(() => errorList.value.slice(0, 1), timeout) } const errorList = ref([]) -export {addNewError, errorList} \ No newline at end of file +export {addNewMessage, errorList, color} \ No newline at end of file diff --git a/front/MyINPulse-front/src/views/errorWrapper.vue b/front/MyINPulse-front/src/views/errorWrapper.vue index 96c0baf..1d56a2a 100644 --- a/front/MyINPulse-front/src/views/errorWrapper.vue +++ b/front/MyINPulse-front/src/views/errorWrapper.vue @@ -1,11 +1,22 @@ <script setup lang="ts"> +import {errorList} from "@/services/popupDisplayer.ts"; +import ErrorModal from "@/components/errorModal.vue"; </script> <template> - +<div class="error-wrapper"> + <error-modal v-for="elm in errorList" :data=elm></error-modal> +</div> </template> <style scoped> +.error-wrapper{ + position: absolute; + left: 70%; + //background-color: blue; + height: 100%; + width: 30%; +} </style> \ No newline at end of file diff --git a/front/MyINPulse-front/src/views/test.vue b/front/MyINPulse-front/src/views/test.vue index e879aef..90db6d1 100644 --- a/front/MyINPulse-front/src/views/test.vue +++ b/front/MyINPulse-front/src/views/test.vue @@ -1,9 +1,9 @@ <script setup lang="ts"> import {store} from "../main.ts"; import {callApi} from "@/services/api.ts"; -import ErrorModal from "@/views/errorModal.vue"; +import ErrorModal from "@/components/errorModal.vue"; import {errorList} from "@/services/popupDisplayer.ts"; -import TempModal from "@/views/temp-modal.vue"; +import TempModal from "@/components/temp-modal.vue"; import ErrorWrapper from "@/App.vue"; function addResToTable(id: any){ return (req: any) => { From b5c9b4067228e267accfd6e1cffcd9c070146c9d Mon Sep 17 00:00:00 2001 From: Pierre Tellier <piair338@gmail.com> Date: Sat, 8 Feb 2025 18:49:22 +0100 Subject: [PATCH 5/7] fix: fixed TS errors --- front/MyINPulse-front/src/services/api.ts | 4 ++-- front/MyINPulse-front/src/services/popupDisplayer.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/front/MyINPulse-front/src/services/api.ts b/front/MyINPulse-front/src/services/api.ts index 71e3194..cea6a9e 100644 --- a/front/MyINPulse-front/src/services/api.ts +++ b/front/MyINPulse-front/src/services/api.ts @@ -34,11 +34,11 @@ axiosInstance.interceptors.response.use( ); // TODO: spawn a error modal -function defaultApiErrorHandler(err: String){ +function defaultApiErrorHandler(err: string){ addNewMessage(err, color.Red); } -function defaultApiSuccessHandler(response: () => void){ +function defaultApiSuccessHandler(response: any){ addNewMessage(response.data, color.green) } function callApi(endpoint: string, onSuccessHandler?: any, onErrorHandler?: any): void { diff --git a/front/MyINPulse-front/src/services/popupDisplayer.ts b/front/MyINPulse-front/src/services/popupDisplayer.ts index 77113f9..3b222a2 100644 --- a/front/MyINPulse-front/src/services/popupDisplayer.ts +++ b/front/MyINPulse-front/src/services/popupDisplayer.ts @@ -1,5 +1,4 @@ import {ref} from "vue"; - enum color {Red, Yellow, Blue, green} function addNewMessage(errorMessage: string, type?: color, timeout?: number){ @@ -15,6 +14,6 @@ function addNewMessage(errorMessage: string, type?: color, timeout?: number){ setTimeout(() => errorList.value.slice(0, 1), timeout) } -const errorList = ref([]) +const errorList: any= ref([]) export {addNewMessage, errorList, color} \ No newline at end of file From b30e1196f4a062099a3b6494170ad2172027f296 Mon Sep 17 00:00:00 2001 From: ALAMI Adnane <Adnane.Alami@bordeaux-inp.fr> Date: Sat, 8 Feb 2025 20:18:44 +0100 Subject: [PATCH 6/7] canvas included in the main page, still shiting with vue --- front/MyINPulse-front/src/App.vue | 21 ++ .../src/components/canvas/Avantage.vue | 21 ++ .../src/components/canvas/Canaux.vue | 21 ++ .../src/components/canvas/Couts.vue | 21 ++ .../src/components/canvas/Header.vue | 37 ++++ .../src/components/canvas/Indicateurs.vue | 21 ++ .../src/components/canvas/Lean-canvas.vue | 42 ++++ .../src/components/canvas/Probleme.vue | 21 ++ .../src/components/canvas/Revenus.vue | 21 ++ .../src/components/canvas/Segments.vue | 21 ++ .../src/components/canvas/Solution.vue | 21 ++ .../src/components/canvas/Valeur.vue | 21 ++ .../src/components/canvas/style-project.css | 184 ++++++++++++++++++ front/MyINPulse-front/src/main.ts | 3 +- front/MyINPulse-front/src/router/index.ts | 7 + .../MyINPulse-front/src/views/CanvasView.vue | 12 ++ 16 files changed, 494 insertions(+), 1 deletion(-) create mode 100644 front/MyINPulse-front/src/components/canvas/Avantage.vue create mode 100644 front/MyINPulse-front/src/components/canvas/Canaux.vue create mode 100644 front/MyINPulse-front/src/components/canvas/Couts.vue create mode 100644 front/MyINPulse-front/src/components/canvas/Header.vue create mode 100644 front/MyINPulse-front/src/components/canvas/Indicateurs.vue create mode 100644 front/MyINPulse-front/src/components/canvas/Lean-canvas.vue create mode 100644 front/MyINPulse-front/src/components/canvas/Probleme.vue create mode 100644 front/MyINPulse-front/src/components/canvas/Revenus.vue create mode 100644 front/MyINPulse-front/src/components/canvas/Segments.vue create mode 100644 front/MyINPulse-front/src/components/canvas/Solution.vue create mode 100644 front/MyINPulse-front/src/components/canvas/Valeur.vue create mode 100644 front/MyINPulse-front/src/components/canvas/style-project.css create mode 100644 front/MyINPulse-front/src/views/CanvasView.vue diff --git a/front/MyINPulse-front/src/App.vue b/front/MyINPulse-front/src/App.vue index 71701b5..6c3e95f 100644 --- a/front/MyINPulse-front/src/App.vue +++ b/front/MyINPulse-front/src/App.vue @@ -3,6 +3,7 @@ import { RouterLink, RouterView } from 'vue-router' import HelloWorld from './components/HelloWorld.vue' import Header from './components/Header.vue'; import ProjectComp from './components/Project-comp.vue'; +import CanvasView from './components/canvas/Lean-canvas.vue'; </script> <template> @@ -15,9 +16,28 @@ import ProjectComp from './components/Project-comp.vue'; :projectName="project.name" /> </div> + <div id="canvas"> + <button @click="$router.push('/canvas')">Voir Canvas</button> + </div> </template> <style scoped> + +#canvas { /* My shit */ + margin-top: 20px; +} +button { + padding: 10px 15px; + background-color: #007bff; + color: white; + border: none; + cursor: pointer; + border-radius: 5px; +} +button:hover { + background-color: #0056b3; +} + </style> <script lang="ts"> @@ -26,6 +46,7 @@ export default { components: { Header, ProjectComp, + CanvasView, // My shit }, data() { return { diff --git a/front/MyINPulse-front/src/components/canvas/Avantage.vue b/front/MyINPulse-front/src/components/canvas/Avantage.vue new file mode 100644 index 0000000..59532ce --- /dev/null +++ b/front/MyINPulse-front/src/components/canvas/Avantage.vue @@ -0,0 +1,21 @@ +<template> + <div class="cell produit" @click="toggleExpand"> + <h3>9. Avantage déloyal</h3> + <p>Ce qui ne peut pas être facilement copié ou acheté</p> + </div> + </template> + + <script> + export default { + methods: { + toggleExpand(event) { + event.currentTarget.classList.toggle("expanded"); + } + } + }; + </script> + + <style scoped> + @import "@/components/canvas/style-project.css"; + </style> + \ No newline at end of file diff --git a/front/MyINPulse-front/src/components/canvas/Canaux.vue b/front/MyINPulse-front/src/components/canvas/Canaux.vue new file mode 100644 index 0000000..6aee546 --- /dev/null +++ b/front/MyINPulse-front/src/components/canvas/Canaux.vue @@ -0,0 +1,21 @@ +<template> + <div class="cell produit" @click="toggleExpand"> + <h3>5. Canaux</h3> + <p>Chemins d'accès aux clients</p> + </div> + </template> + + <script> + export default { + methods: { + toggleExpand(event) { + event.currentTarget.classList.toggle("expanded"); + } + } + }; + </script> + + <style scoped> + @import "@/components/canvas/style-project.css"; + </style> + \ No newline at end of file diff --git a/front/MyINPulse-front/src/components/canvas/Couts.vue b/front/MyINPulse-front/src/components/canvas/Couts.vue new file mode 100644 index 0000000..c232d3f --- /dev/null +++ b/front/MyINPulse-front/src/components/canvas/Couts.vue @@ -0,0 +1,21 @@ +<template> + <div class="cell produit" @click="toggleExpand"> + <h3>7. Structure des coûts</h3> + <p>Coûts d'acquisition, distribution, hébergement, employés...</p> + </div> + </template> + + <script> + export default { + methods: { + toggleExpand(event) { + event.currentTarget.classList.toggle("expanded"); + } + } + }; + </script> + + <style scoped> + @import "@/components/canvas/style-project.css"; + </style> + \ No newline at end of file diff --git a/front/MyINPulse-front/src/components/canvas/Header.vue b/front/MyINPulse-front/src/components/canvas/Header.vue new file mode 100644 index 0000000..e860400 --- /dev/null +++ b/front/MyINPulse-front/src/components/canvas/Header.vue @@ -0,0 +1,37 @@ +<template> + <header> + <img src="@/assets/logo-inpulse.png" alt="INPulse Logo"> + <div class="header-buttons"> + <div class="contact-menu"> + <button class="contact-button" @click="toggleDropdown">Contact</button> + <div class="contact-dropdown" v-show="isDropdownOpen"> + <button>Contact All</button> + <button>Contact Person 1</button> + <button>Contact Person 2</button> + <button>Contact Person 3</button> + </div> + <div class="return"><a href="/">return to list project</a></div> + </div> + </div> + </header> + </template> + + <script> + export default { + data() { + return { + isDropdownOpen: false + }; + }, + methods: { + toggleDropdown() { + this.isDropdownOpen = !this.isDropdownOpen; + } + } + }; + </script> + + <style scoped> + @import "@/components/canvas/style-project.css"; + </style> + \ No newline at end of file diff --git a/front/MyINPulse-front/src/components/canvas/Indicateurs.vue b/front/MyINPulse-front/src/components/canvas/Indicateurs.vue new file mode 100644 index 0000000..0fd6d1a --- /dev/null +++ b/front/MyINPulse-front/src/components/canvas/Indicateurs.vue @@ -0,0 +1,21 @@ +<template> + <div class="cell produit" @click="toggleExpand"> + <h3>8. Indicateurs clés</h3> + <p>Activités clés que vous souhaitez évaluer</p> + </div> + </template> + + <script> + export default { + methods: { + toggleExpand(event) { + event.currentTarget.classList.toggle("expanded"); + } + } + }; + </script> + + <style scoped> + @import "@/components/canvas/style-project.css"; + </style> + \ No newline at end of file diff --git a/front/MyINPulse-front/src/components/canvas/Lean-canvas.vue b/front/MyINPulse-front/src/components/canvas/Lean-canvas.vue new file mode 100644 index 0000000..0141a17 --- /dev/null +++ b/front/MyINPulse-front/src/components/canvas/Lean-canvas.vue @@ -0,0 +1,42 @@ +<template> + <div class="canvas"> + <div class="row"> + <Probleme /> + <Segments /> + </div> + <div class="row"> + <Solution /> + <Valeur /> + <Canaux /> + </div> + <div class="row"> + <Couts /> + <Revenus /> + </div> + <div class="row"> + <Indicateurs /> + <Avantage /> + </div> + </div> + </template> + + <script> + import Probleme from "./Probleme.vue"; + import Segments from "./Segments.vue"; + import Solution from "./Solution.vue"; + import Valeur from "./Valeur.vue"; + import Canaux from "./Canaux.vue"; + import Revenus from "./Revenus.vue"; + import Couts from "./Couts.vue"; + import Indicateurs from "./Indicateurs.vue"; + import Avantage from "./Avantage.vue"; + + export default { + components: { Probleme, Segments, Solution, Valeur, Canaux, Revenus, Couts, Indicateurs, Avantage } + }; + </script> + + <style scoped> + @import "@/components/canvas/style-project.css"; + </style> + \ No newline at end of file diff --git a/front/MyINPulse-front/src/components/canvas/Probleme.vue b/front/MyINPulse-front/src/components/canvas/Probleme.vue new file mode 100644 index 0000000..d36b983 --- /dev/null +++ b/front/MyINPulse-front/src/components/canvas/Probleme.vue @@ -0,0 +1,21 @@ +<template> + <div class="cell produit" @click="toggleExpand"> + <h3>1. Problème</h3> + <p>3 problèmes essentiels à résoudre pour le client</p> + </div> + </template> + + <script> + export default { + methods: { + toggleExpand(event) { + event.currentTarget.classList.toggle("expanded"); + } + } + }; + </script> + + <style scoped> + @import "@/components/canvas/style-project.css"; + </style> + \ No newline at end of file diff --git a/front/MyINPulse-front/src/components/canvas/Revenus.vue b/front/MyINPulse-front/src/components/canvas/Revenus.vue new file mode 100644 index 0000000..0b8b57e --- /dev/null +++ b/front/MyINPulse-front/src/components/canvas/Revenus.vue @@ -0,0 +1,21 @@ +<template> + <div class="cell produit" @click="toggleExpand"> + <h3>6. Sources de revenus</h3> + <p>Modèle de revenus, durée, revenus espérés, marge espérée</p> + </div> + </template> + + <script> + export default { + methods: { + toggleExpand(event) { + event.currentTarget.classList.toggle("expanded"); + } + } + }; + </script> + + <style scoped> + @import "@/components/canvas/style-project.css"; + </style> + \ No newline at end of file diff --git a/front/MyINPulse-front/src/components/canvas/Segments.vue b/front/MyINPulse-front/src/components/canvas/Segments.vue new file mode 100644 index 0000000..76eaba5 --- /dev/null +++ b/front/MyINPulse-front/src/components/canvas/Segments.vue @@ -0,0 +1,21 @@ +<template> + <div class="cell produit" @click="toggleExpand"> + <h3>2. Segments de clients</h3> + <p>Clients cibles</p> + </div> + </template> + + <script> + export default { + methods: { + toggleExpand(event) { + event.currentTarget.classList.toggle("expanded"); + } + } + }; + </script> + + <style scoped> + @import "@/components/canvas/style-project.css"; + </style> + \ No newline at end of file diff --git a/front/MyINPulse-front/src/components/canvas/Solution.vue b/front/MyINPulse-front/src/components/canvas/Solution.vue new file mode 100644 index 0000000..b3d5a05 --- /dev/null +++ b/front/MyINPulse-front/src/components/canvas/Solution.vue @@ -0,0 +1,21 @@ +<template> + <div class="cell produit" @click="toggleExpand"> + <h3>4. Solution</h3> + <p>3 fonctionnalités essentielles pour le client</p> + </div> + </template> + + <script> + export default { + methods: { + toggleExpand(event) { + event.currentTarget.classList.toggle("expanded"); + } + } + }; + </script> + + <style scoped> + @import "@/components/canvas/style-project.css"; + </style> + \ No newline at end of file diff --git a/front/MyINPulse-front/src/components/canvas/Valeur.vue b/front/MyINPulse-front/src/components/canvas/Valeur.vue new file mode 100644 index 0000000..d577863 --- /dev/null +++ b/front/MyINPulse-front/src/components/canvas/Valeur.vue @@ -0,0 +1,21 @@ +<template> + <div class="cell produit" @click="toggleExpand"> + <h3>3. Proposition de valeur unique</h3> + <p>Message simple, clair et persuasif expliquant en quoi votre produit est différent et mérite d'être acheté</p> + </div> + </template> + + <script> + export default { + methods: { + toggleExpand(event) { + event.currentTarget.classList.toggle("expanded"); + } + } + }; + </script> + + <style scoped> + @import "@/components/canvas/style-project.css"; + </style> + \ No newline at end of file diff --git a/front/MyINPulse-front/src/components/canvas/style-project.css b/front/MyINPulse-front/src/components/canvas/style-project.css new file mode 100644 index 0000000..7761d40 --- /dev/null +++ b/front/MyINPulse-front/src/components/canvas/style-project.css @@ -0,0 +1,184 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 10px; + } + + .canvas { + display: flex; + flex-direction: column; + max-width: 1200px; + margin: 20px auto; + border: 2px dashed #d33; + background: #fff; + } + + .row { + display: flex; + } + + .cell { + flex: 1; + border: 1px solid #ddd; + padding: 10px; + text-align: center; + background-color: #f1f1f1; + } + + .produit { + background-color: #f9e4e4; + } + + .marche { + background-color: #e4f1f9; + } + + .valeur { + background-color: #f9f4e4; + } + + h3 { + margin: 0; + font-size: 18px; + color: #333; + } + + p { + margin: 5px 0 0; + font-size: 14px; + } + + + body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #f9f9f9; + } + + h1 img { + height: 80px; + } + + .canvas { + max-width: 1200px; + margin: 20px auto; + padding: 20px; + border: 2px dashed #d33; + background-color: #fff; + } + + .row { + display: flex; + margin-bottom: 10px; + } + + .cell { + flex: 1; + border: 1px solid #ddd; + padding: 20px; + text-align: center; + background-color: #f1f1f1; + } + + #ade { + max-width: 1200px; + margin: 20px auto; + padding: 20px; + text-align: center; + background-color: #e8f5e9; + border: 2px solid #4caf50; + border-radius: 10px; + } + + #ade h3 { + color: #2e7d32; + } + + #ade p { + margin: 10px 0; + font-size: 16px; + color: #333; + } + header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 20px; + background-color: #fff; + border-bottom: 2px solid #ddd; + } + + header img { + height: 60px; + } + + header .contact-menu { + position: relative; + } + + .expanded { + transform: scale(1.2); /* L'élément reste agrandi */ + transition: transform 0.3s ease; /* Animation fluide */ + } + + .contact-button, .return { + padding: 10px 15px; + border: none; + border-radius: 4px; + background-color: #2196f3; + color: #fff; + cursor: pointer; + } + + .contact-button:hover, .return:hover { + background-color: #1976d2; + } + + /* Dropdown styling */ + .contact-dropdown { + position: absolute; + right: 0; + top: 50px; + display: none; + flex-direction: column; + gap: 10px; + padding: 15px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + z-index: 10; + } + + .contact-dropdown button { + padding: 8px 12px; + border: none; + border-radius: 4px; + background-color: #4caf50; + color: #fff; + cursor: pointer; + } + + .contact-dropdown button:hover { + background-color: #388e3c; + } + + .return { + background-color: #f44336; + } + + .return:hover { + background-color: #d32f2f; + } + + .header-buttons { + display: flex; + align-items: center; + gap: 15px; + } + + + a{ + color: white; + } \ No newline at end of file diff --git a/front/MyINPulse-front/src/main.ts b/front/MyINPulse-front/src/main.ts index e33d5c1..5bf00b7 100644 --- a/front/MyINPulse-front/src/main.ts +++ b/front/MyINPulse-front/src/main.ts @@ -25,7 +25,8 @@ keycloakService.CallInit(() => { }) -createApp(App).mount('#app'); +// this shit made by me so i can run the canva vue app +createApp(App).use(router).mount('#app'); // TODO: fix the comment /* diff --git a/front/MyINPulse-front/src/router/index.ts b/front/MyINPulse-front/src/router/index.ts index e9697d9..54cde30 100644 --- a/front/MyINPulse-front/src/router/index.ts +++ b/front/MyINPulse-front/src/router/index.ts @@ -19,6 +19,13 @@ const router = createRouter({ // which is lazy-loaded when the route is visited. component: () => import('../views/Home.vue'), }, + + // route pour les canvas (made by adnane), in fact the two vue apps are separated for now + { + path: '/canvas', + name: 'canvas', + component: () => import('../views/CanvasView.vue'), + }, ], }) diff --git a/front/MyINPulse-front/src/views/CanvasView.vue b/front/MyINPulse-front/src/views/CanvasView.vue new file mode 100644 index 0000000..d87bd29 --- /dev/null +++ b/front/MyINPulse-front/src/views/CanvasView.vue @@ -0,0 +1,12 @@ +<!-- src/views/CanvasView.vue --> +<template> + <div> + <h1>Page Canvas</h1> + <LeanCanvas /> + </div> + </template> + + <script setup lang="ts"> + import LeanCanvas from '@/components/canvas/Lean-canvas.vue'; + </script> + \ No newline at end of file From 4fda5513a920e58e50419140abec3965fbe55f3c Mon Sep 17 00:00:00 2001 From: ALAMI Adnane <Adnane.Alami@bordeaux-inp.fr> Date: Sat, 8 Feb 2025 20:33:03 +0100 Subject: [PATCH 7/7] error corrected --- front/MyINPulse-front/src/App.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/MyINPulse-front/src/App.vue b/front/MyINPulse-front/src/App.vue index 65e6bf6..f187256 100644 --- a/front/MyINPulse-front/src/App.vue +++ b/front/MyINPulse-front/src/App.vue @@ -43,8 +43,8 @@ button:hover { </style> <script lang="ts"> -import Header from "@/components/Header.vue"; -import ProjectComp from "@/components/Project-comp.vue"; +//import Header from "@/components/Header.vue"; +//import ProjectComp from "@/components/Project-comp.vue"; export default { name: 'App',