Compare commits
	
		
			28 Commits
		
	
	
		
			9a1d24f334
			...
			front_test
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a1322c64ca | |||
| 8394bf02f2 | |||
| 
						 | 
					f48b570494 | ||
| 
						 | 
					0733f8d5af | ||
| 
						 | 
					8071c01c5d | ||
| 4ee3d9bc44 | |||
| d75d45e204 | |||
| 
						 | 
					9f3754776f | ||
| 651fb2b1a1 | |||
| 
						 | 
					aa5988ce75 | ||
| 
						 | 
					9ae18e1e4b | ||
| 22ebb0e1f4 | |||
| 09e4b3262f | |||
| 6a3d4239ab | |||
| 9d71c93b5b | |||
| 
						 | 
					5145b833ae | ||
| 
						 | 
					4080cee818 | ||
| 
						 | 
					f4d73654d1 | ||
| 4fda5513a9 | |||
| 32407b0e8f | |||
| b30e1196f4 | |||
| 
						 | 
					b5c9b40672 | ||
| 
						 | 
					b228b78e17 | ||
| 
						 | 
					dfaa97346f | ||
| 
						 | 
					3de90295bd | ||
| 
						 | 
					dbf06d8c64 | ||
| 
						 | 
					edd4993f3f | ||
| 
						 | 
					8abbddaebd | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +1,4 @@
 | 
				
			|||||||
.env
 | 
					.env
 | 
				
			||||||
.idea
 | 
					.idea
 | 
				
			||||||
keycloak/CAS/target
 | 
					keycloak/CAS/target
 | 
				
			||||||
 | 
					docker-compose.yaml
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										8
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								Makefile
									
									
									
									
									
								
							@@ -3,7 +3,7 @@ help:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
clean: 
 | 
					clean: 
 | 
				
			||||||
	@cp config/prod.docker-compose.yaml docker-compose.yaml
 | 
						@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 docker-compose.yaml
 | 
				
			||||||
	@rm -f .env
 | 
						@rm -f .env
 | 
				
			||||||
	@rm -f front/MyINPulse-front/.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.front.env front/MyINPulse-front/.env
 | 
				
			||||||
	@cp config/frontdev.main.env .env
 | 
						@cp config/frontdev.main.env .env
 | 
				
			||||||
	@cp config/frontdev.docker-compose.yaml docker-compose.yaml
 | 
						@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
 | 
						@cd ./front/MyINPulse-front/ && npm run dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
prod: clean
 | 
					prod: clean
 | 
				
			||||||
	@cp config/prod.front.env front/MyINPulse-front/.env
 | 
						@cp config/prod.front.env front/MyINPulse-front/.env
 | 
				
			||||||
	@cp config/prod.main.env .env
 | 
						@cp config/prod.main.env .env
 | 
				
			||||||
	@cp config/frontdev.docker-compose.yaml docker-compose.yaml
 | 
						@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.front.env front/MyINPulse-front/.env
 | 
				
			||||||
	@cp config/backdev.main.env .env
 | 
						@cp config/backdev.main.env .env
 | 
				
			||||||
	@cp config/backdev.docker-compose.yaml docker-compose.yaml
 | 
						@cp config/backdev.docker-compose.yaml docker-compose.yaml
 | 
				
			||||||
	@docker compose up -d 
 | 
						@docker compose up -d --build
 | 
				
			||||||
	@echo "cd MyINPulse-back"
 | 
						@echo "cd MyINPulse-back"
 | 
				
			||||||
	@echo "./gradlew bootRun --args='--server.port=8081'"
 | 
						@echo "./gradlew bootRun --args='--server.port=8081'"
 | 
				
			||||||
@@ -1,22 +1,22 @@
 | 
				
			|||||||
package enseirb.myinpulse;
 | 
					package enseirb.myinpulse;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import enseirb.myinpulse.security.KeycloakJwtRolesConverter;
 | 
				
			||||||
import org.springframework.boot.SpringApplication;
 | 
					import org.springframework.boot.SpringApplication;
 | 
				
			||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
 | 
					import org.springframework.boot.autoconfigure.SpringBootApplication;
 | 
				
			||||||
import org.springframework.context.annotation.Bean;
 | 
					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.HttpSecurity;
 | 
				
			||||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
 | 
					import org.springframework.security.oauth2.jwt.*;
 | 
				
			||||||
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
 | 
					 | 
				
			||||||
import org.springframework.security.web.SecurityFilterChain;
 | 
					import org.springframework.security.web.SecurityFilterChain;
 | 
				
			||||||
import org.springframework.web.cors.CorsConfiguration;
 | 
					import org.springframework.web.cors.CorsConfiguration;
 | 
				
			||||||
import org.springframework.web.cors.CorsConfigurationSource;
 | 
					import org.springframework.web.cors.CorsConfigurationSource;
 | 
				
			||||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 | 
					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
 | 
					@SpringBootApplication
 | 
				
			||||||
public class MyinpulseApplication {
 | 
					public class MyinpulseApplication {
 | 
				
