diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 6ebfb08..b1eaab8 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -4,70 +4,6 @@ on: pull_request: jobs: - checkstyle: - name: "Checkstyle Main" - runs-on: "vps-4" - container: - image: "cimg/openjdk:23.0-node" - steps: - - name: "Checkout" - uses: actions/checkout@v3 - - name: Setup Java 23 - uses: actions/setup-java@v3 - with: - distribution: "temurin" - java-version: "23" - - name: "Cache Gradle dependencies" - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: gradle-${{ runner.os }}-common - restore-keys: | - gradle-${{ runner.os }}- - - name: "Prepare Gradle" - working-directory: ./backend - run: gradle clean - - name: "Check" - working-directory: ./backend - run: gradle checkstyleMain - - name: "Stop Gradle" - working-directory: ./backend - run: gradle --stop - - test: - name: "Test" - runs-on: "vps-4" - container: - image: "cimg/openjdk:22.0-node" - steps: - - name: "Checkout" - uses: actions/checkout@v3 - - name: Setup Java 22 - uses: actions/setup-java@v3 - with: - distribution: "temurin" - java-version: "22" - - name: "Cache Gradle dependencies" - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: gradle-${{ runner.os }}-common - restore-keys: | - gradle-${{ runner.os }}- - - name: "Prepare Gradle" - working-directory: ./backend - run: gradle clean - - name: "Test" - working-directory: ./backend - run: gradle test - - name: "Stop Gradle" - working-directory: ./backend - run: gradle --stop - eslint: name: eslint runs-on: vps-4 @@ -78,14 +14,6 @@ jobs: uses: actions/checkout@v4 - name: Install bun uses: oven-sh/setup-bun@v2 - - uses: actions/cache@v3 - working-directory: ./frontend - with: - path: | - frontend/node_modules/ - key: ${{ runner.os }}-bun- - restore-keys: | - ${{ runner.os }}-bun- - name: Install dependencies run: | cd frontend @@ -105,14 +33,6 @@ jobs: uses: actions/checkout@v4 - name: Install bun uses: oven-sh/setup-bun@v2 - - uses: actions/cache@v3 - working-directory: ./frontend - with: - path: | - frontend/node_modules/ - key: ${{ runner.os }}-bun- - restore-keys: | - ${{ runner.os }}-bun- - name: Install dependencies run: | cd frontend @@ -132,22 +52,6 @@ jobs: uses: actions/checkout@v4 - name: Install bun uses: oven-sh/setup-bun@v2 - - uses: actions/cache@v3 - working-directory: ./frontend - with: - path: | - frontend/node_modules/ - key: ${{ runner.os }}-bun- - restore-keys: | - ${{ runner.os }}-bun- - - uses: actions/cache@v3 - working-directory: ./frontend - with: - path: | - frontend/dist/ - key: ${{ runner.os }}-dist- - restore-keys: | - ${{ runner.os }}-dist- - name: Install dependencies run: | cd frontend diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index a2060f7..f10394a 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -2,21 +2,6 @@ plugins { java id("org.springframework.boot") version "3.3.3" id("io.spring.dependency-management") version "1.1.6" - id("checkstyle") -} - -checkstyle { - configFile = file("$rootDir/config/checkstyle/checkstyle.xml") -} - -tasks.withType { - reports { - // Disable HTML report - html.required.set(false) - - // Disable XML report - xml.required.set(false) - } } group = "de.szut" @@ -24,7 +9,7 @@ version = "0.0.1-SNAPSHOT" java { toolchain { - languageVersion = JavaLanguageVersion.of(23) + languageVersion = JavaLanguageVersion.of(22) } } @@ -39,7 +24,6 @@ repositories { } dependencies { - implementation("com.stripe:stripe-java:20.79.0") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-web") compileOnly("org.projectlombok:lombok") diff --git a/backend/config/checkstyle/checkstyle.xml b/backend/config/checkstyle/checkstyle.xml deleted file mode 100644 index bdcefb8..0000000 --- a/backend/config/checkstyle/checkstyle.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/backend/requests/user.http b/backend/requests/user.http deleted file mode 100644 index 5e7aa5c..0000000 --- a/backend/requests/user.http +++ /dev/null @@ -1,27 +0,0 @@ -### Get User by ID -GET http://localhost:8080/user/52cc0208-a3bd-4367-94c5-0404b016a003 -Authorization: Bearer {{token}} - -### Get current user with token -GET http://localhost:8080/user -Authorization: Bearer {{token}} - -### Create User -POST http://localhost:8080/user -Content-Type: application/json -Authorization: Bearer {{token}} - -{ - "keycloakId": "52cc0208-a3bd-4367-94c5-0404b016a003", - "username": "john.doe" -} - -### Deposit -POST http://localhost:8080/deposit/checkout -Content-Type: application/json -Origin: http://localhost:8080 -Authorization: Bearer {{token}} - -{ - "amount": 60.12 -} \ No newline at end of file diff --git a/backend/src/main/java/de/szut/casino/config/OpenAPIConfiguration.java b/backend/src/main/java/de/szut/casino/config/OpenAPIConfiguration.java index 7af90b9..f102caf 100644 --- a/backend/src/main/java/de/szut/casino/config/OpenAPIConfiguration.java +++ b/backend/src/main/java/de/szut/casino/config/OpenAPIConfiguration.java @@ -58,4 +58,4 @@ public class OpenAPIConfiguration { } -} +} \ No newline at end of file diff --git a/backend/src/main/java/de/szut/casino/deposit/DepositController.java b/backend/src/main/java/de/szut/casino/deposit/DepositController.java deleted file mode 100644 index 6bc0fbc..0000000 --- a/backend/src/main/java/de/szut/casino/deposit/DepositController.java +++ /dev/null @@ -1,44 +0,0 @@ -package de.szut.casino.deposit; - -import com.stripe.Stripe; -import com.stripe.exception.StripeException; -import com.stripe.model.checkout.Session; -import com.stripe.param.checkout.SessionCreateParams; -import de.szut.casino.deposit.dto.AmountDto; -import de.szut.casino.deposit.dto.SessionIdDto; -import jakarta.validation.Valid; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@RestController -public class DepositController { - - @Value("${stripe.secret.key}") - private String stripeKey; - - @Value("${app.frontend-host}") - private String frontendHost; - - @PostMapping("/deposit/checkout") - public ResponseEntity checkout(@RequestBody @Valid AmountDto amountDto) throws StripeException { - Stripe.apiKey = stripeKey; - - SessionCreateParams params = SessionCreateParams.builder() - .addLineItem(SessionCreateParams.LineItem.builder() - .setAmount((long) amountDto.getAmount() * 100) - .setCurrency("EUR") - .setQuantity(1L) - .setName("Einzahlung") - .build()) - .setSuccessUrl(frontendHost+"/home?success=true") - .setCancelUrl(frontendHost+"/home?success=false") - .setMode(SessionCreateParams.Mode.PAYMENT) - .build(); - - Session session = Session.create(params); - - return ResponseEntity.ok(new SessionIdDto(session.getId())); - } -} - diff --git a/backend/src/main/java/de/szut/casino/deposit/dto/AmountDto.java b/backend/src/main/java/de/szut/casino/deposit/dto/AmountDto.java deleted file mode 100644 index 1f1708e..0000000 --- a/backend/src/main/java/de/szut/casino/deposit/dto/AmountDto.java +++ /dev/null @@ -1,17 +0,0 @@ -package de.szut.casino.deposit.dto; - -import jakarta.validation.constraints.Min; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Setter -@Getter -@AllArgsConstructor -@NoArgsConstructor -public class AmountDto { - @Min(50) - private double amount; -} - diff --git a/backend/src/main/java/de/szut/casino/deposit/dto/SessionIdDto.java b/backend/src/main/java/de/szut/casino/deposit/dto/SessionIdDto.java deleted file mode 100644 index b3de1bc..0000000 --- a/backend/src/main/java/de/szut/casino/deposit/dto/SessionIdDto.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.szut.casino.deposit.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Setter -@Getter -@AllArgsConstructor -@NoArgsConstructor -public class SessionIdDto { - private String sessionId; -} - diff --git a/backend/src/main/java/de/szut/casino/security/KeycloakLogoutHandler.java b/backend/src/main/java/de/szut/casino/security/KeycloakLogoutHandler.java index 5e08794..2403c82 100644 --- a/backend/src/main/java/de/szut/casino/security/KeycloakLogoutHandler.java +++ b/backend/src/main/java/de/szut/casino/security/KeycloakLogoutHandler.java @@ -45,4 +45,4 @@ public class KeycloakLogoutHandler implements LogoutHandler { } } -} +} \ No newline at end of file diff --git a/backend/src/main/java/de/szut/casino/security/KeycloakSecurityConfig.java b/backend/src/main/java/de/szut/casino/security/KeycloakSecurityConfig.java index 6be8b83..8ddcbcb 100644 --- a/backend/src/main/java/de/szut/casino/security/KeycloakSecurityConfig.java +++ b/backend/src/main/java/de/szut/casino/security/KeycloakSecurityConfig.java @@ -14,6 +14,7 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; import org.springframework.security.web.session.HttpSessionEventPublisher; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import java.util.ArrayList; import java.util.List; @@ -23,6 +24,10 @@ import java.util.Map; @EnableWebSecurity class KeycloakSecurityConfig { + private static final String GROUPS = "groups"; + private static final String REALM_ACCESS_CLAIM = "realm_access"; + private static final String ROLES_CLAIM = "roles"; + private final KeycloakLogoutHandler keycloakLogoutHandler; KeycloakSecurityConfig(KeycloakLogoutHandler keycloakLogoutHandler) { diff --git a/backend/src/main/java/de/szut/casino/user/UserController.java b/backend/src/main/java/de/szut/casino/user/UserController.java deleted file mode 100644 index 4d232ac..0000000 --- a/backend/src/main/java/de/szut/casino/user/UserController.java +++ /dev/null @@ -1,62 +0,0 @@ -package de.szut.casino.user; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RestController; - -import de.szut.casino.user.dto.CreateUserDto; -import de.szut.casino.user.dto.GetUserDto; -import jakarta.validation.Valid; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@RestController -public class UserController { - - @Autowired - private UserService userService; - - @GetMapping("/user/{id}") - public ResponseEntity getUser(@PathVariable String id) { - if (id == null || !userService.exists(id)) { - return ResponseEntity.notFound().build(); - } - - return ResponseEntity.ok(userService.getUser(id)); - } - - @PostMapping("/user") - public ResponseEntity createUser(@RequestBody @Valid CreateUserDto userData) { - if (userService.exists(userData.getKeycloakId())) { - - return this.redirect("/user/" + userData.getKeycloakId()); - } - - return ResponseEntity.ok(userService.createUser(userData)); - } - - @GetMapping("/user") - public ResponseEntity getCurrentUser(@RequestHeader("Authorization") String token) { - GetUserDto userData = userService.getCurrentUser(token); - - if (userData == null) { - return ResponseEntity.notFound().build(); - } - - return ResponseEntity.ok(userData); - } - - private ResponseEntity redirect(String route) { - HttpHeaders headers = new HttpHeaders(); - headers.add("Location", route); - - return new ResponseEntity<>(headers, HttpStatus.FOUND); - } -} diff --git a/backend/src/main/java/de/szut/casino/user/UserEntity.java b/backend/src/main/java/de/szut/casino/user/UserEntity.java deleted file mode 100644 index 83d8f44..0000000 --- a/backend/src/main/java/de/szut/casino/user/UserEntity.java +++ /dev/null @@ -1,32 +0,0 @@ -package de.szut.casino.user; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import java.math.BigDecimal; - -@Setter -@Getter -@Entity -@NoArgsConstructor -public class UserEntity { - @Id - @GeneratedValue - private Long id; - @Column(unique = true) - private String keycloakId; - private String username; - - @Column(precision = 19, scale = 2) - private BigDecimal balance; - - public UserEntity(String keycloakId, String username, BigDecimal balance) { - this.keycloakId = keycloakId; - this.username = username; - this.balance = balance; - } -} diff --git a/backend/src/main/java/de/szut/casino/user/UserMappingService.java b/backend/src/main/java/de/szut/casino/user/UserMappingService.java deleted file mode 100644 index 80f5546..0000000 --- a/backend/src/main/java/de/szut/casino/user/UserMappingService.java +++ /dev/null @@ -1,18 +0,0 @@ -package de.szut.casino.user; - -import de.szut.casino.user.dto.CreateUserDto; -import de.szut.casino.user.dto.GetUserDto; -import org.springframework.stereotype.Service; - -import java.math.BigDecimal; - -@Service -public class UserMappingService { - public GetUserDto mapToGetUserDto(UserEntity user) { - return new GetUserDto(user.getKeycloakId(), user.getUsername(), user.getBalance()); - } - - public UserEntity mapToUserEntity(CreateUserDto createUserDto) { - return new UserEntity(createUserDto.getKeycloakId(), createUserDto.getUsername(), BigDecimal.ZERO); } -} - diff --git a/backend/src/main/java/de/szut/casino/user/UserRepository.java b/backend/src/main/java/de/szut/casino/user/UserRepository.java deleted file mode 100644 index aaa5752..0000000 --- a/backend/src/main/java/de/szut/casino/user/UserRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.szut.casino.user; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Service; - -import java.util.Optional; - -@Service -public interface UserRepository extends JpaRepository { - @Query("SELECT u FROM UserEntity u WHERE u.keycloakId = ?1") - Optional findOneByKeycloakId(String keycloakId); - - boolean existsByKeycloakId(String keycloakId); -} diff --git a/backend/src/main/java/de/szut/casino/user/UserService.java b/backend/src/main/java/de/szut/casino/user/UserService.java deleted file mode 100644 index 724962e..0000000 --- a/backend/src/main/java/de/szut/casino/user/UserService.java +++ /dev/null @@ -1,64 +0,0 @@ -package de.szut.casino.user; - -import java.util.Optional; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; - -import de.szut.casino.user.dto.CreateUserDto; -import de.szut.casino.user.dto.GetUserDto; -import de.szut.casino.user.dto.KeycloakUserDto; - -@Service -public class UserService { - @Autowired - private UserRepository userRepository; - - @Autowired - private RestTemplate http; - - @Autowired - private UserMappingService mappingService; - - public UserEntity createUser(CreateUserDto createUserDto) { - UserEntity user = mappingService.mapToUserEntity(createUserDto); - userRepository.save(user); - - return user; - } - - public GetUserDto getUser(String keycloakId) { - Optional user = this.userRepository.findOneByKeycloakId(keycloakId); - - return user.map(userEntity -> mappingService.mapToGetUserDto(userEntity)).orElse(null); - } - - public GetUserDto getCurrentUser(String token) { - KeycloakUserDto userData = getKeycloakUserInfo(token); - - if (userData == null) { - return null; - } - Optional user = this.userRepository.findOneByKeycloakId(userData.getSub()); - - return user.map(userEntity -> mappingService.mapToGetUserDto(userEntity)).orElse(null); - - } - - private KeycloakUserDto getKeycloakUserInfo(String token) { - HttpHeaders headers = new HttpHeaders(); - headers.set("Authorization", token); - ResponseEntity response = this.http.exchange("http://localhost:9090/realms/LF12/protocol/openid-connect/userinfo", HttpMethod.GET, new HttpEntity<>(headers), KeycloakUserDto.class); - - return response.getBody(); - } - - public boolean exists(String keycloakId) { - return userRepository.existsByKeycloakId(keycloakId); - } -} diff --git a/backend/src/main/java/de/szut/casino/user/dto/CreateUserDto.java b/backend/src/main/java/de/szut/casino/user/dto/CreateUserDto.java deleted file mode 100644 index ff28427..0000000 --- a/backend/src/main/java/de/szut/casino/user/dto/CreateUserDto.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.szut.casino.user.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -public class CreateUserDto { - private String keycloakId; - private String username; -} diff --git a/backend/src/main/java/de/szut/casino/user/dto/GetUserDto.java b/backend/src/main/java/de/szut/casino/user/dto/GetUserDto.java deleted file mode 100644 index 8521029..0000000 --- a/backend/src/main/java/de/szut/casino/user/dto/GetUserDto.java +++ /dev/null @@ -1,18 +0,0 @@ -package de.szut.casino.user.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.math.BigDecimal; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -public class GetUserDto { - private String keycloakId; - private String username; - private BigDecimal balance; -} diff --git a/backend/src/main/java/de/szut/casino/user/dto/KeycloakUserDto.java b/backend/src/main/java/de/szut/casino/user/dto/KeycloakUserDto.java deleted file mode 100644 index 4238e13..0000000 --- a/backend/src/main/java/de/szut/casino/user/dto/KeycloakUserDto.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.szut.casino.user.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -public class KeycloakUserDto { - private String sub; - private String preferred_username; -} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index c7ccadf..d668f7a 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1,10 +1,9 @@ -spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:5432/postgresdb +spring.datasource.url=jdbc:postgresql://${DB_HOST:-localhost}:5432/postgresdb spring.datasource.username=postgres_user spring.datasource.password=postgres_pass server.port=8080 spring.jpa.hibernate.ddl-auto=create-drop -stripe.secret.key=${STRIPE_SECRET_KEY:sk_test_51QrePYIvCfqz7ANgqam8rEwWcMeKiLOof3j6SCMgu2sl4sESP45DJxca16mWcYo1sQaiBv32CMR6Z4AAAGQPCJo300ubuZKO8I} -app.frontend-host=http://localhost:4200 + spring.application.name=lf12_starter #client registration configuration diff --git a/backend/src/test/java/de/szut/casino/health/HealthControllerTest.java b/backend/src/test/java/de/szut/casino/health/HealthControllerTest.java deleted file mode 100644 index 1214c7a..0000000 --- a/backend/src/test/java/de/szut/casino/health/HealthControllerTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package de.szut.casino.health; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.test.web.servlet.MockMvc; - -@WebMvcTest(HealthController.class) -@AutoConfigureMockMvc(addFilters = false) -public class HealthControllerTest { - - @Autowired - private MockMvc mockMvc; - - @Test - void healthCheckReturnsUpStatus() throws Exception { - mockMvc.perform(get("/health")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.status").value("UP")); - } -} \ No newline at end of file diff --git a/backend/src/test/java/de/szut/casino/user/UserControllerTest.java b/backend/src/test/java/de/szut/casino/user/UserControllerTest.java deleted file mode 100644 index 2addb43..0000000 --- a/backend/src/test/java/de/szut/casino/user/UserControllerTest.java +++ /dev/null @@ -1,122 +0,0 @@ -package de.szut.casino.user; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import de.szut.casino.user.dto.CreateUserDto; -import de.szut.casino.user.dto.GetUserDto; - -@WebMvcTest(UserController.class) -@AutoConfigureMockMvc(addFilters = false) -public class UserControllerTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @MockBean - private UserService userService; - - private GetUserDto getUserDto; - private CreateUserDto createUserDto; - private UserEntity testUser; - private final String TEST_ID = "test-id-123"; - private final String AUTH_TOKEN = "Bearer test-token"; - - @BeforeEach - void setUp() { - getUserDto = new GetUserDto(); - getUserDto.setKeycloakId(TEST_ID); - getUserDto.setUsername("testuser"); - - testUser = new UserEntity(); - testUser.setKeycloakId(TEST_ID); - testUser.setUsername("testuser"); - - createUserDto = new CreateUserDto(); - createUserDto.setKeycloakId(TEST_ID); - createUserDto.setUsername("testuser"); - } - - @Test - void getUserByIdSuccess() throws Exception { - when(userService.exists(TEST_ID)).thenReturn(true); - when(userService.getUser(TEST_ID)).thenReturn(getUserDto); - - mockMvc.perform(get("/user/" + TEST_ID)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.keycloakId").value(TEST_ID)) - .andExpect(jsonPath("$.username").value("testuser")); - } - - @Test - void getUserByIdNotFound() throws Exception { - when(userService.exists(TEST_ID)).thenReturn(false); - - mockMvc.perform(get("/user/" + TEST_ID)) - .andExpect(status().isNotFound()); - } - - @Test - void createUserSuccess() throws Exception { - when(userService.exists(TEST_ID)).thenReturn(false); - when(userService.createUser(any(CreateUserDto.class))).thenReturn(testUser); - - mockMvc.perform(post("/user") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(createUserDto))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.keycloakId").value(TEST_ID)) - .andExpect(jsonPath("$.username").value("testuser")); - } - - @Test - void createUserAlreadyExists() throws Exception { - when(userService.exists(TEST_ID)).thenReturn(true); - - mockMvc.perform(post("/user") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(createUserDto))) - .andExpect(status().isFound()) - .andExpect(header().string("Location", "/user/" + TEST_ID)); - } - - @Test - void getCurrentUserSuccess() throws Exception { - when(userService.getCurrentUser(AUTH_TOKEN)).thenReturn(getUserDto); - - mockMvc.perform(get("/user") - .header("Authorization", AUTH_TOKEN)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.keycloakId").value(TEST_ID)) - .andExpect(jsonPath("$.username").value("testuser")); - } - - @Test - void getCurrentUserNotFound() throws Exception { - when(userService.getCurrentUser(anyString())).thenReturn(null); - - mockMvc.perform(get("/user") - .header("Authorization", AUTH_TOKEN)) - .andExpect(status().isNotFound()); - } -} \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2442d38..0b7e617 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,3 +1,5 @@ +version: '3' + volumes: keycloak_data: postgres_data_keycloak_db: diff --git a/frontend/bun.lock b/frontend/bun.lock index 5b0629c..dd78f81 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -5,7 +5,6 @@ "name": "lf10-starter2024", "dependencies": { "@angular/animations": "^18.2.0", - "@angular/cdk": "~18.2.14", "@angular/common": "^18.2.0", "@angular/compiler": "^18.2.0", "@angular/core": "^18.2.0", @@ -13,13 +12,7 @@ "@angular/platform-browser": "^18.2.0", "@angular/platform-browser-dynamic": "^18.2.0", "@angular/router": "^18.2.0", - "@fortawesome/angular-fontawesome": "^1.0.0", - "@fortawesome/fontawesome-svg-core": "^6.7.2", - "@fortawesome/free-brands-svg-icons": "^6.7.2", - "@fortawesome/free-solid-svg-icons": "^6.7.2", - "@stripe/stripe-js": "^5.6.0", "@tailwindcss/postcss": "^4.0.3", - "gsap": "^3.12.7", "keycloak-angular": "^16.0.1", "keycloak-js": "^25.0.5", "postcss": "^8.5.1", @@ -79,8 +72,6 @@ "@angular/build": ["@angular/build@18.2.14", "", { "dependencies": { "@ampproject/remapping": "2.3.0", "@angular-devkit/architect": "0.1802.14", "@babel/core": "7.25.2", "@babel/helper-annotate-as-pure": "7.24.7", "@babel/helper-split-export-declaration": "7.24.7", "@babel/plugin-syntax-import-attributes": "7.24.7", "@inquirer/confirm": "3.1.22", "@vitejs/plugin-basic-ssl": "1.1.0", "browserslist": "^4.23.0", "critters": "0.0.24", "esbuild": "0.23.0", "fast-glob": "3.3.2", "https-proxy-agent": "7.0.5", "listr2": "8.2.4", "lmdb": "3.0.13", "magic-string": "0.30.11", "mrmime": "2.0.0", "parse5-html-rewriting-stream": "7.0.0", "picomatch": "4.0.2", "piscina": "4.6.1", "rollup": "4.22.4", "sass": "1.77.6", "semver": "7.6.3", "vite": "5.4.14", "watchpack": "2.4.1" }, "peerDependencies": { "@angular/compiler-cli": "^18.0.0", "@angular/localize": "^18.0.0", "@angular/platform-server": "^18.0.0", "@angular/service-worker": "^18.0.0", "less": "^4.2.0", "postcss": "^8.4.0", "tailwindcss": "^2.0.0 || ^3.0.0", "typescript": ">=5.4 <5.6" }, "optionalPeers": ["@angular/localize", "@angular/platform-server", "@angular/service-worker", "less", "postcss", "tailwindcss"] }, "sha512-9g24Oe/ZLULacW3hEpRCjSZIJPJTzN5BeFbA27epSV5NsrQOoeUGsEpRs90Zmt6eReO0fW1BGshWRoZtpSedcw=="], - "@angular/cdk": ["@angular/cdk@18.2.14", "", { "dependencies": { "tslib": "^2.3.0" }, "optionalDependencies": { "parse5": "^7.1.2" }, "peerDependencies": { "@angular/common": "^18.0.0 || ^19.0.0", "@angular/core": "^18.0.0 || ^19.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "sha512-vDyOh1lwjfVk9OqoroZAP8pf3xxKUvyl+TVR8nJxL4c5fOfUFkD7l94HaanqKSRwJcI2xiztuu92IVoHn8T33Q=="], - "@angular/cli": ["@angular/cli@18.2.14", "", { "dependencies": { "@angular-devkit/architect": "0.1802.14", "@angular-devkit/core": "18.2.14", "@angular-devkit/schematics": "18.2.14", "@inquirer/prompts": "5.3.8", "@listr2/prompt-adapter-inquirer": "2.0.15", "@schematics/angular": "18.2.14", "@yarnpkg/lockfile": "1.1.0", "ini": "4.1.3", "jsonc-parser": "3.3.1", "listr2": "8.2.4", "npm-package-arg": "11.0.3", "npm-pick-manifest": "9.1.0", "pacote": "18.0.6", "resolve": "1.22.8", "semver": "7.6.3", "symbol-observable": "4.0.0", "yargs": "17.7.2" }, "bin": { "ng": "bin/ng.js" } }, "sha512-kWgRRQtJPkr8iwN7DMbTi3sXOnv7H5QhbU/GgD3nNX3D8YCSPmnby4PAE/P3wn7FsIK9JsSchsCt7MZ37Urh9A=="], "@angular/common": ["@angular/common@18.2.13", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/core": "18.2.13", "rxjs": "^6.5.3 || ^7.4.0" } }, "sha512-4ZqrNp1PoZo7VNvW+sbSc2CB2axP1sCH2wXl8B0wdjsj8JY1hF1OhuugwhpAHtGxqewed2kCXayE+ZJqSTV4jw=="], @@ -375,16 +366,6 @@ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.5", "", { "dependencies": { "@eslint/core": "^0.10.0", "levn": "^0.4.1" } }, "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A=="], - "@fortawesome/angular-fontawesome": ["@fortawesome/angular-fontawesome@1.0.0", "", { "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.7.1", "tslib": "^2.8.1" }, "peerDependencies": { "@angular/core": "^19.0.0" } }, "sha512-EC2fYuXIuw2ld1kzJi+zysWus6OeGGfLQtbh0hW9zyyq5aBo8ZJkcJKBsVQ8E6Mg7nHyTWaXn+sdcXTPDWz+UQ=="], - - "@fortawesome/fontawesome-common-types": ["@fortawesome/fontawesome-common-types@6.7.2", "", {}, "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg=="], - - "@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.7.2", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.2" } }, "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA=="], - - "@fortawesome/free-brands-svg-icons": ["@fortawesome/free-brands-svg-icons@6.7.2", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.2" } }, "sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q=="], - - "@fortawesome/free-solid-svg-icons": ["@fortawesome/free-solid-svg-icons@6.7.2", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.2" } }, "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA=="], - "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], @@ -549,8 +530,6 @@ "@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="], - "@stripe/stripe-js": ["@stripe/stripe-js@5.6.0", "", {}, "sha512-w8CEY73X/7tw2KKlL3iOk679V9bWseE4GzNz3zlaYxcTjmcmWOathRb0emgo/QQ3eoNzmq68+2Y2gxluAv3xGw=="], - "@tailwindcss/node": ["@tailwindcss/node@4.0.3", "", { "dependencies": { "enhanced-resolve": "^5.18.0", "jiti": "^2.4.2", "tailwindcss": "4.0.3" } }, "sha512-QsVJokOl0pJ4AbJV33D2npvLcHGPWi5MOSZtrtE0GT3tSx+3D0JE2lokLA8yHS1x3oCY/3IyRyy7XX6tmzid7A=="], "@tailwindcss/oxide": ["@tailwindcss/oxide@4.0.3", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.0.3", "@tailwindcss/oxide-darwin-arm64": "4.0.3", "@tailwindcss/oxide-darwin-x64": "4.0.3", "@tailwindcss/oxide-freebsd-x64": "4.0.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.0.3", "@tailwindcss/oxide-linux-arm64-musl": "4.0.3", "@tailwindcss/oxide-linux-x64-gnu": "4.0.3", "@tailwindcss/oxide-linux-x64-musl": "4.0.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.0.3", "@tailwindcss/oxide-win32-x64-msvc": "4.0.3" } }, "sha512-FFcp3VNvRjjmFA39ORM27g2mbflMQljhvM7gxBAujHxUy4LXlKa6yMF9wbHdTbPqTONiCyyOYxccvJyVyI/XBg=="], @@ -1069,8 +1048,6 @@ "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], - "gsap": ["gsap@3.12.7", "", {}, "sha512-V4GsyVamhmKefvcAKaoy0h6si0xX7ogwBoBSs2CTJwt7luW0oZzC0LhdkyuKV8PJAXr7Yaj8pMjCKD4GJ+eEMg=="], - "handle-thing": ["handle-thing@2.0.1", "", {}, "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg=="], "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], diff --git a/frontend/package.json b/frontend/package.json index 0ad0967..2a1f257 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,7 +14,6 @@ "private": true, "dependencies": { "@angular/animations": "^18.2.0", - "@angular/cdk": "~18.2.14", "@angular/common": "^18.2.0", "@angular/compiler": "^18.2.0", "@angular/core": "^18.2.0", @@ -22,13 +21,7 @@ "@angular/platform-browser": "^18.2.0", "@angular/platform-browser-dynamic": "^18.2.0", "@angular/router": "^18.2.0", - "@fortawesome/angular-fontawesome": "^1.0.0", - "@fortawesome/fontawesome-svg-core": "^6.7.2", - "@fortawesome/free-brands-svg-icons": "^6.7.2", - "@fortawesome/free-solid-svg-icons": "^6.7.2", - "@stripe/stripe-js": "^5.6.0", "@tailwindcss/postcss": "^4.0.3", - "gsap": "^3.12.7", "keycloak-angular": "^16.0.1", "keycloak-js": "^25.0.5", "postcss": "^8.5.1", diff --git a/frontend/public/blackjack.webp b/frontend/public/blackjack.webp deleted file mode 100644 index a791c14..0000000 Binary files a/frontend/public/blackjack.webp and /dev/null differ diff --git a/frontend/public/liars-dice.webp b/frontend/public/liars-dice.webp deleted file mode 100644 index df1fd1c..0000000 Binary files a/frontend/public/liars-dice.webp and /dev/null differ diff --git a/frontend/public/lootbox.webp b/frontend/public/lootbox.webp deleted file mode 100644 index 710deed..0000000 Binary files a/frontend/public/lootbox.webp and /dev/null differ diff --git a/frontend/public/plinko.webp b/frontend/public/plinko.webp deleted file mode 100644 index c11370b..0000000 Binary files a/frontend/public/plinko.webp and /dev/null differ diff --git a/frontend/public/poker.webp b/frontend/public/poker.webp deleted file mode 100644 index 9c60024..0000000 Binary files a/frontend/public/poker.webp and /dev/null differ diff --git a/frontend/public/slots.webp b/frontend/public/slots.webp deleted file mode 100644 index 5cf639a..0000000 Binary files a/frontend/public/slots.webp and /dev/null differ diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index 41260d2..0680b43 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -1,6 +1 @@ -
-
- -
- -
+ diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index bbc5fb6..7dea888 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -2,12 +2,11 @@ import { Component, ChangeDetectionStrategy } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterOutlet } from '@angular/router'; import { KeycloakAngularModule } from 'keycloak-angular'; -import { FooterComponent } from './shared/components/footer/footer.component'; @Component({ selector: 'app-root', standalone: true, - imports: [CommonModule, RouterOutlet, KeycloakAngularModule, FooterComponent], + imports: [CommonModule, RouterOutlet, KeycloakAngularModule], providers: [], templateUrl: './app.component.html', styleUrl: './app.component.css', diff --git a/frontend/src/app/app.config.ts b/frontend/src/app/app.config.ts index 217efd4..e7761ba 100644 --- a/frontend/src/app/app.config.ts +++ b/frontend/src/app/app.config.ts @@ -4,7 +4,6 @@ import { provideExperimentalZonelessChangeDetection, } from '@angular/core'; import { provideRouter } from '@angular/router'; -import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { routes } from './app.routes'; import { @@ -13,7 +12,6 @@ import { KeycloakService, } from 'keycloak-angular'; import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; -import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; export const initializeKeycloak = (keycloak: KeycloakService) => async () => keycloak.init({ @@ -27,7 +25,7 @@ export const initializeKeycloak = (keycloak: KeycloakService) => async () => onLoad: 'check-sso', silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html', checkLoginIframe: false, - redirectUri: window.location.origin + '/', + redirectUri: 'http://localhost:4200', }, }); @@ -39,7 +37,6 @@ export const appConfig: ApplicationConfig = { providers: [ provideRouter(routes), KeycloakAngularModule, - FontAwesomeModule, { provide: APP_INITIALIZER, useFactory: initializeApp, @@ -54,6 +51,5 @@ export const appConfig: ApplicationConfig = { useClass: KeycloakBearerInterceptor, multi: true, }, - provideAnimationsAsync(), ], }; diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 6fbef95..73ed20c 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -1,5 +1,6 @@ import { Routes } from '@angular/router'; import { LandingComponent } from './feature/landing/landing.component'; +import { HomeComponent } from './feature/home/home.component'; import { authGuard } from './auth.guard'; export const routes: Routes = [ @@ -7,13 +8,9 @@ export const routes: Routes = [ path: '', component: LandingComponent, }, - { - path: 'login/success', - loadComponent: () => import('./feature/login-success/login-success.component'), - }, { path: 'home', - loadComponent: () => import('./feature/home/home.component'), + component: HomeComponent, canActivate: [authGuard], }, ]; diff --git a/frontend/src/app/auth.guard.ts b/frontend/src/app/auth.guard.ts index 035ccc8..0743ff6 100644 --- a/frontend/src/app/auth.guard.ts +++ b/frontend/src/app/auth.guard.ts @@ -1,16 +1,23 @@ -import { CanActivateFn, Router } from '@angular/router'; +import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from '@angular/router'; import { inject } from '@angular/core'; import { KeycloakService } from 'keycloak-angular'; -export const authGuard: CanActivateFn = async () => { +export const authGuard: CanActivateFn = async ( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot +) => { const keycloakService = inject(KeycloakService); - const router = inject(Router); + const isLoggedIn = keycloakService.isLoggedIn(); - if (keycloakService.isLoggedIn()) { + if (isLoggedIn) { return true; } - router.navigate(['']); + const baseurl = window.location.origin; + + keycloakService.login({ + redirectUri: `${baseurl}${state.url}`, + }); return false; }; diff --git a/frontend/src/app/feature/deposit/deposit.component.html b/frontend/src/app/feature/deposit/deposit.component.html deleted file mode 100644 index e8e4dfb..0000000 --- a/frontend/src/app/feature/deposit/deposit.component.html +++ /dev/null @@ -1,25 +0,0 @@ -@if (isOpen) { - -} diff --git a/frontend/src/app/feature/deposit/deposit.component.ts b/frontend/src/app/feature/deposit/deposit.component.ts deleted file mode 100644 index f0f900a..0000000 --- a/frontend/src/app/feature/deposit/deposit.component.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - ElementRef, - EventEmitter, - inject, - Input, - OnInit, - Output, - ViewChild, - AfterViewInit, - OnDestroy, - OnChanges, - SimpleChanges, - ChangeDetectorRef, -} from '@angular/core'; -import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; -import { loadStripe, Stripe } from '@stripe/stripe-js'; -import { DepositService } from '../../service/deposit.service'; -import { debounceTime } from 'rxjs'; -import { environment } from '../../../environments/environment'; -import { NgIf } from '@angular/common'; -import { ModalAnimationService } from '../../shared/services/modal-animation.service'; -import gsap from 'gsap'; - -@Component({ - selector: 'app-deposit', - standalone: true, - imports: [ReactiveFormsModule, NgIf], - templateUrl: './deposit.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class DepositComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges { - @Input() isOpen = false; - @Output() close = new EventEmitter(); - @ViewChild('modalBg') modalBg!: ElementRef; - @ViewChild('modalCard') modalCard!: ElementRef; - protected form!: FormGroup; - protected errorMsg = ''; - private stripe: Stripe | null = null; - private service: DepositService = inject(DepositService); - private modalAnimationService: ModalAnimationService = inject(ModalAnimationService); - private cdr: ChangeDetectorRef = inject(ChangeDetectorRef); - - async ngOnInit() { - this.form = new FormGroup({ - amount: new FormControl(50, [Validators.min(50)]), - }); - - this.form.controls['amount'].valueChanges.pipe(debounceTime(1000)).subscribe((value) => { - if (value < 50) { - this.errorMsg = 'Minimum Einzahlungsbetrag ist 50€'; - } - }); - - this.stripe = await loadStripe(environment.STRIPE_KEY); - } - - ngAfterViewInit() { - if (this.isOpen) { - this.openModal(); - } - } - - ngOnChanges(changes: SimpleChanges) { - if (changes['isOpen']) { - this.cdr.detectChanges(); - setTimeout(() => { - if (this.modalBg?.nativeElement && this.modalCard?.nativeElement) { - if (changes['isOpen'].currentValue) { - this.openModal(); - } else { - this.closeModal(); - } - } - }, 0); - } - } - - ngOnDestroy() { - gsap.killTweensOf([this.modalBg?.nativeElement, this.modalCard?.nativeElement]); - } - - private openModal() { - if (this.modalBg?.nativeElement && this.modalCard?.nativeElement) { - this.modalAnimationService.openModal( - this.modalCard.nativeElement, - this.modalBg.nativeElement - ); - } - } - - submit() { - if (!this.stripe) { - this.errorMsg = 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.'; - return; - } - if (!this.form.valid) { - this.errorMsg = 'Bitte geben Sie einen gültigen Betrag ein.'; - return; - } - - this.service.handleDeposit(this.form.value.amount as number).subscribe(({ sessionId }) => { - this.stripe?.redirectToCheckout({ sessionId }); - }); - } - - public closeModal() { - if (this.modalBg?.nativeElement && this.modalCard?.nativeElement) { - this.modalAnimationService.closeModal( - this.modalCard.nativeElement, - this.modalBg.nativeElement, - () => this.close.emit() - ); - } - } -} diff --git a/frontend/src/app/feature/home/home.component.html b/frontend/src/app/feature/home/home.component.html index fb2424a..6a710c5 100644 --- a/frontend/src/app/feature/home/home.component.html +++ b/frontend/src/app/feature/home/home.component.html @@ -1,110 +1,29 @@ - -
-
-
+ -
-
-
-

Beliebte Spiele

-
- - -
-
- -
-
-
-
- -
-
-

{{ game.name }}

- -
-
-
-
-
-
- -
-

Alle Spiele

-
-
-
- -
-
-

{{ game.name }}

- -
-
-
-
-
-
-
- -
-
-

Konto

-
- - - - -
-
- - - -
-

Letzte Transaktionen

-
-
-
-

{{ transaction.type }}

-

{{ transaction.date }}

-
- - {{ transaction.amount | currency: 'EUR' }} - -
-
-
-
+
+
+

Spiel Vorschau

+

Spiel Name

+ +
+
+

Spiel Vorschau

+

Spiel Name

+ +
+
+

Spiel Vorschau

+

Spiel Name

+
diff --git a/frontend/src/app/feature/home/home.component.ts b/frontend/src/app/feature/home/home.component.ts index 88fe552..06aa423 100644 --- a/frontend/src/app/feature/home/home.component.ts +++ b/frontend/src/app/feature/home/home.component.ts @@ -1,80 +1,19 @@ -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { NavbarComponent } from '../../shared/components/navbar/navbar.component'; -import { CurrencyPipe, NgFor } from '@angular/common'; -import { Game } from '../../model/Game'; -import { Transaction } from '../../model/Transaction'; -import { DepositComponent } from '../deposit/deposit.component'; -import { ConfirmationComponent } from '../../shared/components/confirmation/confirmation.component'; -import { ActivatedRoute } from '@angular/router'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { KeycloakService } from 'keycloak-angular'; @Component({ selector: 'app-homepage', standalone: true, - imports: [NavbarComponent, CurrencyPipe, NgFor, DepositComponent, ConfirmationComponent], + imports: [], templateUrl: './home.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export default class HomeComponent implements OnInit { - isDepositModalOpen = false; - isDepositSuccessful = false; +export class HomeComponent { + private keycloakService: KeycloakService = inject(KeycloakService); - constructor(public route: ActivatedRoute) {} + logout() { + const baseUrl = window.location.origin; - ngOnInit() { - this.isDepositSuccessful = this.route.snapshot.queryParams['success'] == 'true'; - if (this.isDepositSuccessful) { - this.openDepositConfirmationModal(); - } - } - - featuredGames: Game[] = [ - { - id: '1', - name: 'Poker', - image: '/poker.webp', - }, - { - id: '2', - name: 'Blackjack', - image: '/blackjack.webp', - }, - { - id: '3', - name: 'Slots', - image: '/slots.webp', - }, - { - id: '4', - name: 'Plinko', - image: '/plinko.webp', - }, - { - id: '5', - name: 'Liars Dice', - image: '/liars-dice.webp', - }, - { - id: '6', - name: 'Lootboxen', - image: '/lootbox.webp', - }, - ]; - - allGames: Game[] = [...this.featuredGames]; - - recentTransactions: Transaction[] = []; - - openDepositModal() { - this.isDepositModalOpen = true; - } - closeDepositModal() { - this.isDepositModalOpen = false; - } - - openDepositConfirmationModal() { - this.isDepositSuccessful = true; - } - closeDepositConfirmationModal() { - this.isDepositSuccessful = false; + this.keycloakService.logout(`${baseUrl}/`); } } diff --git a/frontend/src/app/feature/landing/landing.component.html b/frontend/src/app/feature/landing/landing.component.html index bffa9ad..80d5974 100644 --- a/frontend/src/app/feature/landing/landing.component.html +++ b/frontend/src/app/feature/landing/landing.component.html @@ -1,142 +1,5 @@ - - -
-
-
-
-

- Willkommensbonus -

-
200% bis zu 500€
-

+ 200 Freispiele

- - -
- -
-

Beliebte Spiele

-
-
-
-
-
-
-

Slots

-

Klassische Spielautomaten

- -
-
-
-
-

Plinko

-

Spannendes Geschicklichkeitsspiel

- -
-
- -
- -
-
-
-

Poker

-

Texas Hold'em & mehr

- -
-
-
-
-

Liars Dice

-

Würfelspiel mit Strategie

- -
-
- -
-
-
- - - - -
- -
-
-
- -
-
-
50 Mio.€+
-
Ausgezahlt
-
- -
-
10 Mio.+
-
Spiele
-
- -
-
24/7
-
Support *
-
-
-
-
-
+@if (isLoggedIn) { + +} @else { + +} diff --git a/frontend/src/app/feature/landing/landing.component.ts b/frontend/src/app/feature/landing/landing.component.ts index 7fa92b6..d2e28c0 100644 --- a/frontend/src/app/feature/landing/landing.component.ts +++ b/frontend/src/app/feature/landing/landing.component.ts @@ -1,55 +1,21 @@ -import { ChangeDetectionStrategy, Component, OnInit, OnDestroy } from '@angular/core'; -import { NavbarComponent } from '../../shared/components/navbar/navbar.component'; -import { NgFor } from '@angular/common'; +import { Component, inject } from '@angular/core'; +import { KeycloakService } from 'keycloak-angular'; +import { RouterLink } from '@angular/router'; @Component({ - selector: 'app-landing-page', + selector: 'app-landing', standalone: true, - imports: [NavbarComponent, NgFor], + imports: [RouterLink], templateUrl: './landing.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, }) -export class LandingComponent implements OnInit, OnDestroy { - currentSlide = 0; - private autoplayInterval: ReturnType | undefined; +export class LandingComponent { + private keycloakService: KeycloakService = inject(KeycloakService); - ngOnInit() { - this.startAutoplay(); - } + public isLoggedIn = this.keycloakService.isLoggedIn(); - ngOnDestroy() { - this.stopAutoplay(); - } + public login() { + const baseUrl = window.location.origin; - prevSlide() { - this.currentSlide = this.currentSlide === 0 ? 1 : 0; - this.resetAutoplay(); - } - - nextSlide() { - this.currentSlide = this.currentSlide === 1 ? 0 : 1; - this.resetAutoplay(); - } - - goToSlide(index: number) { - this.currentSlide = index; - this.resetAutoplay(); - } - - private startAutoplay() { - this.autoplayInterval = setInterval(() => { - this.nextSlide(); - }, 5000); - } - - private stopAutoplay() { - if (this.autoplayInterval) { - clearInterval(this.autoplayInterval); - } - } - - private resetAutoplay() { - this.stopAutoplay(); - this.startAutoplay(); + this.keycloakService.login({ redirectUri: `${baseUrl}/home` }); } } diff --git a/frontend/src/app/feature/login-success/login-success.component.css b/frontend/src/app/feature/login-success/login-success.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/app/feature/login-success/login-success.component.html b/frontend/src/app/feature/login-success/login-success.component.html deleted file mode 100644 index ba9d449..0000000 --- a/frontend/src/app/feature/login-success/login-success.component.html +++ /dev/null @@ -1 +0,0 @@ -

Logging in...

diff --git a/frontend/src/app/feature/login-success/login-success.component.ts b/frontend/src/app/feature/login-success/login-success.component.ts deleted file mode 100644 index 067afe6..0000000 --- a/frontend/src/app/feature/login-success/login-success.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; -import { UserService } from '../../service/user.service'; -import { KeycloakService } from 'keycloak-angular'; -import { Router } from '@angular/router'; - -@Component({ - selector: 'app-login-success', - standalone: true, - imports: [], - templateUrl: './login-success.component.html', - styleUrl: './login-success.component.css', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export default class LoginSuccessComponent implements OnInit { - private userService: UserService = inject(UserService); - private keycloakService: KeycloakService = inject(KeycloakService); - private router: Router = inject(Router); - - async ngOnInit() { - const userProfile = await this.keycloakService.loadUserProfile(); - const user = await this.userService.getOrCreateUser(userProfile); - sessionStorage.setItem('user', JSON.stringify(user)); - - this.router.navigate(['home']); - } -} diff --git a/frontend/src/app/model/Game.ts b/frontend/src/app/model/Game.ts deleted file mode 100644 index a3f3e74..0000000 --- a/frontend/src/app/model/Game.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface Game { - id: string; - name: string; - image: string; -} diff --git a/frontend/src/app/model/Transaction.ts b/frontend/src/app/model/Transaction.ts deleted file mode 100644 index 21277b4..0000000 --- a/frontend/src/app/model/Transaction.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface Transaction { - id: string; - type: string; - amount: number; - date: string; -} diff --git a/frontend/src/app/model/User.ts b/frontend/src/app/model/User.ts deleted file mode 100644 index a579b7a..0000000 --- a/frontend/src/app/model/User.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface User { - keycloakId: string; - username: string; - balance: number; -} diff --git a/frontend/src/app/service/deposit.service.ts b/frontend/src/app/service/deposit.service.ts deleted file mode 100644 index 02c8ea1..0000000 --- a/frontend/src/app/service/deposit.service.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { inject, Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { Observable } from 'rxjs'; - -@Injectable({ - providedIn: 'root', -}) -export class DepositService { - private http: HttpClient = inject(HttpClient); - - handleDeposit(amount: number): Observable<{ sessionId: string }> { - return this.http.post<{ sessionId: string }>('/backend/deposit/checkout', { amount }); - } -} diff --git a/frontend/src/app/service/user.service.ts b/frontend/src/app/service/user.service.ts deleted file mode 100644 index 20573ff..0000000 --- a/frontend/src/app/service/user.service.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { inject, Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { KeycloakProfile } from 'keycloak-js'; -import { catchError, EMPTY, Observable } from 'rxjs'; -import { User } from '../model/User'; - -@Injectable({ - providedIn: 'root', -}) -export class UserService { - private http: HttpClient = inject(HttpClient); - - public getUser(id: string): Observable { - return this.http.get(`/backend/user/${id}`).pipe(catchError(() => EMPTY)); - } - - public getCurrentUser(): Observable { - return this.http.get('/backend/user').pipe(catchError(() => EMPTY)); - } - - public createUser(id: string, username: string): Observable { - return this.http.post('/backend/user', { - keycloakId: id, - username: username, - }); - } - - public async getOrCreateUser(userProfile: KeycloakProfile) { - if (userProfile.id == null) { - return; - } - return await this.getUser(userProfile.id) - .toPromise() - .then(async (user) => { - if (user) { - return user; - } - - return await this.createUser(userProfile.id ?? '', userProfile.username ?? '').toPromise(); - }); - } -} diff --git a/frontend/src/app/shared/components/confirmation/confirmation.component.html b/frontend/src/app/shared/components/confirmation/confirmation.component.html deleted file mode 100644 index 193ea9a..0000000 --- a/frontend/src/app/shared/components/confirmation/confirmation.component.html +++ /dev/null @@ -1,11 +0,0 @@ -@if (successful) { - -} diff --git a/frontend/src/app/shared/components/confirmation/confirmation.component.ts b/frontend/src/app/shared/components/confirmation/confirmation.component.ts deleted file mode 100644 index d407985..0000000 --- a/frontend/src/app/shared/components/confirmation/confirmation.component.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - Component, - ElementRef, - EventEmitter, - Input, - Output, - ViewChild, - AfterViewInit, - OnDestroy, -} from '@angular/core'; -import { ModalAnimationService } from '../../services/modal-animation.service'; -import gsap from 'gsap'; - -@Component({ - selector: 'app-confirmation', - standalone: true, - imports: [], - templateUrl: './confirmation.component.html', -}) -export class ConfirmationComponent implements AfterViewInit, OnDestroy { - @Input() successful = true; - @Output() close = new EventEmitter(); - @ViewChild('modalBg') modalBg!: ElementRef; - @ViewChild('modalCard') modalCard!: ElementRef; - - constructor(private modalAnimationService: ModalAnimationService) {} - - ngAfterViewInit() { - if (this.successful) { - this.openModal(); - } - } - - ngOnDestroy() { - gsap.killTweensOf([this.modalBg?.nativeElement, this.modalCard?.nativeElement]); - } - - private openModal() { - this.modalAnimationService.openModal(this.modalCard.nativeElement, this.modalBg.nativeElement); - } - - public closeModal() { - this.modalAnimationService.closeModal( - this.modalCard.nativeElement, - this.modalBg.nativeElement, - () => this.close.emit() - ); - } -} diff --git a/frontend/src/app/shared/components/footer/footer.component.html b/frontend/src/app/shared/components/footer/footer.component.html deleted file mode 100644 index e886973..0000000 --- a/frontend/src/app/shared/components/footer/footer.component.html +++ /dev/null @@ -1,80 +0,0 @@ -
-
-
- - - -
- -
- - - - - - - -
-
-
- -
-
- -
-
-
-
diff --git a/frontend/src/app/shared/components/footer/footer.component.ts b/frontend/src/app/shared/components/footer/footer.component.ts deleted file mode 100644 index 1c3b309..0000000 --- a/frontend/src/app/shared/components/footer/footer.component.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { faMoneyBillTransfer, faCreditCard, faWallet } from '@fortawesome/free-solid-svg-icons'; -import { faPaypal, faGooglePay, faApplePay } from '@fortawesome/free-brands-svg-icons'; - -@Component({ - selector: 'app-footer', - standalone: true, - templateUrl: './footer.component.html', - imports: [FontAwesomeModule], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class FooterComponent { - currentYear: number = new Date().getFullYear(); - - faPaypal = faPaypal; - faCreditCard = faCreditCard; - faMoneyBillTransfer = faMoneyBillTransfer; - faWallet = faWallet; - faGooglePay = faGooglePay; - faApplePay = faApplePay; -} diff --git a/frontend/src/app/shared/components/navbar/navbar.component.html b/frontend/src/app/shared/components/navbar/navbar.component.html deleted file mode 100644 index daa53f7..0000000 --- a/frontend/src/app/shared/components/navbar/navbar.component.html +++ /dev/null @@ -1,75 +0,0 @@ - diff --git a/frontend/src/app/shared/components/navbar/navbar.component.ts b/frontend/src/app/shared/components/navbar/navbar.component.ts deleted file mode 100644 index a6bbc15..0000000 --- a/frontend/src/app/shared/components/navbar/navbar.component.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { KeycloakService } from 'keycloak-angular'; -import { UserService } from '../../../service/user.service'; - -import { CurrencyPipe } from '@angular/common'; -@Component({ - selector: 'app-navbar', - templateUrl: './navbar.component.html', - standalone: true, - imports: [RouterModule, CurrencyPipe], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class NavbarComponent implements OnInit { - isMenuOpen = false; - private keycloakService: KeycloakService = inject(KeycloakService); - isLoggedIn = this.keycloakService.isLoggedIn(); - - private userService = inject(UserService); - private user = this.userService.getCurrentUser(); - - public balance = signal(0); - - ngOnInit() { - this.user.subscribe((user) => { - this.balance.set(user?.balance ?? 0); - }); - } - - login() { - try { - const baseUrl = window.location.origin; - this.keycloakService.login({ redirectUri: `${baseUrl}/login/success` }); - } catch (error) { - console.error('Login failed:', error); - } - } - - logout() { - this.keycloakService.logout(); - } - - toggleMenu() { - this.isMenuOpen = !this.isMenuOpen; - } -} diff --git a/frontend/src/app/shared/services/modal-animation.service.ts b/frontend/src/app/shared/services/modal-animation.service.ts deleted file mode 100644 index f54c2bc..0000000 --- a/frontend/src/app/shared/services/modal-animation.service.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Injectable } from '@angular/core'; -import gsap from 'gsap'; - -@Injectable({ - providedIn: 'root', -}) -export class ModalAnimationService { - private readonly defaultDuration = 0.3; - private readonly defaultEase = 'power2.out'; - - openModal(modalElement: HTMLElement, overlayElement: HTMLElement) { - gsap.set(overlayElement, { opacity: 0, display: 'block' }); - gsap.set(modalElement, { - opacity: 0, - scale: 0.95, - y: 20, - display: 'block', - }); - - gsap.to(overlayElement, { - opacity: 1, - duration: this.defaultDuration, - ease: this.defaultEase, - }); - - gsap.to(modalElement, { - opacity: 1, - scale: 1, - y: 0, - duration: this.defaultDuration, - ease: this.defaultEase, - }); - } - - closeModal(modalElement: HTMLElement, overlayElement: HTMLElement, onComplete?: () => void) { - gsap.to([overlayElement, modalElement], { - opacity: 0, - duration: this.defaultDuration, - ease: this.defaultEase, - onComplete: () => { - gsap.set([overlayElement, modalElement], { display: 'none' }); - onComplete?.(); - }, - }); - - gsap.to(modalElement, { - scale: 0.95, - y: 20, - duration: this.defaultDuration, - ease: this.defaultEase, - }); - } -} diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts deleted file mode 100644 index 53866cc..0000000 --- a/frontend/src/environments/environment.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const environment = { - STRIPE_KEY: - 'pk_test_51QrePYIvCfqz7ANgMizBorPpVjJ8S6gcaL4yvcMQnVaKyReqcQ6jqaQEF7aDZbDu8rNVsTZrw8ABek4ToxQX7KZe00jpGh8naG', -}; diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 300a61a..8de5d08 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -1,176 +1,12 @@ @import 'tailwindcss'; @theme { - --color-deep-blue: #0a1219; - --color-deep-blue-light: #121e27; - --color-deep-blue-contrast: #1a2835; - - --color-emerald: #10b981; - --color-emerald-dark: #059669; - --color-emerald-light: #34d399; - - --color-text-primary: #ffffff; - --color-text-secondary: #94a3b8; - --color-text-tertiary: #64748b; - - --color-accent-yellow: #fbbf24; - --color-accent-red: #ef4444; - --color-accent-purple: #8b5cf6; + --color-deep-blue: #0f212e; + --color-deep-blue-light: #1a2c38; + --color-deep-blue-contrast: #1b2c3b; + --color-light-blue: #1475e1; } body { - @apply bg-deep-blue text-text-primary h-full; -} - -button, -a { - @apply cursor-pointer active:scale-95 text-text-primary transition-all duration-200; -} - -.card { - @apply bg-deep-blue-contrast rounded-lg overflow-hidden shadow-lg hover:shadow-xl transition-shadow duration-300; -} - -.button-primary { - @apply bg-emerald hover:bg-emerald-dark text-text-primary transition-all duration-300 active:scale-95 rounded; -} - -.button-secondary { - @apply bg-deep-blue-light hover:bg-deep-blue-contrast w-full py-2 rounded my-2; -} - -.game-card-content { - @apply p-4; -} - -.nav-button { - @apply hidden lg:block absolute top-1/2 -translate-y-1/2 bg-deep-blue-contrast hover:bg-deep-blue-contrast/90 text-text-primary p-3 rounded-full opacity-0 group-hover:opacity-100 transition-all duration-300 shadow-lg hover:scale-110; -} - -.slider-container { - @apply flex transition-transform duration-500 ease-out; -} - -.slider-grid { - @apply min-w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4; -} - -.welcome-bonus { - @apply text-4xl sm:text-5xl lg:text-7xl font-extrabold text-emerald-light mb-3 sm:mb-4; -} - -.bonus-description { - @apply text-text-secondary text-base sm:text-lg mb-6 sm:mb-8; -} - -.section-heading { - @apply font-bold text-text-primary; -} - -.game-heading-sm { - @apply font-bold text-text-primary text-sm mb-2; -} - -.game-heading-xl { - @apply font-bold text-text-primary text-xl mb-2; -} - -.game-text { - @apply text-text-secondary text-sm mb-4; -} - -.stat-container { - @apply bg-deep-blue-contrast rounded-lg shadow-lg p-4 sm:p-6 text-center; -} - -.stat-number { - @apply text-xl sm:text-2xl font-bold text-emerald; -} - -.stat-text { - @apply text-text-secondary text-sm; -} - -.nav-brand { - @apply flex items-center text-text-primary text-xl font-semibold; -} - -.nav-link { - @apply px-3 py-2 rounded-md font-normal text-sm text-text-secondary hover:text-text-primary hover:bg-deep-blue-contrast transition-all duration-200; -} - -.nav-toggle { - @apply text-text-secondary hover:text-text-primary transition-colors duration-200; -} - -.nav-mobile-menu { - @apply p-2 pt-2 mb-4 space-y-1 bg-deep-blue-contrast rounded-b-lg; -} - -.nav-mobile-link { - @apply block px-3 py-2 rounded-md text-sm text-text-secondary hover:text-text-primary hover:bg-deep-blue-light transition-all duration-200; -} - -.footer-section { - @apply col-span-2 md:col-span-1; -} - -.footer-heading { - @apply text-text-primary text-sm font-semibold mb-4; -} - -.footer-link { - @apply text-text-secondary hover:text-text-primary text-sm transition-all duration-200; -} - -.footer-payment-method { - @apply bg-deep-blue rounded p-3 flex items-center justify-center space-x-2 hover:bg-deep-blue/50 transition-all duration-200; -} - -.footer-payment-icon { - @apply text-text-secondary text-lg; -} - -.footer-payment-text { - @apply text-text-secondary text-xs whitespace-nowrap; -} - -.footer-copyright { - @apply text-text-secondary text-sm; -} - -.footer-disclaimer { - @apply text-xs; -} - -.modal-bg { - @apply fixed inset-0 bg-black/80 backdrop-blur-sm z-50 focus:outline-none focus:ring-2 focus:ring-emerald-light; -} - -.modal-card { - @apply bg-deep-blue-contrast overflow-hidden hover:shadow-xl transition-shadow duration-300 p-6 rounded-xl shadow-2xl z-50 min-w-[300px] max-w-[400px] w-full mx-auto border border-deep-blue-light/20 fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2; -} - -.modal-heading { - @apply text-2xl font-bold text-text-primary mb-4; -} - -.modal-card input { - @apply w-full px-4 py-2.5 bg-deep-blue-light/50 text-white rounded-lg my-1 border border-deep-blue-light/30 focus:border-emerald/50 focus:ring-1 focus:ring-emerald/50 outline-none transition-all duration-200; -} - -.modal-card label { - @apply text-text-secondary text-sm font-medium mb-1 block; -} - -.modal-card button { - @apply transition-all duration-200; -} - -.modal-card .button-primary { - @apply bg-emerald hover:bg-emerald-dark text-text-primary transition-all duration-300 active:scale-95 shadow-lg shadow-emerald/20; -} - -.modal-card .button-secondary { - @apply bg-deep-blue-light/50 hover:bg-deep-blue-light w-full py-2.5 my-2 border border-deep-blue-light/30 hover:border-deep-blue-light/50; + @apply bg-deep-blue text-gray-100; }