			||||||
@@ -25,34 +25,5 @@ public class MyinpulseApplication {
 | 
				
			|||||||
		SpringApplication.run(MyinpulseApplication.class, args);
 | 
							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();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
spring.application.name=myinpulse
 | 
					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.jwk-set-uri=http://localhost:7080/realms/test/protocol/openid-connect/certs
 | 
				
			||||||
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/test
 | 
					spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/test
 | 
				
			||||||
 | 
					logging.level.org.springframework.security=DEBUG
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,3 +2,4 @@ VITE_KEYCLOAK_URL=http://localhost:7080
 | 
				
			|||||||
VITE_KEYCLOAK_CLIENT_ID=myinpulse
 | 
					VITE_KEYCLOAK_CLIENT_ID=myinpulse
 | 
				
			||||||
VITE_KEYCLOAK_REALM=test
 | 
					VITE_KEYCLOAK_REALM=test
 | 
				
			||||||
VITE_APP_URL=http://localhost:8080
 | 
					VITE_APP_URL=http://localhost:8080
 | 
				
			||||||
 | 
					VITE_BACKEND_URL=http://localhost:8081/
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
VITE_KEYCLOAK_URL=http://localhost:7080
 | 
					VITE_KEYCLOAK_URL=http://localhost:7080
 | 
				
			||||||
VITE_KEYCLOAK_CLIENT_ID=myinpulse
 | 
					VITE_KEYCLOAK_CLIENT_ID=myinpulse-dev
 | 
				
			||||||
VITE_KEYCLOAK_REALM=test
 | 
					VITE_KEYCLOAK_REALM=test
 | 
				
			||||||
VITE_APP_URL=http://localhost:5173
 | 
					VITE_APP_URL=http://localhost:5173
 | 
				
			||||||
 | 
					VITE_BACKEND_URL=http://localhost:8081/
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,3 +2,4 @@ VITE_KEYCLOAK_URL=https://0549cd63f912d5dc9b31278d6f.eirb.fr
 | 
				
			|||||||
VITE_KEYCLOAK_CLIENT_ID=myinpulse-eirb
 | 
					VITE_KEYCLOAK_CLIENT_ID=myinpulse-eirb
 | 
				
			||||||
VITE_KEYCLOAK_REALM=test
 | 
					VITE_KEYCLOAK_REALM=test
 | 
				
			||||||
VITE_APP_URL=https://0549cd63f912d5dc9b31278d6f.piair.dev
 | 
					VITE_APP_URL=https://0549cd63f912d5dc9b31278d6f.piair.dev
 | 
				
			||||||
 | 
					VITE_BACKEND_URL=http://TODO/
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								front/MyINPulse-front/fake_data/db.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								front/MyINPulse-front/fake_data/db.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "entrepreneurs": [
 | 
				
			||||||
 | 
					      { "id": 1, "name": "Alice", "email": "alice@example.com" },
 | 
				
			||||||
 | 
					      { "id": 2, "name": "Bob", "email": "bob@example.com" },
 | 
				
			||||||
 | 
					      { "id": 3, "name": "Charlie", "email": "charlie@example.com" }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "data": [
 | 
				
			||||||
 | 
					      { "canva_data": "this is a fake data to test api" }
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								front/MyINPulse-front/fake_data/open.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										2
									
								
								front/MyINPulse-front/fake_data/open.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/bash
 | 
				
			||||||
 | 
					json-server --watch db.json --port 5000
 | 
				
			||||||
							
								
								
									
										78
									
								
								front/MyINPulse-front/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										78
									
								
								front/MyINPulse-front/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -8,13 +8,9 @@
 | 
				
			|||||||
      "name": "my-vue-app",
 | 
					      "name": "my-vue-app",
 | 
				
			||||||
      "version": "0.0.0",
 | 
					      "version": "0.0.0",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@auth0/auth0-vue": "^2.4.0",
 | 
					 | 
				
			||||||
        "@dsb-norge/vue-keycloak-js": "^3.0.1",
 | 
					 | 
				
			||||||
        "axios": "^1.7.9",
 | 
					        "axios": "^1.7.9",
 | 
				
			||||||
        "cors": "^2.8.5",
 | 
					        "cors": "^2.8.5",
 | 
				
			||||||
        "keycloak-js": "^26.1.0",
 | 
					        "keycloak-js": "^26.1.0",
 | 
				
			||||||
        "oidc-client-ts": "^3.1.0",
 | 
					 | 
				
			||||||
        "oidc-spa": "^6.0.7",
 | 
					 | 
				
			||||||
        "pinia": "^2.3.1",
 | 
					        "pinia": "^2.3.1",
 | 
				
			||||||
        "pinia-plugin-persistedstate": "^4.2.0",
 | 
					        "pinia-plugin-persistedstate": "^4.2.0",
 | 
				
			||||||
        "vue": "^3.5.13",
 | 
					        "vue": "^3.5.13",
 | 
				
			||||||
@@ -54,28 +50,6 @@
 | 
				
			|||||||
        "url": "https://github.com/sponsors/antfu"
 | 
					        "url": "https://github.com/sponsors/antfu"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@auth0/auth0-spa-js": {
 | 
					 | 
				
			||||||
      "version": "2.1.3",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-2.1.3.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-NMTBNuuG4g3rame1aCnNS5qFYIzsTUV5qTFPRfTyYFS1feS6jsCBR+eTq9YkxCp1yuoM2UIcjunPaoPl77U9xQ=="
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/@auth0/auth0-vue": {
 | 
					 | 
				
			||||||
      "version": "2.4.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@auth0/auth0-vue/-/auth0-vue-2.4.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-12iLvojP8Pvxqu2Abxzksp0HqlSovGiAUhWrppnOaJP02MZEBQo+c/IwM6VbM0edNk+eqqjX5u96iw5peaCPSg==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@auth0/auth0-spa-js": "^2.1.3",
 | 
					 | 
				
			||||||
        "vue": "^3.2.41"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependencies": {
 | 
					 | 
				
			||||||
        "vue-router": "^4.0.12"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependenciesMeta": {
 | 
					 | 
				
			||||||
        "vue-router": {
 | 
					 | 
				
			||||||
          "optional": true
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/@babel/code-frame": {
 | 
					    "node_modules/@babel/code-frame": {
 | 
				
			||||||
      "version": "7.26.2",
 | 
					      "version": "7.26.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
 | 
				
			||||||
@@ -489,22 +463,6 @@
 | 
				
			|||||||
        "node": ">=6.9.0"
 | 
					        "node": ">=6.9.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@dsb-norge/vue-keycloak-js": {
 | 
					 | 
				
			||||||
      "version": "3.0.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@dsb-norge/vue-keycloak-js/-/vue-keycloak-js-3.0.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-uJ4deVw4Vyp2FrG0JjYAy8NE6zIlIIl/92mQDlSGH+9kc758hBdrCdZSD3aVzv/Lwh07tpOXsx4jXzbVQkPfkA==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "keycloak-js": "26.0.7"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependencies": {
 | 
					 | 
				
			||||||
        "vue": ">=3.0.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/@dsb-norge/vue-keycloak-js/node_modules/keycloak-js": {
 | 
					 | 
				
			||||||
      "version": "26.0.7",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.0.7.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-vKCk1ISMiouYRLjMzi5fKp58RnYxKEBS29BoDgVfYwVW94IXchj9jLqBvIet31VD1v79l3WaWT+WMX7fH8O4wA=="
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/@esbuild/aix-ppc64": {
 | 
					    "node_modules/@esbuild/aix-ppc64": {
 | 
				
			||||||
      "version": "0.24.2",
 | 
					      "version": "0.24.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
 | 
				
			||||||
@@ -2639,14 +2597,6 @@
 | 
				
			|||||||
        "graceful-fs": "^4.1.6"
 | 
					        "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==",
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=18"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/keycloak-js": {
 | 
					    "node_modules/keycloak-js": {
 | 
				
			||||||
      "version": "26.1.0",
 | 
					      "version": "26.1.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.1.0.tgz",
 | 
				
			||||||
@@ -2982,34 +2932,6 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g=="
 | 
					      "integrity": "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/oidc-client-ts": {
 | 
					 | 
				
			||||||
      "version": "3.1.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.1.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-IDopEXjiwjkmJLYZo6BTlvwOtnlSniWZkKZoXforC/oLZHC9wkIxd25Kwtmo5yKFMMVcsp3JY6bhcNJqdYk8+g==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "jwt-decode": "^4.0.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=18"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/oidc-spa": {
 | 
					 | 
				
			||||||
      "version": "6.0.7",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/oidc-spa/-/oidc-spa-6.0.7.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-2N/mGou2RVyFcpD2dyKWpDkCeDbSwqFg7U1o6y4WiL62EbEa7QHWgM0GlU9mWzt1r2dXJQZYrqbL07AVVv/juQ==",
 | 
					 | 
				
			||||||
      "peerDependencies": {
 | 
					 | 
				
			||||||
        "@types/react": "*",
 | 
					 | 
				
			||||||
        "react": "*"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependenciesMeta": {
 | 
					 | 
				
			||||||
        "@types/react": {
 | 
					 | 
				
			||||||
          "optional": true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "react": {
 | 
					 | 
				
			||||||
          "optional": true
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/open": {
 | 
					    "node_modules/open": {
 | 
				
			||||||
      "version": "10.1.0",
 | 
					      "version": "10.1.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,15 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { RouterLink, RouterView } from 'vue-router'
 | 
					import { RouterLink, RouterView } from 'vue-router'
 | 
				
			||||||
import HelloWorld from './components/HelloWorld.vue'
 | 
					import ErrorWrapper from "@/views/errorWrapper.vue";
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<Header />
 | 
				
			||||||
 | 
					    <RouterLink to="/">Home</RouterLink> |
 | 
				
			||||||
 | 
					    <RouterLink to="/canvas">Canvas</RouterLink>
 | 
				
			||||||
    <RouterView />
 | 
					    <RouterView />
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped>
 | 
					
 | 
				
			||||||
</style>
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										75
									
								
								front/MyINPulse-front/src/components/Agenda.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								front/MyINPulse-front/src/components/Agenda.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div id="agenda">
 | 
				
			||||||
 | 
					        <h3>Rendez-vous</h3>
 | 
				
			||||||
 | 
					        <table>
 | 
				
			||||||
 | 
					        <tbody>
 | 
				
			||||||
 | 
					            <tr v-for=" (p, index) in projectRDV" :key="index" >
 | 
				
			||||||
 | 
					                <td>{{ p.projectName }} </td> <td>{{ p.date }}</td> <td>{{ p.lieu }}</td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					        </tbody>
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					    import { defineProps } from "vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    interface rendezVous{
 | 
				
			||||||
 | 
					        projectName: String,
 | 
				
			||||||
 | 
					        date: String,
 | 
				
			||||||
 | 
					        lieu: String,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const props = defineProps<{
 | 
				
			||||||
 | 
					        projectRDV: rendezVous[]
 | 
				
			||||||
 | 
					    }>();
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped>
 | 
				
			||||||
 | 
					 #agenda {   
 | 
				
			||||||
 | 
					    padding: 20px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Table Styling */
 | 
				
			||||||
 | 
					 table {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    border-collapse: collapse;
 | 
				
			||||||
 | 
					    font-family: Arial, sans-serif;
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					    margin-top: 20px;
 | 
				
			||||||
 | 
					    border: 1px solid #ccc;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /* Header Row (if exists) */
 | 
				
			||||||
 | 
					  th {
 | 
				
			||||||
 | 
					    background-color: #f4f4f4;
 | 
				
			||||||
 | 
					    padding: 12px;
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					    border: 1px solid #ccc;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /* Table Body Rows */
 | 
				
			||||||
 | 
					  tbody tr {
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #ddd;
 | 
				
			||||||
 | 
					    transition: background-color 0.2s ease; /* Smooth hover effect */
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  tbody tr:hover {
 | 
				
			||||||
 | 
					    background-color: #f9f9f9; /* Highlight row on hover */
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /* Cells Styling */
 | 
				
			||||||
 | 
					  td {
 | 
				
			||||||
 | 
					    padding: 10px;
 | 
				
			||||||
 | 
					    border: 1px solid #eee;
 | 
				
			||||||
 | 
					    font-size: 14px;
 | 
				
			||||||
 | 
					    vertical-align: middle; /* Align text to middle */
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /* First Column Styling */
 | 
				
			||||||
 | 
					  td:first-child {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    width: 50px; /* Adjust width as needed */
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										26
									
								
								front/MyINPulse-front/src/components/Header.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								front/MyINPulse-front/src/components/Header.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <header>
 | 
				
			||||||
 | 
					      <img src="./icons/logo inpulse.png" alt="INPulse" />
 | 
				
			||||||
 | 
					    </header>
 | 
				
			||||||
 | 
					  </template>
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  <script lang="ts">
 | 
				
			||||||
 | 
					  export default {
 | 
				
			||||||
 | 
					    name: 'Header',
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  </script>
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  <style scoped>
 | 
				
			||||||
 | 
					  header img {
 | 
				
			||||||
 | 
					    width: 100px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  header{
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: space-between;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    padding: 10px 20px;
 | 
				
			||||||
 | 
					    background-color: #fff;
 | 
				
			||||||
 | 
					    border-bottom: 2px solid #ddd;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  </style>
 | 
				
			||||||
@@ -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>
 | 
					 | 
				
			||||||
							
								
								
									
										129
									
								
								front/MyINPulse-front/src/components/Project-comp.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								front/MyINPulse-front/src/components/Project-comp.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div @click="goToLink" class="project">
 | 
				
			||||||
 | 
					        <div class="project-header">
 | 
				
			||||||
 | 
					            <h2 >{{ projectName }}</h2>
 | 
				
			||||||
 | 
					            <div class="project-buttons">
 | 
				
			||||||
 | 
					                <button class="contact-btn">Contact</button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="project-body">   
 | 
				
			||||||
 | 
					            <ul>
 | 
				
			||||||
 | 
					                <li v-for="(name, index) in listName" :key="index">{{ name }}</li>
 | 
				
			||||||
 | 
					            </ul>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { defineProps } from "vue";
 | 
				
			||||||
 | 
					import { useRouter } from 'vue-router'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps<{
 | 
				
			||||||
 | 
					    projectName: string;
 | 
				
			||||||
 | 
					    listName: string[];
 | 
				
			||||||
 | 
					    projectLink: string;
 | 
				
			||||||
 | 
					}>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const router = useRouter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const goToLink = () => {
 | 
				
			||||||
 | 
					  if (props.projectLink) {
 | 
				
			||||||
 | 
					    router.push(props.projectLink);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped>
 | 
				
			||||||
 | 
					.project {
 | 
				
			||||||
 | 
					    background: linear-gradient(to right, #f4f4f4, #ffffff);
 | 
				
			||||||
 | 
					    border: 1px solid #ddd;
 | 
				
			||||||
 | 
					    border-radius: 10px;
 | 
				
			||||||
 | 
					    padding: 20px;
 | 
				
			||||||
 | 
					    margin: 20px 0;
 | 
				
			||||||
 | 
					    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					    font-family: Arial, sans-serif;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /* Header Styling */
 | 
				
			||||||
 | 
					  .project-header {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: space-between;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  .project-header h2 {
 | 
				
			||||||
 | 
					    font-size: 20px;
 | 
				
			||||||
 | 
					    color: #333;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /* Button Container */
 | 
				
			||||||
 | 
					  .project-buttons {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    gap: 10px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .info-btn {
 | 
				
			||||||
 | 
					    background-color: #4CAF50;
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  .info-btn:hover {
 | 
				
			||||||
 | 
					    background-color: #45a049;
 | 
				
			||||||
 | 
					    transform: scale(1.05);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  .contact-btn {
 | 
				
			||||||
 | 
					    background-color: #007BFF;
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  .contact-btn:hover {
 | 
				
			||||||
 | 
					    background-color: #0056b3;
 | 
				
			||||||
 | 
					    transform: scale(1.05);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .project-body {
 | 
				
			||||||
 | 
					    margin-top: 15px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  .project-body p {
 | 
				
			||||||
 | 
					    font-size: 16px;
 | 
				
			||||||
 | 
					    color: #555;
 | 
				
			||||||
 | 
					    margin-bottom: 10px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  .project-body ul {
 | 
				
			||||||
 | 
					    list-style-type: disc;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding-left: 20px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  .project-body ul li {
 | 
				
			||||||
 | 
					    font-size: 14px;
 | 
				
			||||||
 | 
					    color: #666;
 | 
				
			||||||
 | 
					    line-height: 1.6;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  button {
 | 
				
			||||||
 | 
					  padding: 10px 15px;
 | 
				
			||||||
 | 
					  background-color: #007bff;
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					  border-radius: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					  button:hover {
 | 
				
			||||||
 | 
					    background-color: #0056b3;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@@ -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>
 | 
					 | 
				
			||||||
@@ -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>
 | 
					 | 
				
			||||||
							
								
								
									
										88
									
								
								front/MyINPulse-front/src/components/canvas/CanvasItem.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								front/MyINPulse-front/src/components/canvas/CanvasItem.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div :class="['cell', { expanded }]"
 | 
				
			||||||
 | 
					    @click="toggleExpand"
 | 
				
			||||||
 | 
					    :style="{ justifyContent: expanded ? 'flex-start' : 'center' }"> <!-- Looking for finding a way 
 | 
				
			||||||
 | 
					                                                          to make this style in the toggleExpand event -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <h3>{{ title }}</h3>
 | 
				
			||||||
 | 
					      <p>{{ currentDescription }}</p>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { ref, defineProps, onMounted } from "vue";
 | 
				
			||||||
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps<{  
 | 
				
			||||||
 | 
					    title: string;
 | 
				
			||||||
 | 
					    description: string;
 | 
				
			||||||
 | 
					}>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const expanded = ref(false);
 | 
				
			||||||
 | 
					const currentDescription = ref(props.description);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const fetchData = async () => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const response = await axios.get("http://localhost:5000/data"); // Update the URL if needed
 | 
				
			||||||
 | 
					    currentDescription.value = response.data[0].canva_data;
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    console.error("Erreur lors de la récupération des données :", error);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const toggleExpand = async () => {
 | 
				
			||||||
 | 
					  if (!expanded.value) {
 | 
				
			||||||
 | 
					    await fetchData();
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    currentDescription.value = props.description;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  expanded.value = !expanded.value;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					<style scoped>
 | 
				
			||||||
 | 
					@import "@/components/canvas/style-project.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.cell {
 | 
				
			||||||
 | 
						display: flex;
 | 
				
			||||||
 | 
						flex-direction: column;
 | 
				
			||||||
 | 
						align-items: center;
 | 
				
			||||||
 | 
						justify-content: center;
 | 
				
			||||||
 | 
						text-align: center;
 | 
				
			||||||
 | 
						transition: all 0.3s ease;
 | 
				
			||||||
 | 
						cursor: pointer;
 | 
				
			||||||
 | 
					  box-shadow: 0 4px 5px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.cell:not(.expanded):hover {
 | 
				
			||||||
 | 
					  transform: scale(1.05);
 | 
				
			||||||
 | 
					  box-shadow: 0 8px 9px rgba(0, 0, 0, 0.2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.cell h3 {
 | 
				
			||||||
 | 
					    font-size: 20px;
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					    /*margin-bottom: 10px;*/
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.cell p {
 | 
				
			||||||
 | 
					    font-size: 14px;
 | 
				
			||||||
 | 
					    color: #666;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.expanded {
 | 
				
			||||||
 | 
					  position: fixed;
 | 
				
			||||||
 | 
						top: 0;
 | 
				
			||||||
 | 
						left: 0;
 | 
				
			||||||
 | 
						width: 100%;
 | 
				
			||||||
 | 
						height: 100%;
 | 
				
			||||||
 | 
						background: white;
 | 
				
			||||||
 | 
						z-index: 10;
 | 
				
			||||||
 | 
						display: flex;
 | 
				
			||||||
 | 
						align-items: center;
 | 
				
			||||||
 | 
						justify-content: center;
 | 
				
			||||||
 | 
						box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										120
									
								
								front/MyINPulse-front/src/components/canvas/HeaderCanvas.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								front/MyINPulse-front/src/components/canvas/HeaderCanvas.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,120 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <header>
 | 
				
			||||||
 | 
					    <img src="../icons/logo inpulse.png" alt="INPulse Logo">
 | 
				
			||||||
 | 
					    <div class="header-buttons">
 | 
				
			||||||
 | 
					      <div class="menu">
 | 
				
			||||||
 | 
					        <button class="contact-button" @click="toggleDropdown">Contact</button>
 | 
				
			||||||
 | 
					        <div class="contact-dropdown" v-bind:class="{ 'dropdown-visible': isDropdownOpen }">
 | 
				
			||||||
 | 
					          <button @click="contactAll">Contact All</button>
 | 
				
			||||||
 | 
					          <button v-for="(email, index) in entrepreneurEmails" :key="index">
 | 
				
			||||||
 | 
					            {{ email }}
 | 
				
			||||||
 | 
					          </button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <button class="return-button">
 | 
				
			||||||
 | 
					            <RouterLink to="/">Return to list project</RouterLink>
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </header>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup>
 | 
				
			||||||
 | 
					import { ref, onMounted } from "vue";
 | 
				
			||||||
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isDropdownOpen = ref(false);
 | 
				
			||||||
 | 
					const entrepreneurEmails = ref([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const toggleDropdown = () => {
 | 
				
			||||||
 | 
					  isDropdownOpen.value = !isDropdownOpen.value;
 | 
				
			||||||
 | 
					  console.log("Dropdown toggled:", isDropdownOpen.value); // for debug purposes
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const fetchEntrepreneurs = async () => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const response = await axios.get("http://localhost:5000/entrepreneurs");
 | 
				
			||||||
 | 
					    entrepreneurEmails.value = response.data.map(e => e.email);
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    console.error("Erreur lors de la récupération des entrepreneurs:", error);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const contactAll = () => {
 | 
				
			||||||
 | 
					  alert("Contacter tous les entrepreneurs : " + entrepreneurEmails.value.join(", "));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(fetchEntrepreneurs);
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped>
 | 
				
			||||||
 | 
					@import "@/components/canvas/style-project.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					header {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  justify-content: space-between;
 | 
				
			||||||
 | 
					  align-items: flex-start;
 | 
				
			||||||
 | 
					  padding: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.header-buttons {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: flex-start;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.menu {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-direction: column;
 | 
				
			||||||
 | 
					  gap: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.contact-button, .return-button {
 | 
				
			||||||
 | 
					  background-color: #000;
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  padding: 10px;
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					  font-size: 14px;
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.return-button a {
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.contact-dropdown {
 | 
				
			||||||
 | 
					  display: none;
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  background-color: white;
 | 
				
			||||||
 | 
					  box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
 | 
				
			||||||
 | 
					  border-radius: 8px;
 | 
				
			||||||
 | 
					  padding: 10px;
 | 
				
			||||||
 | 
					  margin-top: 5px;
 | 
				
			||||||
 | 
					  z-index: 1000;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.contact-dropdown button {
 | 
				
			||||||
 | 
					  display: block;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  padding: 5px;
 | 
				
			||||||
 | 
					  text-align: left;
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  background: none;
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.contact-dropdown button:hover {
 | 
				
			||||||
 | 
					  background-color: #f0f0f0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.dropdown-visible {
 | 
				
			||||||
 | 
					  display: block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					header img {
 | 
				
			||||||
 | 
					  width: 100px;
 | 
				
			||||||
 | 
					  height: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										56
									
								
								front/MyINPulse-front/src/components/canvas/LeanCanvas.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								front/MyINPulse-front/src/components/canvas/LeanCanvas.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div class="canvas">
 | 
				
			||||||
 | 
					    <CanvasItem
 | 
				
			||||||
 | 
					      v-for="(item, index) in items"
 | 
				
			||||||
 | 
					      :key="index"
 | 
				
			||||||
 | 
					      :title="item.title"
 | 
				
			||||||
 | 
					      :description="item.description"
 | 
				
			||||||
 | 
					      :class="item.class"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { ref } from "vue";
 | 
				
			||||||
 | 
					import CanvasItem from "@/components/canvas/CanvasItem.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const items = ref([
 | 
				
			||||||
 | 
					  { title: "1. Problème", description: "3 problèmes essentiels à résoudre pour le client", class: "Probleme" },
 | 
				
			||||||
 | 
					  { title: "2. Segments", description: "Les segments de clientèle visés", class: "Segments" },
 | 
				
			||||||
 | 
					  { title: "3. Valeur", description: "La proposition de valeur", class: "Valeur" },
 | 
				
			||||||
 | 
					  { title: "4. Solution", description: "Les solutions proposées", class: "Solution" },
 | 
				
			||||||
 | 
					  { title: "5. Avantage", description: "Les avantages concurrentiels", class: "Avantage" },
 | 
				
			||||||
 | 
					  { title: "6. Canaux", description: "Les canaux de distribution", class: "Canaux" },
 | 
				
			||||||
 | 
					  { title: "7. Indicateurs", description: "Les indicateurs clés de performance", class: "Indicateurs" },
 | 
				
			||||||
 | 
					  { title: "8. Coûts", description: "Les coûts associés", class: "Couts" },
 | 
				
			||||||
 | 
					  { title: "9. Revenus", description: "Les sources de revenus", class: "Revenus" }
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped>
 | 
				
			||||||
 | 
					@import "@/components/canvas/style-project.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.canvas {
 | 
				
			||||||
 | 
					  display: grid;
 | 
				
			||||||
 | 
					  grid-template-columns: repeat(10, 1fr);
 | 
				
			||||||
 | 
					  grid-template-rows: repeat(6, 1fr);
 | 
				
			||||||
 | 
					  gap: 10px;
 | 
				
			||||||
 | 
					  padding: 10px;
 | 
				
			||||||
 | 
					  max-width: 1200px;
 | 
				
			||||||
 | 
					  margin: 20px auto;
 | 
				
			||||||
 | 
					  background-color: #fff; 
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  height: 80vh;
 | 
				
			||||||
 | 
					  overflow: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.Probleme { grid-column: 1 / 3; grid-row: 1 / 5; }
 | 
				
			||||||
 | 
					.Segments { grid-column: 9 / 11; grid-row: 1 / 5; }
 | 
				
			||||||
 | 
					.Valeur { grid-column: 5 / 7; grid-row: 1 / 5; }
 | 
				
			||||||
 | 
					.Solution { grid-column: 3 / 5; grid-row: 1 / 3; }
 | 
				
			||||||
 | 
					.Avantage { grid-column: 7 / 9; grid-row: 1 / 3; }
 | 
				
			||||||
 | 
					.Canaux { grid-column: 7 / 9; grid-row: 3 / 5; }
 | 
				
			||||||
 | 
					.Indicateurs { grid-column: 3 / 5; grid-row: 3 / 5; }
 | 
				
			||||||
 | 
					.Couts { grid-column: 1 / 6; grid-row: 5 / 7; }
 | 
				
			||||||
 | 
					.Revenus { grid-column: 6 / 11; grid-row: 5 / 7; }
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										156
									
								
								front/MyINPulse-front/src/components/canvas/style-project.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								front/MyINPulse-front/src/components/canvas/style-project.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,156 @@
 | 
				
			|||||||
 | 
					body {
 | 
				
			||||||
 | 
					    font-family: Arial, sans-serif;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 10px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  .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;
 | 
				
			||||||
 | 
					    margin: 40px;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .row {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    margin-bottom: 10px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #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;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .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;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
							
								
								
									
										94
									
								
								front/MyINPulse-front/src/components/errorModal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								front/MyINPulse-front/src/components/errorModal.vue
									
									
									
									
									
										Normal file
									
								
							@@ -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>
 | 
				
			||||||
@@ -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>
 | 
					 | 
				
			||||||
@@ -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>
 | 
					 | 
				
			||||||
@@ -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>
 | 
					 | 
				
			||||||
@@ -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>
 | 
					 | 
				
			||||||
@@ -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>
 | 
					 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								front/MyINPulse-front/src/components/icons/logo inpulse.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								front/MyINPulse-front/src/components/icons/logo inpulse.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 39 KiB  | 
@@ -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}
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
@@ -1,30 +1,36 @@
 | 
				
			|||||||
import { createApp } from 'vue'
 | 
					import { createApp } from 'vue'
 | 
				
			||||||
import App from './App.vue'
 | 
					import App from './App.vue'
 | 
				
			||||||
import router from './router'
 | 
					import router from './router/router.ts'
 | 
				
			||||||
//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 {createPinia} from "pinia";
 | 
				
			||||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
 | 
					import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
 | 
				
			||||||
import AuthStorePlugin from './plugins/authStore';
 | 
					import AuthStorePlugin from './plugins/authStore';
 | 
				
			||||||
import keycloakService from './services/keycloak';
 | 
					import keycloakService from './services/keycloak';
 | 
				
			||||||
 | 
					import {useAuthStore} from "@/stores/authStore.ts";
 | 
				
			||||||
 | 
					let store: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
keycloakService.CallInit(() => {
 | 
					keycloakService.CallInit(() => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
        const app = createApp(App)
 | 
					        const app = createApp(App)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Setup pinia store, allowing user to keep logged in status after refresh
 | 
				
			||||||
// Setup pinia store, allowing user to keep logged in status after refresh
 | 
					 | 
				
			||||||
        const pinia = createPinia();
 | 
					        const pinia = createPinia();
 | 
				
			||||||
        pinia.use(piniaPluginPersistedstate);
 | 
					        pinia.use(piniaPluginPersistedstate);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        app.use(pinia);
 | 
					        app.use(pinia);
 | 
				
			||||||
        app.use(AuthStorePlugin, { pinia });
 | 
					        app.use(AuthStorePlugin, { pinia });
 | 
				
			||||||
 | 
					        store = useAuthStore();
 | 
				
			||||||
        app.use(router)
 | 
					        app.use(router)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        app.mount('#app');
 | 
					        app.mount('#app');
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					        console.error("Error while initiating Keycloak.")
 | 
				
			||||||
 | 
					        console.error(e)
 | 
				
			||||||
 | 
					        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
 | 
					// TODO: fix the comment
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
@@ -66,3 +72,6 @@ app.use(VueKeyCloak,{
 | 
				
			|||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {store};
 | 
				
			||||||
@@ -3,21 +3,26 @@ import { createRouter, createWebHistory } from 'vue-router'
 | 
				
			|||||||
const router = createRouter({
 | 
					const router = createRouter({
 | 
				
			||||||
  history: createWebHistory(import.meta.env.BASE_URL),
 | 
					  history: createWebHistory(import.meta.env.BASE_URL),
 | 
				
			||||||
  routes: [
 | 
					  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',
 | 
					      path: '/test',
 | 
				
			||||||
      name: 'test',
 | 
					      name: 'test',
 | 
				
			||||||
      // route level code-splitting
 | 
					      // route level code-splitting
 | 
				
			||||||
      // this generates a separate chunk (About.[hash].js) for this route
 | 
					      // this generates a separate chunk (About.[hash].js) for this route
 | 
				
			||||||
      // which is lazy-loaded when the route is visited.
 | 
					      // which is lazy-loaded when the route is visited.
 | 
				
			||||||
      component: () => import('../views/Home.vue'),
 | 
					      component: () => import('../views/test.vue'),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      path: '/',
 | 
				
			||||||
 | 
					      name: 'Admin-main',
 | 
				
			||||||
 | 
					      component: () => import('../views/AdminMain.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'),
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
@@ -1,13 +1,56 @@
 | 
				
			|||||||
// file: src/services/api.js
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import axios from "axios";
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					import {store} from "@/main.ts";
 | 
				
			||||||
 | 
					import {addNewMessage, color} from "@/services/popupDisplayer.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Creating an instance for axios to be used by the token interceptor service
 | 
					const axiosInstance = axios.create({
 | 
				
			||||||
const instance = axios.create({
 | 
					    baseURL: import.meta.env.VITE_BACKEND_URL,
 | 
				
			||||||
    baseURL: `${import.meta.env.VITE_BE_API_URL}/api`,
 | 
					 | 
				
			||||||
    headers: {
 | 
					    headers: {
 | 
				
			||||||
        "Content-Type": "application/json",
 | 
					        'Content-Type': 'application/json',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default instance;
 | 
					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){
 | 
				
			||||||
 | 
					    addNewMessage(err, color.Red);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function defaultApiSuccessHandler(response: any){
 | 
				
			||||||
 | 
					    addNewMessage(response.data, color.green)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					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}
 | 
				
			||||||
							
								
								
									
										19
									
								
								front/MyINPulse-front/src/services/popupDisplayer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								front/MyINPulse-front/src/services/popupDisplayer.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					import {ref} from "vue";
 | 
				
			||||||
 | 
					enum color {Red, Yellow, Blue, green}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function addNewMessage(errorMessage: string, type?: color, timeout?: number){
 | 
				
			||||||
 | 
					    if (timeout == null){
 | 
				
			||||||
 | 
					        timeout = 5000;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (type == null){
 | 
				
			||||||
 | 
					        type = color.Red;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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: any= ref([])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {addNewMessage, errorList, color}
 | 
				
			||||||
							
								
								
									
										68
									
								
								front/MyINPulse-front/src/views/AdminMain.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								front/MyINPulse-front/src/views/AdminMain.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <Header />
 | 
				
			||||||
 | 
					    <error-wrapper></error-wrapper>
 | 
				
			||||||
 | 
					    <div id="container"> 
 | 
				
			||||||
 | 
					      <div id="main">
 | 
				
			||||||
 | 
					          <ProjectComp 
 | 
				
			||||||
 | 
					            v-for="(project, index) in projects" 
 | 
				
			||||||
 | 
					            :key="index"
 | 
				
			||||||
 | 
					            :projectName="project.name"
 | 
				
			||||||
 | 
					            :listName="project.members"
 | 
				
			||||||
 | 
					            :projectLink="project.link"
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					      <Agenda :projectRDV="rendezVous" />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import Header from '../components/Header.vue';
 | 
				
			||||||
 | 
					import Agenda from "../components/Agenda.vue"
 | 
				
			||||||
 | 
					import ProjectComp from '../components/Project-comp.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { ref } from "vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const projects = ref([
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "Projet Alpha",
 | 
				
			||||||
 | 
					    link: "/canvas", // to test
 | 
				
			||||||
 | 
					    members: ["Alice", "Bob", "Charlie"],
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "Projet Beta",
 | 
				
			||||||
 | 
					    link: "./canvas", // to test
 | 
				
			||||||
 | 
					    members: ["David", "Eve", "Frank"],
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rendezVous = ref([
 | 
				
			||||||
 | 
					  { projectName: "Projet Alpha", date: "2025-03-10", lieu: "P106" },
 | 
				
			||||||
 | 
					  { projectName: "Projet Beta", date: "2025-04-15", lieu: "Td10" },
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#container {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    display: grid;
 | 
				
			||||||
 | 
					    grid-template-columns: 3fr 1fr; /* Main body takes 3/4, agenda 1/4 */
 | 
				
			||||||
 | 
					    height: 100vh; /* Full viewport height */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					button {
 | 
				
			||||||
 | 
					  padding: 10px 15px;
 | 
				
			||||||
 | 
					  background-color: #007bff;
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					  border-radius: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					button:hover {
 | 
				
			||||||
 | 
					  background-color: #0056b3;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										17
									
								
								front/MyINPulse-front/src/views/CanvasView.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								front/MyINPulse-front/src/views/CanvasView.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					      <header>
 | 
				
			||||||
 | 
					        <HeaderCanvas />
 | 
				
			||||||
 | 
					      </header>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					      <h1>Page Canvas</h1>
 | 
				
			||||||
 | 
					      <LeanCanvas />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import HeaderCanvas from '../components/canvas/HeaderCanvas.vue';
 | 
				
			||||||
 | 
					import LeanCanvas from '../components/canvas/LeanCanvas.vue';
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
@@ -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>
 | 
					 | 
				
			||||||
							
								
								
									
										22
									
								
								front/MyINPulse-front/src/views/errorWrapper.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								front/MyINPulse-front/src/views/errorWrapper.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +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>
 | 
				
			||||||
@@ -1,7 +1,16 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import {useAuthStore} from "@/stores/authStore";
 | 
					import {store} from "../main.ts";
 | 
				
			||||||
 | 
					import {callApi} from "@/services/api.ts";
 | 
				
			||||||
 | 
					import ErrorModal from "@/components/errorModal.vue";
 | 
				
			||||||
 | 
					import {errorList} from "@/services/popupDisplayer.ts";
 | 
				
			||||||
 | 
					//import TempModal from "@/components/temp-modal.vue";
 | 
				
			||||||
 | 
					import ErrorWrapper from "@/App.vue";
 | 
				
			||||||
 | 
					function addResToTable(id: any){
 | 
				
			||||||
 | 
					  return (req: any) => {
 | 
				
			||||||
 | 
					    console.log(req)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const store = useAuthStore()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -26,19 +35,26 @@ const store = useAuthStore()
 | 
				
			|||||||
      <td>{{store.user.refreshToken}}</td>
 | 
					      <td>{{store.user.refreshToken}}</td>
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
      <td>Unauthenticated API call</td>
 | 
					      <td>Entrepreneur API call</td>
 | 
				
			||||||
      <td><button>call</button></td>
 | 
					      <td><button @click="callApi('random')">call</button></td>
 | 
				
			||||||
      <td>res</td>
 | 
					      <td>res</td>
 | 
				
			||||||
      <td></td>
 | 
					      <td></td>
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
      <td>Authenticated API call</td>
 | 
					      <td>Admin API call</td>
 | 
				
			||||||
      <td><button>call</button></td>
 | 
					      <td><button @click="callApi('random2')">call</button></td>
 | 
				
			||||||
      <td>res</td>
 | 
					      <td>res</td>
 | 
				
			||||||
      <td></td>
 | 
					      <td></td>
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
 | 
					    <tr>
 | 
				
			||||||
 | 
					      <td>Unauth API call</td>
 | 
				
			||||||
 | 
					      <td><button @click="callApi('random3')">call</button></td>
 | 
				
			||||||
 | 
					      <td>res</td>
 | 
				
			||||||
 | 
					      <td id="3"></td>
 | 
				
			||||||
 | 
					    </tr>
 | 
				
			||||||
    </tbody>
 | 
					    </tbody>
 | 
				
			||||||
  </table>
 | 
					  </table>
 | 
				
			||||||
 | 
					  <temp-modal></temp-modal>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped>
 | 
					<style scoped>
 | 
				
			||||||
							
								
								
									
										19
									
								
								tests/example.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								tests/example.spec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					// @ts-check
 | 
				
			||||||
 | 
					import { test, expect } from '@playwright/test';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('has title', async ({ page }) => {
 | 
				
			||||||
 | 
					  await page.goto('https://playwright.dev/');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Expect a title "to contain" a substring.
 | 
				
			||||||
 | 
					  await expect(page).toHaveTitle(/Playwright/);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('get started link', async ({ page }) => {
 | 
				
			||||||
 | 
					  await page.goto('https://playwright.dev/');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Click the get started link.
 | 
				
			||||||
 | 
					  await page.getByRole('link', { name: 'Get started' }).click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Expects page to have a heading with the name of Installation.
 | 
				
			||||||
 | 
					  await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										123
									
								
								tests/front.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								tests/front.spec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
				
			|||||||
 | 
					import { test,expect } from '@playwright/test'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('Title Page',async({page}) =>{
 | 
				
			||||||
 | 
					    await page.goto('http://localhost:5173/')
 | 
				
			||||||
 | 
					    await expect(page).toHaveTitle(/Vite App/)
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('Navigation between pages', async ({ page }) => {
 | 
				
			||||||
 | 
					  // Aller à la page d'accueil
 | 
				
			||||||
 | 
					  await page.goto('http://localhost:5173/');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Vérifier que l'URL a changé pour la page Canvas
 | 
				
			||||||
 | 
					  await page.click('text=Canvas');
 | 
				
			||||||
 | 
					  await expect(page).toHaveURL(/canvas/);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Vérifier que l'URL a changé pour la page Home
 | 
				
			||||||
 | 
					  await page.click('text=Home');
 | 
				
			||||||
 | 
					  await expect(page).toHaveURL(/\//);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					test.describe('Tests de la Page Canvas', () => {
 | 
				
			||||||
 | 
					test('Vérifier que tous les blocs Canvas sont affichés', async ({ page }) => {
 | 
				
			||||||
 | 
					  // Aller à la page Canvas
 | 
				
			||||||
 | 
					  await page.goto('http://localhost:5173/canvas');
 | 
				
			||||||
 | 
					  await expect(page.locator('h1')).toHaveText('Page Canvas');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const sections = [
 | 
				
			||||||
 | 
					        '1. Problème', '2. Segments', '3. Valeur', '4. Solution',
 | 
				
			||||||
 | 
					        '5. Avantage', '6. Canaux', '7. Indicateurs', '8. Coûts', '9. Revenus'
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const section of sections) {
 | 
				
			||||||
 | 
					        await expect(page.locator(`text=${section}`)).toBeVisible();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('Vérifier le contenu de la page Canvas', async ({ page }) => {
 | 
				
			||||||
 | 
					  await page.goto('http://localhost:5173/canvas');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Attendre le chargement des éléments
 | 
				
			||||||
 | 
					  await page.waitForSelector('.canvas');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Vérifier le titre
 | 
				
			||||||
 | 
					  await expect(page.locator('h1')).toHaveText('Page Canvas');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Nouveau sélecteur plus précis
 | 
				
			||||||
 | 
					  const canvasItems = page.locator('.canvas > div'); // Sélectionne les div directes dans .canvas
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Vérifier le nombre d'éléments
 | 
				
			||||||
 | 
					  await expect(canvasItems).toHaveCount(9);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Contenu attendu
 | 
				
			||||||
 | 
					  const expectedContent = [
 | 
				
			||||||
 | 
					    { title: '1. Problème', description: '3 problèmes essentiels à résoudre pour le client' },
 | 
				
			||||||
 | 
					    { title: '2. Segments', description: 'Les segments de clientèle visés' },
 | 
				
			||||||
 | 
					    { title: '3. Valeur', description: 'La proposition de valeur' },
 | 
				
			||||||
 | 
					    { title: '4. Solution', description: 'Les solutions proposées' },
 | 
				
			||||||
 | 
					    { title: '5. Avantage', description: 'Les avantages concurrentiels' },
 | 
				
			||||||
 | 
					    { title: '6. Canaux', description: 'Les canaux de distribution' },
 | 
				
			||||||
 | 
					    { title: '7. Indicateurs', description: 'Les indicateurs clés de performance' },
 | 
				
			||||||
 | 
					    { title: '8. Coûts', description: 'Les coûts associés' },
 | 
				
			||||||
 | 
					    { title: '9. Revenus', description: 'Les sources de revenus' }
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Vérifier chaque élément
 | 
				
			||||||
 | 
					  for (let i = 0; i < expectedContent.length; i++) {
 | 
				
			||||||
 | 
					    const item = canvasItems.nth(i);
 | 
				
			||||||
 | 
					    await expect(item.locator('h3')).toHaveText(expectedContent[i].title);
 | 
				
			||||||
 | 
					    await expect(item.locator('p')).toHaveText(expectedContent[i].description);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test.describe('Tests de la page Home', () => {
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  test('Vérifier la présence des projets', async ({ page }) => {
 | 
				
			||||||
 | 
					      await page.goto('http://localhost:5173');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Vérifier les titres des projets avec getByRole pour éviter les doublons
 | 
				
			||||||
 | 
					      await expect(page.getByRole('heading', { name: 'Projet Alpha' })).toBeVisible();
 | 
				
			||||||
 | 
					      await expect(page.getByRole('heading', { name: 'Projet Beta' })).toBeVisible();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('Vérifier les membres des projets', async ({ page }) => {
 | 
				
			||||||
 | 
					      await page.goto('http://localhost:5173');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const membresAlpha = ['Alice', 'Bob', 'Charlie'];
 | 
				
			||||||
 | 
					      const membresBeta = ['David', 'Eve', 'Frank'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      for (const membre of membresAlpha) {
 | 
				
			||||||
 | 
					          await expect(page.getByText(membre)).toBeVisible();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      for (const membre of membresBeta) {
 | 
				
			||||||
 | 
					          await expect(page.getByText(membre)).toBeVisible();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('Vérifier les boutons Contact', async ({ page }) => {
 | 
				
			||||||
 | 
					      await page.goto('http://localhost:5173');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Vérifier que les boutons "Contact" existent et sont visibles
 | 
				
			||||||
 | 
					      const contactButtons = await page.locator('button:has-text("Contact")').count();
 | 
				
			||||||
 | 
					      expect(contactButtons).toBe(2); // Vérifie qu'il y a bien 2 boutons Contact
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('Vérifier la table des rendez-vous', async ({ page }) => {
 | 
				
			||||||
 | 
					      await page.goto('http://localhost:5173');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await expect(page.getByRole('heading', { name: 'Rendez-vous' })).toBeVisible();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Vérifier la première ligne du tableau
 | 
				
			||||||
 | 
					      await expect(page.locator('table').getByRole('cell', { name: 'Projet Alpha' })).toBeVisible();
 | 
				
			||||||
 | 
					      await expect(page.getByText('2025-03-10')).toBeVisible();
 | 
				
			||||||
 | 
					      await expect(page.getByText('P106')).toBeVisible();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Vérifier la deuxième ligne du tableau
 | 
				
			||||||
 | 
					      await expect(page.locator('table').getByRole('cell', { name: 'Projet Beta' })).toBeVisible();
 | 
				
			||||||
 | 
					      await expect(page.getByText('2025-04-15')).toBeVisible();
 | 
				
			||||||
 | 
					      await expect(page.getByText('Td10')).toBeVisible();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
		Reference in New Issue
	
	Block a user