diff --git a/README.md b/README.md index e852e0e..8004d7e 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,15 @@ Ein digitales Kaffeekassen-System der [Fachschaft für Informatik und Mathematik der Universität Passau](https://fsinfo.fim.uni-passau.de/). -## Deploy +![Shop-Ansicht](img/buymenu.png) + +## Features + +Eine [Feature-Liste](https://github.com/fsinfopassau/PRoST/wiki/Features) mit Nutzungsanleitung ist im [Wiki](https://github.com/fsinfopassau/PRoST/wiki) zu finden -Docker-Compose-Environment: +## Deploy -- VITE_API_URL: URL für die Backend-API -- /data : Ordner für Datenbank und Item-Bilder +Details stehen unter [Setup im Wiki](https://github.com/fsinfopassau/PRoST/wiki/Setup) **Build & Run Compose**: @@ -22,6 +25,8 @@ docker compose build docker compose up ``` +Die Seite ist unter [localhost/prost](http://localhost/prost) zu finden + ## Development **Frontend**: @@ -34,6 +39,8 @@ npm install npm run dev ``` +Frontend ist unter [localhost:8080](http://localhost:8080) zu finden + **Backend**: - [Google-Java-Codestyle](https://github.com/google/styleguide/blob/gh-pages/intellij-java-google-style.xml) diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/data/DataFilter.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/data/DataFilter.java index af8b6da..10d0a68 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/data/DataFilter.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/data/DataFilter.java @@ -18,7 +18,7 @@ public static boolean isValidString(String value, String name) { return false; } if (value.length() > MAX_NAME_LENGTH) { - System.out.println("[DF] :: " + name + " size to large"); + System.out.println("[DF] :: " + name + " size too large"); return false; } return true; @@ -61,4 +61,11 @@ public static String formatMoney(BigDecimal amount) { return df.format(amount); } + public static boolean isValidMoney(BigDecimal amount) { + if (amount == null) { + return false; + } + return amount.scale() <= 2; + } + } diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/security/CustomUserDetailsContextMapper.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/security/CustomUserDetailsContextMapper.java index e1187e6..4508ddb 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/security/CustomUserDetailsContextMapper.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/security/CustomUserDetailsContextMapper.java @@ -1,5 +1,6 @@ package de.unipassau.fim.fsinfo.prost.security; +import java.util.Arrays; import java.util.Collection; import lombok.Getter; import lombok.Setter; @@ -7,9 +8,9 @@ import org.springframework.ldap.core.DirContextOperations; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.ldap.userdetails.UserDetailsContextMapper; +import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper; -public class CustomUserDetailsContextMapper implements UserDetailsContextMapper { +public class CustomUserDetailsContextMapper extends LdapUserDetailsMapper { @Override public UserDetails mapUserFromContext(DirContextOperations ctx, String username, @@ -30,6 +31,14 @@ public UserDetails mapUserFromContext(DirContextOperations ctx, String username, userDetails.setEmail(mails[0]); } + // Additional handling for service accounts if needed + if (ctx.attributeExists("objectClass")) { + String[] objectClasses = ctx.getStringAttributes("objectClass"); + if (objectClasses != null && Arrays.asList(objectClasses).contains("simpleSecurityObject")) { + userDetails.setServiceAccount(true); + } + } + return userDetails; } @@ -47,6 +56,7 @@ public static final class CustomUserDetails implements UserDetails { private Collection authorities; private String displayName; private String email; + private boolean serviceAccount; @Override public Collection getAuthorities() { diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/security/LdapConfig.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/security/LdapConfig.java index b1aa5ca..d6d57ee 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/security/LdapConfig.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/security/LdapConfig.java @@ -26,6 +26,7 @@ public LdapAuthoritiesPopulator ldapAuthoritiesPopulator() { authorities.setGroupSearchFilter("(member={0})"); authorities.setConvertToUpperCase(true); authorities.setRolePrefix(""); + authorities.setIgnorePartialResultException(true); // Handle service accounts without groups return authorities; } diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/security/WebConfig.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/security/WebConfig.java index 0673458..216018c 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/security/WebConfig.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/security/WebConfig.java @@ -1,18 +1,21 @@ package de.unipassau.fim.fsinfo.prost.security; import de.unipassau.fim.fsinfo.prost.data.UserAccessRole; +import de.unipassau.fim.fsinfo.prost.security.CustomUserDetailsContextMapper.CustomUserDetails; import java.util.Arrays; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.Authentication; import org.springframework.security.web.SecurityFilterChain; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; @@ -76,10 +79,15 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests(auth -> auth .requestMatchers(AUTH_WHITELIST).permitAll() .requestMatchers(USER_SPACE) - .hasAnyAuthority(UserAccessRole.FSINFO.name(), UserAccessRole.KIOSK.name(), - UserAccessRole.KAFFEEKASSE.name()) + .access((authentication, object) -> + new AuthorizationDecision(isServiceAccountOr(authentication.get(), + UserAccessRole.FSINFO, UserAccessRole.KIOSK, UserAccessRole.KAFFEEKASSE)) + ) .requestMatchers(KIOSK_SPACE) - .hasAnyAuthority(UserAccessRole.KIOSK.name(), UserAccessRole.KAFFEEKASSE.name()) + .access((authentication, object) -> new AuthorizationDecision( + isServiceAccountOr(authentication.get(), UserAccessRole.KIOSK, + UserAccessRole.KAFFEEKASSE)) + ) .requestMatchers(ADMIN_SPACE).hasAnyAuthority(UserAccessRole.KAFFEEKASSE.name()) .anyRequest().authenticated() // Require authentication for all other requests ) @@ -89,6 +97,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .build(); } + @Autowired public void configure(AuthenticationManagerBuilder auth) throws Exception { auth @@ -101,4 +110,21 @@ public void configure(AuthenticationManagerBuilder auth) throws Exception { .userDetailsContextMapper(userDetailsContextMapper); } + private boolean isServiceAccountOr(Authentication authentication, UserAccessRole... roles) { + if (authentication.getPrincipal() instanceof CustomUserDetails userDetails) { + boolean isServiceAccount = userDetails.isServiceAccount(); + boolean hasKioskRole = userDetails.getAuthorities().stream() + .anyMatch(grantedAuthority -> { + for (UserAccessRole role : roles) { + if (grantedAuthority.getAuthority().equals(role.name())) { + return true; + } + } + return false; + }); + return isServiceAccount || hasKioskRole; + } + return false; + } + } \ No newline at end of file diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/AuthenticationService.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/AuthenticationService.java index 0ab6a6f..16dedfe 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/AuthenticationService.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/AuthenticationService.java @@ -76,15 +76,22 @@ public Collection getRoles(Authentication authentication) { List userRoles = new ArrayList<>(); + if (userDetails != null && userDetails.isServiceAccount()) { + userRoles.add(UserAccessRole.KIOSK); + } + authentication.getAuthorities().forEach(authority -> { if (authority != null) { try { UserAccessRole role = UserAccessRole.valueOf(authority.getAuthority()); - userRoles.add(role); + if (!userRoles.contains(role)) { + userRoles.add(role); + } } catch (IllegalArgumentException e) { System.err.println( "[AC] :: Could not map authority " + authority.getAuthority() - + " to UserAccessRole of User " + userDetails.getUsername() + "!"); + + " to UserAccessRole of User " + (userDetails == null ? "[No UserDetails]" + : userDetails.getUsername()) + "!"); } } }); diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/MailService.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/MailService.java index 426dc32..57d6a1c 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/MailService.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/MailService.java @@ -21,15 +21,21 @@ @Service public class MailService { - @Value("${MAIL_USER_NAME:Username}") + @Value("${MAIL_SENDER_ADDR:addr@test.com}") + private String mailSenderAddr; + + @Value("${MAIL_USER_NAME:}") private String mailUserName; - @Value("${MAIL_USER_PASSWORD:password}") + @Value("${MAIL_USER_PASSWORD:}") private String mailUserPassword; @Value("${MAIL_HOST_NAME:smtp.test.com}") private String mailHostName; + @Value("${MAIL_USE_SSL:true}") + private boolean useSsl; + @Value("${MAIL_HOST_PORT:587}") private int mailHostPort; @@ -123,12 +129,16 @@ private boolean sendMail(String address, String subject, String text) { try { email.setHostName(mailHostName); email.setSmtpPort(mailHostPort); - email.setAuthentication(mailUserName, mailUserPassword); - email.setFrom(mailUserName); + if (mailUserName != null && !mailUserName.isEmpty()) { + email.setAuthentication(mailUserName, mailUserPassword == null ? "" : mailUserPassword); + } + email.setFrom(mailSenderAddr); email.addTo(address); email.setMsg(text); - email.setSSLOnConnect(true); - email.setStartTLSEnabled(true); + if (useSsl) { + email.setSSLOnConnect(true); + email.setStartTLSEnabled(true); + } email.setSubject(subject); email.send(); diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/ShopService.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/ShopService.java index b29f0b0..b4d6e20 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/ShopService.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/ShopService.java @@ -90,7 +90,7 @@ public Optional createItem(String identifier, String displayName, Stri return Optional.empty(); } - if (price == null) { + if (!DataFilter.isValidMoney(price)) { System.out.println("[SS] :: The price is invalid or null"); return Optional.empty(); } @@ -150,13 +150,20 @@ public Optional changePrice(String identifier, String value) { try { if (item.isPresent()) { BigDecimal price = new BigDecimal(value); + + if (!DataFilter.isValidMoney(price)) { + System.out.println("[SS] :: Price-value with " + price + " not valid!"); + return Optional.empty(); + } + if (price.compareTo(MAX_PRICE) > 0) { - System.out.println("[SS] :: Price is with " + price + " to high"); + System.out.println("[SS] :: Price is with " + price + " too high!"); return Optional.empty(); } else if (price.compareTo(MIN_PRICE) < 0) { - System.out.println("[SS] :: Price is with " + price + " to low"); + System.out.println("[SS] :: Price is with " + price + " too low!"); return Optional.empty(); } + item.get().setPrice(price); itemRepository.save(item.get()); return item; diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/TransactionService.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/TransactionService.java index 0b70804..7449c2f 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/TransactionService.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/TransactionService.java @@ -1,5 +1,6 @@ package de.unipassau.fim.fsinfo.prost.service; +import de.unipassau.fim.fsinfo.prost.data.DataFilter; import de.unipassau.fim.fsinfo.prost.data.TransactionType; import de.unipassau.fim.fsinfo.prost.data.dao.ProstUser; import de.unipassau.fim.fsinfo.prost.data.dao.TransactionEntry; @@ -44,7 +45,7 @@ public Optional moneyTransfer(Optional sender, Optional receiver, Optional bearer, BigDecimal amount, TransactionType type) { - if (amount.scale() > 2) { + if (!DataFilter.isValidMoney(amount)) { System.err.println("[TS] :: " + amount + " has not the right money-precision!"); return Optional.empty(); } @@ -100,7 +101,7 @@ private Optional buy(ProstUser receiver, ProstUser bearer, BigDecimal amount) { if (amount.compareTo(BigDecimal.ZERO) < 0) { // Only Positive Values - System.out.println("[TS] :: Buy is with " + amount + " to low"); + System.out.println("[TS] :: Buy is with " + amount + " too low"); return Optional.empty(); } diff --git a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/UserService.java b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/UserService.java index c45acc7..2baa50b 100644 --- a/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/UserService.java +++ b/backend/src/main/java/de/unipassau/fim/fsinfo/prost/service/UserService.java @@ -132,8 +132,13 @@ public boolean setMoneySpent(String id, String amountString) { try { BigDecimal amount = new BigDecimal(amountString); + if (!DataFilter.isValidMoney(amount)) { + System.out.println("[US] :: Price-value with " + amount + " not valid!"); + return false; + } + if (amount.compareTo(BigDecimal.ZERO) < 0) { // Only Positive Values - System.out.println("[US] :: Money Spent is with " + amount + " to low"); + System.out.println("[US] :: Money Spent is with " + amount + " too low"); return false; } diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 4a0886b..c4c02a0 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -22,6 +22,13 @@ server: port: ${PORT:8081} error: include-stacktrace: never +logging: + level: + org: + springframework: + security: ${LOGGING_LEVEL:INFO} + web: ${LOGGING_LEVEL:INFO} + data: ${LOGGING_LEVEL:INFO} prost: save-location: ${DATA_LOCATION:prost-data} ldap-uri: ${LDAP_URI:ldap://ldap:1389/dc=fsinfo,dc=fim,dc=uni-passau,dc=de} \ No newline at end of file diff --git a/backend/src/test/java/de/unipassau/fim/fsinfo/prost/data/DataFilterTest.java b/backend/src/test/java/de/unipassau/fim/fsinfo/prost/data/DataFilterTest.java index e853dff..85328dd 100644 --- a/backend/src/test/java/de/unipassau/fim/fsinfo/prost/data/DataFilterTest.java +++ b/backend/src/test/java/de/unipassau/fim/fsinfo/prost/data/DataFilterTest.java @@ -99,5 +99,25 @@ public void testFormatMoney_ZeroAmount() { public void testFormatMoney_NegativeAmount() { assertEquals("-1.234,56 €", DataFilter.formatMoney(new BigDecimal("-1234.56"))); } + + @Test + public void testValidMoney_RightDecimalAmount() { + assertTrue(DataFilter.isValidMoney(new BigDecimal("-1234.56"))); + } + + @Test + public void testValidMoney_RightDecimalAmount2() { + assertTrue(DataFilter.isValidMoney(new BigDecimal("-1234"))); + } + + @Test + public void testValidMoney_WrongDecimalAmount() { + assertFalse(DataFilter.isValidMoney(new BigDecimal("-1234.563"))); + } + + @Test + public void testValidMoney_NullAmount() { + assertFalse(DataFilter.isValidMoney(null)); + } } diff --git a/backend/src/test/java/de/unipassau/fim/fsinfo/prost/service/ShopServiceTest.java b/backend/src/test/java/de/unipassau/fim/fsinfo/prost/service/ShopServiceTest.java index 6b0a3cf..c35cb29 100644 --- a/backend/src/test/java/de/unipassau/fim/fsinfo/prost/service/ShopServiceTest.java +++ b/backend/src/test/java/de/unipassau/fim/fsinfo/prost/service/ShopServiceTest.java @@ -114,6 +114,13 @@ public void testCreateItem_InvalidData_ReturnsEmpty() { assertTrue(result.isEmpty()); } + @Test + public void testCreateItem_WrongMoneyPrecision_ReturnsEmpty() { + Optional result = shopService.createItem("moneyTest", "Money?", "Category 1", + new BigDecimal("10.001")); + assertTrue(result.isEmpty()); + } + @Test public void testCreateItem_DuplicateIdentifier_ReturnsEmpty() { when(itemRepository.existsById(shopItem.getId())).thenReturn(true); @@ -195,6 +202,14 @@ public void testChangePrice_InvalidItem_ReturnsEmpty() { assertTrue(result.isEmpty()); } + @Test + public void testChangePrice_PricePrecisionError_ReturnsEmpty() { + when(itemRepository.findById(shopItem.getId())).thenReturn(Optional.of(shopItem)); + + Optional result = shopService.changePrice(shopItem.getId(), "10.001"); + assertTrue(result.isEmpty()); + } + @Test public void testChangePrice_PriceToHigh_ReturnsEmpty() { when(itemRepository.findById(shopItem.getId())).thenReturn(Optional.of(shopItem)); diff --git a/backend/src/test/java/de/unipassau/fim/fsinfo/prost/service/TransactionServiceTest.java b/backend/src/test/java/de/unipassau/fim/fsinfo/prost/service/TransactionServiceTest.java index 8f23585..4a17c8c 100644 --- a/backend/src/test/java/de/unipassau/fim/fsinfo/prost/service/TransactionServiceTest.java +++ b/backend/src/test/java/de/unipassau/fim/fsinfo/prost/service/TransactionServiceTest.java @@ -123,6 +123,16 @@ public void testMoneyTransfer_Buy_NegativeAmount() { assertFalse(result.isPresent()); } + @Test + public void testMoneyTransfer_Buy_WrongMoneyPresicion() { + receiver.setBalance(new BigDecimal("20.00")); + + Optional result = transactionService.moneyTransfer(Optional.of(receiver), + Optional.of(receiver), Optional.of(bearer), new BigDecimal("10.001"), TransactionType.BUY); + + assertFalse(result.isPresent()); + } + @Test public void testMoneyTransfer_Change_SetsBalance() { receiver.setBalance(new BigDecimal("20.00")); diff --git a/backend/src/test/java/de/unipassau/fim/fsinfo/prost/service/UserServiceTest.java b/backend/src/test/java/de/unipassau/fim/fsinfo/prost/service/UserServiceTest.java index 9ec2ded..8367569 100644 --- a/backend/src/test/java/de/unipassau/fim/fsinfo/prost/service/UserServiceTest.java +++ b/backend/src/test/java/de/unipassau/fim/fsinfo/prost/service/UserServiceTest.java @@ -176,7 +176,7 @@ public void testSetEnabled_SuccessfulSetEnabled_ReturnsTrue() { @Test public void testSetMoneySpent_UserNotFound_ReturnsFalse() { when(userRepository.findById(prostUser.getId())).thenReturn(Optional.empty()); - boolean result = userService.setMoneySpent(prostUser.getId(), "100.00"); + boolean result = userService.setMoneySpent(prostUser.getId(), "10.00"); assertFalse(result); } @@ -187,6 +187,13 @@ public void testSetMoneySpent_InvalidAmount_ReturnsFalse() { assertFalse(result); } + @Test + public void testSetMoneySpent_WrongPrecision_ReturnsFalse() { + when(userRepository.findById(prostUser.getId())).thenReturn(Optional.of(prostUser)); + boolean result = userService.setMoneySpent(prostUser.getId(), "0.001"); + assertFalse(result); + } + @Test public void testSetMoneySpent_NegativeAmount_ReturnsFalse() { when(userRepository.findById(prostUser.getId())).thenReturn(Optional.of(prostUser)); diff --git a/docker-compose.yml b/docker-compose.yml index 6f3fb7d..4e25ff5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: - VITE_API_URL=http://localhost:8081 - VITE_BASE_PATH=/prost ports: - - "8080:80" + - "80:80" backend: depends_on: [ ldap, mariadb ] container_name: PRoST-Backend diff --git a/frontend/index.html b/frontend/index.html index 3a644ba..4b81f7e 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -11,10 +11,16 @@ FSinfo PRoST + + +
diff --git a/frontend/nginx.conf b/frontend/nginx.conf index ffe244a..4724fe1 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -6,7 +6,7 @@ server { location / { root /usr/share/nginx/html; index index.html index.htm; - try_files $uri $uri/ /index.html =404; + try_files $uri $uri/ /prost/index.html =404; # insert other base_path before index } error_page 500 502 503 504 /50x.html; diff --git a/frontend/public/data-info.html b/frontend/public/data-info.html index acb5062..ebbbb2b 100644 --- a/frontend/public/data-info.html +++ b/frontend/public/data-info.html @@ -7,7 +7,7 @@

§ 1 Verantwortliche

Inhaber & zuständig für die Verwaltung des Systems ist Mirjam Deml. Beauftragte für die Erhebung, Verarbeitung und - Speicherung der Daten ist das Administratoren-Team der FSinfo der Uni Passau. + Speicherung der Daten ist das Administratoren-Team der FSinfo der Uni Passau.

  • @@ -17,10 +17,6 @@

    § 1 Verantwortliche

  • Zugang zum System haben von den Verantwortlichen ausgewählte Personen.
  • -
  • - Aus diesen Gründen müssen das System und deren Verantwortliche nicht mit der - DSGVO im Einklang stehen. -

§ 2 Erhobene und verarbeitete Daten diff --git a/frontend/public/img/mc/dirt-background.png b/frontend/public/img/mc/dirt-background.png new file mode 100644 index 0000000..eca4eb3 Binary files /dev/null and b/frontend/public/img/mc/dirt-background.png differ diff --git a/frontend/public/img/mc/display-card.png b/frontend/public/img/mc/display-card.png new file mode 100644 index 0000000..e3ff383 Binary files /dev/null and b/frontend/public/img/mc/display-card.png differ diff --git a/frontend/public/img/mc/hotbar-selected.png b/frontend/public/img/mc/hotbar-selected.png new file mode 100644 index 0000000..088bba9 Binary files /dev/null and b/frontend/public/img/mc/hotbar-selected.png differ diff --git a/frontend/public/img/mc/hotbar.png b/frontend/public/img/mc/hotbar.png new file mode 100644 index 0000000..6cfa4f5 Binary files /dev/null and b/frontend/public/img/mc/hotbar.png differ diff --git a/frontend/public/img/mc/settings-button-hovered.png b/frontend/public/img/mc/settings-button-hovered.png new file mode 100644 index 0000000..8c61e1b Binary files /dev/null and b/frontend/public/img/mc/settings-button-hovered.png differ diff --git a/frontend/public/img/mc/settings-button-pressed.png b/frontend/public/img/mc/settings-button-pressed.png new file mode 100644 index 0000000..a949cbf Binary files /dev/null and b/frontend/public/img/mc/settings-button-pressed.png differ diff --git a/frontend/public/img/mc/settings-button-square-hovered.png b/frontend/public/img/mc/settings-button-square-hovered.png new file mode 100644 index 0000000..bf26398 Binary files /dev/null and b/frontend/public/img/mc/settings-button-square-hovered.png differ diff --git a/frontend/public/img/mc/settings-button-square-pressed.png b/frontend/public/img/mc/settings-button-square-pressed.png new file mode 100644 index 0000000..f72dc4d Binary files /dev/null and b/frontend/public/img/mc/settings-button-square-pressed.png differ diff --git a/frontend/public/img/mc/settings-button-square.png b/frontend/public/img/mc/settings-button-square.png new file mode 100644 index 0000000..fddf25e Binary files /dev/null and b/frontend/public/img/mc/settings-button-square.png differ diff --git a/frontend/public/img/mc/settings-button.png b/frontend/public/img/mc/settings-button.png new file mode 100644 index 0000000..9d84545 Binary files /dev/null and b/frontend/public/img/mc/settings-button.png differ diff --git a/frontend/public/img/mc_xp-empty.png b/frontend/public/img/mc/xp-empty.png similarity index 100% rename from frontend/public/img/mc_xp-empty.png rename to frontend/public/img/mc/xp-empty.png diff --git a/frontend/public/img/mc_xp.png b/frontend/public/img/mc/xp.png similarity index 100% rename from frontend/public/img/mc_xp.png rename to frontend/public/img/mc/xp.png diff --git a/frontend/public/styles/blue.css b/frontend/public/styles/blue.css index 8e6a2b0..5a4c2db 100644 --- a/frontend/public/styles/blue.css +++ b/frontend/public/styles/blue.css @@ -1,214 +1,19 @@ -body { - background: hsl(240, 100%, 92%); -} - -div { - color: #000080; -} - -a:visited { - color: #565691; -} - -#tab-changer { - background-color: var(--black-a4); - box-shadow: 0 2px 18px var(--black-a4); - border-bottom-left-radius: 12px; - border-bottom-right-radius: 12px; -} - -#main-search { - color: white; - background-color: var(--black-a5); - box-shadow: 0 2px 18px var(--mauve-10); - border: 4px solid var(--black-a2); - border-radius: 12px; -} -#main-search:focus { - border: 4px dashed var(--black-a3); -} -#main-search::selection { - background-color: var(--black-a5); - color: white; -} - -.Button { - background-color: white; - color: #000080; - border: 4px solid var(--mauve-4); - box-shadow: 0 2px 4px var(--black-a3); - border-radius: 12px; -} -.Button:hover { - border: 4px solid #bfbfe6; -} -.Button:active { - background-color: #a8a8e0; - box-shadow: 0 0 0 2px var(--black-a5); -} - -.PageButton { - background-color: white; - color: #000080; - border: 4px solid var(--mauve-4); - box-shadow: 0 2px 4px var(--black-a3); - border-radius: 10000px; -} -.PageButton:hover { - border: 4px solid #bfbfe6; -} -.PageButton:active { - background-color: #a8a8e0; - box-shadow: 0 0 0 2px var(--black-a5); -} - -.CheckBox { - background-color: white; - border-radius: 100%; - box-shadow: 0 2px 10px var(--black-a3); - border: 2px solid var(--black-a5); -} -.CheckBox:hover { - background-color: #bfbfe6; -} -.CheckBox:active { - background-color: #a8a8e0; - box-shadow: 0 0 0 2px var(--black-a5); -} - -.Input { - color: #000080; - border: 3px solid #bfbfe6; -} -.Input:focus { - border: 3px solid #9696e4; -} - -.DisplayCard { - border: 2px solid #000080; - background-color: #fcfcff; - box-shadow: 0 2px 10px var(--mauve-8); - border-radius: 12px; -} - -.Scrollbar { - background: var(--black-a3); -} -.Scrollbar:hover { - background: var(--black-a5); -} -.ScrollbarThumb { - background: #000080; - border-radius: 12px; -} - -/* -Invoice -*/ - -.Table { - background-color: #e2e2ff; - color: #000080; - border-radius: 12px; - border-collapse: collapse; - - tr { - border-bottom: 1px solid gray; - } - tr:last-child { - border-bottom: none; - } -} - -/* -Tabs -*/ - -.TabsTrigger { - background-color: white; - color: #000080; - border: 2px solid white; - background-clip: border-box; - margin: 4px 0; - border-bottom: 4px solid white; -} -.TabsTrigger:first-child { - border-top-left-radius: 12px; - border-bottom-left-radius: 12px; -} -.TabsTrigger:last-child { - border-top-right-radius: 12px; - border-bottom-right-radius: 12px; -} -.TabsTrigger:hover { - border: 2px solid #9696e4; - border-bottom: 4px solid #9696e4; -} -.TabsTrigger[data-state="active"] { - color: #000080; - border-bottom: 4px solid currentColor; -} -.TabsTrigger:active { - box-shadow: 0 0 0 2px black; -} - -/* -Switch -*/ - -.SwitchRoot { - background-color: #a8a8e0; - box-shadow: 0 2px 10px var(--mauve-8); - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - border: 4 solid #000080; -} -.SwitchRoot:focus { - box-shadow: 0 0 0 2px black; -} -.SwitchRoot[data-state="checked"] { - background-color: #000080; -} -.SwitchThumb { - background-color: white; -} - -/* -Dialog -*/ -.DialogOverlay { - background-color: var(--black-a9); -} - -.DialogContent { - background-color: white; - box-shadow: hsl(206 22% 7% / 35%) 0 10px 38px -10px, - hsl(206 22% 7% / 20%) 0 10px 20px -15px; -} - -.DialogTitle { - color: #000080; -} - -/* -Progress -*/ - -.progress-bar-background { - background-image: url(../img/mc_xp-empty.png); /* Background image of the empty bar */ -} - -.progress-bar-foreground { - background-image: url(../img/mc_xp.png); /* Background image of the full bar */ -} - -/* -Colors -*/ - -.Selected { - color: #000080; - border-color: #000080; -} +:root { + --background: hsl(229, 100%, 89%); + --primary: #000080; + --secondary: #afaff0; + --on-hover: #bfbfe6; + --on-active: #fcfcff; + --on-focus: #000080; + --element-bg: #fefeff; + --element-border: rgb(235, 235, 247); + --table: #e2e2ff; + --tr-split: gray; + --on-click-border: rgba(0, 0, 0, 0.3); + --shadow: rgba(0, 0, 0, 0.2); +} + +/* Standard Colors */ .danger-color { color: #ff7777; diff --git a/frontend/public/styles/mc.css b/frontend/public/styles/mc.css new file mode 100644 index 0000000..7e98139 --- /dev/null +++ b/frontend/public/styles/mc.css @@ -0,0 +1,358 @@ +:root { + --background: hsl(229, 100%, 89%); + --primary: #000000; + --secondary: #8b8b8b; + --on-hover: rgba(0, 0, 0, 0); + --on-active: rgba(0, 0, 0, 0); + --on-focus: #a0a0a0; + --element-bg: #c6c6c6; + --element-border: rgb(235, 235, 247); + --table: #8b8b8b; + --tr-split: #373737; + --on-click-border: rgba(0, 0, 0, 0.3); + --shadow: rgba(0, 0, 0, 0.2); +} + +a:visited { + color: inherit; +} + +body { + background-image: url(../img/mc/dirt-background.png); +} + +h1 { + color: #ffffff; +} + +.site-data-info { + border-radius: 0px; +} + +.Input { + color: #ffffff; + border: 3px solid #ffffff; + background-color: #000000; + border-radius: 0px; +} +.Input:focus { + border: 3px solid var(--on-focus); +} + +.CheckBox { + background-color: var(--element-bg); + border-radius: 0px; + border: 2px solid var(--on-focus); +} +.CheckBox:hover { + background-color: var(--on-hover); +} +.CheckBox:active { + background-color: var(--on-active); + box-shadow: 0 0 0 2px var(--shadow); +} + +.DisplayCard, +.DialogContent { + background-image: url(../img/mc/display-card.png); + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; + background-color: rgba(0, 0, 0, 0); + box-shadow: 0 2px 10px var(--shadow); + border: none; + border-radius: 0px; + padding: 1.4rem 2rem; +} + +.UserSummary .Balance { + font-size: 2rem; + display: flex; + justify-content: flex-end; + align-items: center; + + @media screen and (max-width: 450px) { + font-size: 1rem; + } + @media screen and (max-width: 350px) { + font-size: 0.5rem; + } +} + +.item-desc * { + color: #ffffff; +} + +#Checkout-Item * { + color: #ffffff; +} + +.CheckoutCounter * { + color: #ffffff; +} +.CheckoutCounter > Button { + border-radius: 0px; +} + +#Checkout-Complete { + border-radius: 0px; +} + +.Scrollbar { + background: var(--shadow); +} +.ScrollbarThumb { + background: var(--element-border); + border-radius: 0px; +} + +/* Search */ + +#main-search { + color: #ffffff; + border: 4px solid #ffffff; + background-color: #000000; + border-radius: 0px; + box-shadow: 0 2px 18px var(--shadow); +} +#main-search:focus { + border: 4px solid var(--on-focus); +} +#main-search::selection { + background-color: var(--shadow); + color: var(--element-bg); +} + +.CheckBox { + background-color: var(--shadow); + border-radius: 0px; + box-shadow: none; + border: 2px solid var(--shadow); +} +.CheckBox:hover { + background-color: var(--on-hover); + box-shadow: none; +} +.CheckBox:active { + background-color: var(--on-active); + box-shadow: none; +} + +/* +Tabs +*/ + +.TabsTrigger { + border-top: 4px solid #000000; + border-bottom: 4px solid #000000; + border-right: none; + border-left: none; + background-image: url(../img/mc/hotbar.png); + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; + background-color: rgba(0, 0, 0, 0); + color: #ffffff; +} +.TabsTrigger:first-child { + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + border-left: 4px solid #000000; +} +.TabsTrigger:last-child { + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + border-right: 4px solid #000000; +} + +.TabsTrigger:hover { + border: 4px solid #000000; + border-left: none; + border-right: none; +} +.TabsTrigger:hover:first-child { + border: 4px solid #000000; + border-left: 4px solid #000000; + border-right: none; +} +.TabsTrigger:hover:last-child { + border: 4px solid #000000; + border-right: 4px solid #000000; + border-left: none; +} + +.TabsTrigger[data-state="active"] { + background-image: url(../img/mc/hotbar-selected.png); + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; + background-color: rgba(0, 0, 0, 0); + color: #ffffff; + border-bottom: 4px solid #000000; +} +.TabsTrigger:active { + box-shadow: none; +} + +/* Button */ + +a.Button:visited { + color: #ffffff; +} +.Button { + background-image: url(../img/mc/settings-button.png); + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; + color: #ffffff; + border: none; + border-radius: 0px; +} +.Button:hover { + background-image: url(../img/mc/settings-button-hovered.png); + border: none; +} +.Button:active { + background-image: url(../img/mc/settings-button-pressed.png); +} + +.Button.icon { + background-image: url(../img/mc/settings-button-square.png); +} +.Button.icon:hover { + background-image: url(../img/mc/settings-button-square-hovered.png); + border: none; +} +.Button.icon:active { + background-image: url(../img/mc/settings-button-square-pressed.png); +} + +.PageButton { + background-image: url(../img/mc/settings-button-square.png); + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; + background-color: rgba(0, 0, 0, 0); + color: #ffffff; + border: 4px solid rgba(0, 0, 0, 0); + box-shadow: none; + border-radius: 0px; +} +.PageButton:hover { + background-image: url(../img/mc/settings-button-square-hovered.png); + border: 4px solid rgba(0, 0, 0, 0); +} +.PageButton:active { + background-image: url(../img/mc/settings-button-square-pressed.png); + border: 4px solid rgba(0, 0, 0, 0); + box-shadow: none; +} + +/* Table */ + +.Table { + border-radius: 0px; + border-collapse: collapse; + + color: #ffffff; + + border: 4px solid #373737; + border-right-color: #ffffff; + border-bottom-color: #ffffff; + + tr { + border-bottom: 2px solid var(--tr-split); + } + tr:last-child { + border-bottom: none; + } +} + +/* +Switch +*/ + +.SwitchRoot { + background-color: var(--primary); + box-shadow: 0 2px 10px var(--shadow); + -webkit-tap-highlight-color: var(--shadow); + border: 4 solid var(--primary); + border-radius: 0px; +} +.SwitchRoot[data-state="checked"] { + background-color: #ffffff; + border-radius: 0px; +} +.SwitchThumb { + background-color: var(--element-bg); + border-radius: 0px; +} + +/* +Progress +*/ +.progress-bar { + border-radius: 0px; +} + +.progress-bar-background { + background-color: rgba(0, 0, 0, 0); + background-size: cover; + background-image: url(../img/mc/xp-empty.png); /* Background image of the empty bar */ +} + +.progress-bar-foreground { + background-color: rgba(0, 0, 0, 0); + background-size: cover; + background-image: url(../img/mc/xp.png); /* Background image of the full bar */ +} + +.LevelNumberDisplay { + color: #4a7526; +} + +/* Font stuff */ + +* { + font-family: "Silkscreen", sans-serif; + font-weight: 300; +} + +.bold { + font-family: "Silkscreen", sans-serif; + font-weight: 500; +} + +/* Standard Colors */ + +.Selected { + color: #ffffff; + border-color: black; +} + +.danger-color { + color: #ff7777; + border-color: #ff7777; +} +.danger-color:focus { + color: red; + border-color: red; +} + +.good-color { + color: #ffffff; + border-color: #ffffff; +} +.good-color:focus { + color: #ffffff; + border-color: #ffffff; +} + +.important-color { + color: #76590f; + border-color: #76590f; +} +.important-color:focus { + color: #76590f; + border-color: #76590f; +} diff --git a/frontend/public/styles/purple.css b/frontend/public/styles/purple.css index ecc02f6..b66bb04 100644 --- a/frontend/public/styles/purple.css +++ b/frontend/public/styles/purple.css @@ -1,217 +1,19 @@ -body { - background: hsl(268, 100%, 91%); -} - -div { - color: var(--violet-11); -} - -a:visited { - color: var(--violet-9); -} - -#tab-changer { - background-color: var(--black-a4); - box-shadow: 0 2px 18px var(--black-a4); - border-bottom-left-radius: 12px; - border-bottom-right-radius: 12px; -} - -#main-search { - color: white; - background-color: var(--black-a5); - box-shadow: 0 2px 18px var(--mauve-10); - border: 4px solid var(--black-a2); - border-radius: 12px; -} -#main-search:focus { - border: 4px dashed var(--black-a3); -} -#main-search::selection { - background-color: var(--black-a5); - color: white; -} - -.Button { - background-color: white; - color: var(--violet-11); - border: 4px solid var(--mauve-4); - box-shadow: 0 2px 4px var(--black-a3); - border-radius: 12px; -} -.Button:hover { - border: 4px solid var(--violet-7); -} -.Button:active { - background-color: var(--violet-5); - box-shadow: 0 0 0 2px var(--black-a5); -} - -.PageButton { - background-color: white; - color: var(--violet-11); - border: 4px solid var(--mauve-4); - box-shadow: 0 2px 4px var(--black-a3); - border-radius: 10000px; -} -.PageButton:hover { - border: 4px solid var(--violet-7); -} -.PageButton:active { - background-color: var(--violet-5); - box-shadow: 0 0 0 2px var(--black-a5); -} - -.CheckBox { - background-color: white; - border-radius: 100%; - box-shadow: 0 2px 10px var(--black-a3); - border: 2px solid var(--black-a5); -} -.CheckBox:hover { - background-color: var(--violet-7); -} -.CheckBox:active { - background-color: var(--violet-5); - box-shadow: 0 0 0 2px var(--black-a5); -} - -.Input { - color: var(--violet-11); - border: 3px solid var(--violet-7); -} -.Input:focus { - border: 3px solid var(--violet-8); -} - -.DisplayCard { - border: 2px solid var(--violet-11); - background-color: var(--violet-2); - box-shadow: 0 2px 10px var(--mauve-8); - border-radius: 12px; -} - -.Scrollbar { - background: var(--black-a3); -} -.Scrollbar:hover { - background: var(--black-a5); -} -.ScrollbarThumb { - background: var(--violet-11); - border-radius: 12px; -} - -/* -Invoice -*/ - -.Table { - background-color: var(--violet-4); - color: var(--violet-11); - border-radius: 12px; - border-collapse: collapse; - - tr { - border-bottom: 1px solid gray; - } - tr:last-child { - border-bottom: none; - } -} - -/* -Tabs -*/ - -.TabsTrigger { - background-color: white; - color: var(--violet-11); - border: 2px solid white; - background-clip: border-box; - margin: 4px 0; - border-bottom: 4px solid white; -} -.TabsTrigger:first-child { - border-top-left-radius: 12px; - border-bottom-left-radius: 12px; -} -.TabsTrigger:last-child { - border-top-right-radius: 12px; - border-bottom-right-radius: 12px; -} -.TabsTrigger:hover { - border: 2px solid var(--violet-8); - border-bottom: 4px solid var(--violet-8); -} -.TabsTrigger[data-state="active"] { - color: var(--violet-11); - border-bottom: 4px solid currentColor; -} -.TabsTrigger:active { - box-shadow: 0 0 0 2px black; -} - -/* -Switch -*/ - -.SwitchRoot { - background-color: var(--violet-5); - box-shadow: 0 2px 10px var(--mauve-8); - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - border: 4 solid var(--violet-11); -} -.SwitchRoot:focus { - box-shadow: 0 0 0 2px black; -} -.SwitchRoot[data-state="checked"] { - background-color: var(--violet-11); -} -.SwitchThumb { - background-color: white; -} - -/* -Dialog -*/ -.DialogOverlay { - background-color: var(--black-a9); -} - -.DialogContent { - background-color: white; - box-shadow: hsl(206 22% 7% / 35%) 0 10px 38px -10px, - hsl(206 22% 7% / 20%) 0 10px 20px -15px; -} - -.DialogTitle { - color: var(--violet-11); -} - -/* -Progress -*/ -.progress-bar { - border-radius: 99999px; -} - -.progress-bar-background { - background: var(--violet-5); -} - -.progress-bar-foreground { - background-color: var(--violet-11); -} - -/* -Colors -*/ - -.Selected { - color: var(--violet-11); - border-color: var(--violet-11); -} +:root { + --background: hsl(268, 100%, 91%); + --primary: #6550b9; + --secondary: #d4cafe; + --on-hover: #c2b5f5; + --on-active: #eae0fe; + --on-focus: hsl(252, 69%, 76%); + --element-bg: #fffeff; + --element-border: rgb(242, 235, 247); + --table: #f1e4ff; + --tr-split: gray; + --on-click-border: rgba(0, 0, 0, 0.3); + --shadow: rgba(0, 0, 0, 0.2); +} + +/* Standard Colors */ .danger-color { color: #ff7777; diff --git a/frontend/src/Components/App.tsx b/frontend/src/Components/App.tsx index 489cd73..3648c3d 100644 --- a/frontend/src/Components/App.tsx +++ b/frontend/src/Components/App.tsx @@ -18,7 +18,7 @@ import { PersonalInvoiceView } from "./PersonalView/PersonalInvoiceView"; import { PersonalHistoryView } from "./PersonalView/PersonalHistoryView"; import ScrollDialog from "./Util/ScrollDialog"; -const stylesAvailable = ["purple", "blue"]; +const stylesAvailable = ["purple", "blue", "mc"]; export const BASE_PATH = import.meta.env.VITE_BASE_PATH || ""; function loadTheme(themeName: string) { @@ -118,7 +118,7 @@ export function App() { { }} + onSubmit={() => {}} title="Datenschutzhinweise der PRoST-Sotware" trigger={
Datenschutz
} > diff --git a/frontend/src/Components/HistoryTab/History.tsx b/frontend/src/Components/HistoryTab/History.tsx index fc02699..951c803 100644 --- a/frontend/src/Components/HistoryTab/History.tsx +++ b/frontend/src/Components/HistoryTab/History.tsx @@ -5,10 +5,8 @@ export function History() { return ( <>
-
- - -
+ +
); diff --git a/frontend/src/Components/HistoryTab/ShopHistory.tsx b/frontend/src/Components/HistoryTab/ShopHistory.tsx index 5d98ebe..4c904c6 100644 --- a/frontend/src/Components/HistoryTab/ShopHistory.tsx +++ b/frontend/src/Components/HistoryTab/ShopHistory.tsx @@ -83,7 +83,7 @@ export function ShopHistory(props: { personal: boolean }) { return ( <> - +

Historie

diff --git a/frontend/src/Components/HistoryTab/TransactionHistory.tsx b/frontend/src/Components/HistoryTab/TransactionHistory.tsx index e6377df..2eac2e3 100644 --- a/frontend/src/Components/HistoryTab/TransactionHistory.tsx +++ b/frontend/src/Components/HistoryTab/TransactionHistory.tsx @@ -72,7 +72,7 @@ export function TransactionHistory(props: { personal: boolean }) { } } return ( - +

Transaktionen

diff --git a/frontend/src/Components/InvoiceTab/InvoiceTab.tsx b/frontend/src/Components/InvoiceTab/InvoiceTab.tsx index 1c97863..76b8256 100644 --- a/frontend/src/Components/InvoiceTab/InvoiceTab.tsx +++ b/frontend/src/Components/InvoiceTab/InvoiceTab.tsx @@ -117,16 +117,14 @@ export function InvoiceTab() { const failed = selectedItems.length - result.length; if (result.length > 0) { toast.success( - `${result.length} Rechnung${ - result.length > 1 ? "en" : "" + `${result.length} Rechnung${result.length > 1 ? "en" : "" } versendet!` ); } if (failed) { toast.warn( - `${failed} Rechnung${ - result.length > 1 ? "en" : "" + `${failed} Rechnung${result.length > 1 ? "en" : "" } nicht versendet!` ); } @@ -150,7 +148,7 @@ export function InvoiceTab() { if (result) { toast.warn( result.length + - ` Rechnung${result.length > 1 ? "en" : ""} gelöscht!` + ` Rechnung${result.length > 1 ? "en" : ""} gelöscht!` ); setSelectedItems([]); reloadInvoices(); @@ -188,7 +186,7 @@ export function InvoiceTab() { return ( <>
- +

Rechnungen

@@ -204,10 +202,10 @@ export function InvoiceTab() { onClick={cycleMailed} className={ mailed === undefined - ? "Button" + ? "Button icon" : mailed - ? "Button good-color" - : "Button danger-color" + ? "Button icon good-color" + : "Button icon danger-color" } > @@ -238,7 +236,7 @@ export function InvoiceTab() { invoices={getSelectedInvoices()} onSubmit={deleteSelected} > -
+
@@ -247,7 +245,7 @@ export function InvoiceTab() { invoices={getSelectedInvoices()} onSubmit={mailSelected} > -
+
@@ -257,11 +255,9 @@ export function InvoiceTab() { )}
-
+
-
+
@@ -289,9 +285,8 @@ export function InvoiceTab() { return (
selectPage(index)} > {index + 1} diff --git a/frontend/src/Components/PersonalView/PersonalHistoryView.tsx b/frontend/src/Components/PersonalView/PersonalHistoryView.tsx index 9061f59..1e82880 100644 --- a/frontend/src/Components/PersonalView/PersonalHistoryView.tsx +++ b/frontend/src/Components/PersonalView/PersonalHistoryView.tsx @@ -5,10 +5,8 @@ export function PersonalHistoryView() { return ( <>
-
- - -
+ +
); diff --git a/frontend/src/Components/RootTab.tsx b/frontend/src/Components/RootTab.tsx index 77c58d7..93682ea 100644 --- a/frontend/src/Components/RootTab.tsx +++ b/frontend/src/Components/RootTab.tsx @@ -5,6 +5,7 @@ import { getOwnUser } from "../Queries"; import { ErrorComponent } from "./Util/ErrorTab"; import { UserContainer } from "./SearchTab/UserContainer"; import { PersonalUserOverview } from "./PersonalView/PersonalUserOverview"; +import { BASE_PATH } from "./App"; export function RootTab(props: { switchTheme: () => void }) { const [user, setUser] = useState(undefined); @@ -25,8 +26,9 @@ export function RootTab(props: { switchTheme: () => void }) {

Logo PRoST

diff --git a/frontend/src/Components/SearchTab/ItemCheckout.tsx b/frontend/src/Components/SearchTab/ItemCheckout.tsx index 07b6bf0..42cc391 100644 --- a/frontend/src/Components/SearchTab/ItemCheckout.tsx +++ b/frontend/src/Components/SearchTab/ItemCheckout.tsx @@ -48,14 +48,18 @@ export function ItemCheckout() { function checkout() { if (userId && itemId) { console.log("checkout", userId, itemId); - buyItem(userId, itemId, amount).then((result) => { - if (result) { - toast(amount + "x " + item?.displayName + " gekauft!"); - } else { + buyItem(userId, itemId, amount) + .then((result) => { + if (result) { + toast(amount + "x " + item?.displayName + " gekauft!"); + navigate("/"); + } else { + toast.error(item?.displayName + " konnte nicht gekauft werden!"); + } + }) + .catch(() => { toast.error(item?.displayName + " konnte nicht gekauft werden!"); - } - navigate("/"); - }); + }); } } @@ -92,13 +96,13 @@ export function ItemCheckout() {
-
{amount}
-
diff --git a/frontend/src/Components/SearchTab/ItemDisplay.tsx b/frontend/src/Components/SearchTab/ItemDisplay.tsx index 986bce5..ddd3ade 100644 --- a/frontend/src/Components/SearchTab/ItemDisplay.tsx +++ b/frontend/src/Components/SearchTab/ItemDisplay.tsx @@ -27,12 +27,10 @@ export function ItemDisplay(props: { item: ShopItem }) { return ( <>
- -
- Item Image -
+ + Item Image -
+
{item.displayName}
{formatMoney(item.price)}
diff --git a/frontend/src/Components/SettingsTab/ItemSettingCard.tsx b/frontend/src/Components/SettingsTab/ItemSettingCard.tsx index 81f2c14..83d5562 100644 --- a/frontend/src/Components/SettingsTab/ItemSettingCard.tsx +++ b/frontend/src/Components/SettingsTab/ItemSettingCard.tsx @@ -137,7 +137,7 @@ export function ItemSettingCard(props: { title="Gegenstand löschen?" onSubmit={deleteItem} trigger={ -
+
} @@ -214,15 +214,8 @@ export function ItemSettingCard(props: { />
- -
- Item Image -
+ + Item Image { + createTransaction(user, money.toString(), "deposit").then((result) => { if (result) { toast.success( formatMoney(Math.abs(money)) + @@ -116,12 +112,8 @@ export function UserSettingCard(props: { function setTotalSpent(spentTotal: string) { const money = getValidMoney(spentTotal); - if (money) { - changeUser( - user, - money.toString(), - "moneySpent" - ).then((result) => { + if (money != undefined) { + changeUser(user, money.toString(), "moneySpent").then((result) => { if (result) { toast.success("Gesamtausgaben geändert."); onUpdate(); @@ -136,12 +128,8 @@ export function UserSettingCard(props: { function setBalance(newBalance: string) { const money = getValidMoney(newBalance); - if (money) { - createTransaction( - user, - money.toString(), - "change" - ).then((result) => { + if (money != undefined) { + createTransaction(user, money.toString(), "change").then((result) => { if (result) { toast.success("Kontostand geändert."); onUpdate(); @@ -157,8 +145,7 @@ export function UserSettingCard(props: { function isTransactionAmountValid() { const money = getValidMoney(transactionAmount); - if (transactionAmount.length === 0) - return false; + if (transactionAmount.length === 0) return false; if (money && money > 0 && money <= 50) { return true; @@ -199,12 +186,18 @@ export function UserSettingCard(props: { setTransactionAmount(e.target.value)} /> -
+
@@ -234,7 +227,7 @@ export function UserSettingCard(props: { title="Nutzer löschen?" onSubmit={deleteItem} trigger={ -
+
} diff --git a/frontend/src/Components/StatisticsTab/UserSummaryCard.tsx b/frontend/src/Components/StatisticsTab/UserSummaryCard.tsx index 408e601..6ae75ff 100644 --- a/frontend/src/Components/StatisticsTab/UserSummaryCard.tsx +++ b/frontend/src/Components/StatisticsTab/UserSummaryCard.tsx @@ -41,7 +41,9 @@ export function UserSummaryCard(props: Props) {
💙🦆💙
-
{getLevel(user)}
+
+ {getLevel(user)} +
diff --git a/frontend/src/Components/Util/LoginDialog.tsx b/frontend/src/Components/Util/LoginDialog.tsx index 62d35cb..1252c40 100644 --- a/frontend/src/Components/Util/LoginDialog.tsx +++ b/frontend/src/Components/Util/LoginDialog.tsx @@ -89,7 +89,7 @@ export function LoginDialog() { ) : ( - @@ -98,7 +98,7 @@ export function LoginDialog() {
- diff --git a/frontend/src/Components/Util/ScrollDialog.tsx b/frontend/src/Components/Util/ScrollDialog.tsx index 8df5dfa..1b1b64b 100644 --- a/frontend/src/Components/Util/ScrollDialog.tsx +++ b/frontend/src/Components/Util/ScrollDialog.tsx @@ -87,7 +87,7 @@ const ScrollDialog: React.FC> = ({ >
@@ -95,7 +95,7 @@ const ScrollDialog: React.FC> = ({
-
+
diff --git a/frontend/src/Format.ts b/frontend/src/Format.ts index 31b2e8d..013976f 100644 --- a/frontend/src/Format.ts +++ b/frontend/src/Format.ts @@ -1,4 +1,3 @@ - export function convertTimestampToTime(unixTimestamp: number): string { // Convert Unix timestamp to Date object const date = new Date(unixTimestamp); @@ -66,10 +65,10 @@ export function formatMoney( return formatted + " €"; } - /* DataValidation */ -const EMAIL_PATTERN: string = "^[a-zA-Z0-9+_.-]+@[a-zA-Z0-9.-]+\\.+[a-zA-Z0-9.-]+$"; +const EMAIL_PATTERN: string = + "^[a-zA-Z0-9+_.-]+@[a-zA-Z0-9.-]+\\.+[a-zA-Z0-9.-]+$"; const MAX_NAME_LENGTH: number = 30; export function isValidString(value: string): boolean { @@ -111,16 +110,18 @@ export function getValidMoney(value: string): number | undefined { return undefined; } - const numberValue = parseFloat(value.replace(",", ".").replace(/[^0-9.-]+/g, '')); + const numberValue = parseFloat( + value.replace(",", ".").replace(/[^0-9.-]+/g, "") + ); if (isNaN(numberValue)) { return undefined; } // Check if the number has more than 2 decimal places - const decimalPlaces = (numberValue.toString().split('.')[1] || '').length; + const decimalPlaces = (numberValue.toString().split(".")[1] || "").length; if (decimalPlaces > 2) { return undefined; } return parseFloat(numberValue.toFixed(2)); -} \ No newline at end of file +} diff --git a/frontend/src/style.css b/frontend/src/style.css index 19ad205..0abeb81 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -1,7 +1,4 @@ @import "@radix-ui/colors/black-alpha.css"; -@import "@radix-ui/colors/blue.css"; -@import "@radix-ui/colors/mauve.css"; -@import "@radix-ui/colors/violet.css"; /*add new radix-ui colors used for themes*/ * { @@ -68,7 +65,7 @@ h2 { justify-content: center; margin: 0.3rem 0; font-weight: 500; - + @media (max-width: 768px) { font-size: 2rem; } @@ -81,15 +78,19 @@ h3 { justify-content: center; margin: 0.2rem 0; font-weight: 400; - + @media (max-width: 768px) { font-size: 1rem; } } -a,p,li,strong,div { +a, +p, +li, +strong, +div { @media (max-width: 768px) { - font-size: .8rem; + font-size: 0.8rem; } } @@ -97,7 +98,10 @@ svg { margin: 5px; } -a { +a,[aria-haspopup="dialog"] { + * { + cursor: pointer; + } cursor: pointer; } @@ -111,7 +115,6 @@ a { font-size: 12px; color: black; cursor: pointer; - transition: background-color 0.3s ease; border-radius: 10000px; @media (max-width: 768px) { @@ -273,7 +276,7 @@ a { } #tab-changer > img { - padding: .25rem; + padding: 0.25rem; height: 100%; } @@ -284,7 +287,7 @@ a { flex-wrap: wrap; list-style-type: none; height: auto; - margin: 0 .75rem; + margin: 0 0.75rem; } .TabsTrigger { @@ -296,7 +299,6 @@ a { align-items: center; justify-content: center; font-size: 1rem; - transition: background-color 0.2s; } .TabsTrigger * { cursor: pointer; @@ -305,7 +307,6 @@ a { position: relative; } - /* Alignments */ .SmallGridContainer { display: grid; @@ -321,18 +322,34 @@ a { .GridContainer { display: grid; grid-auto-rows: min-content; - grid-template-columns: repeat(auto-fill, minmax(30rem, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(35rem, 1fr)); @media (max-width: 768px) { - grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); + display: flex; + justify-content: center; + flex-direction: column; } } .SingleCardContainer { display: flex; - justify-content: center; + flex-direction: column; + align-items: center; height: 100%; width: 100%; + + @media (max-width: 768px) { + padding: 0 0.8rem; + } +} +.SingleCardContainer > .DisplayCard { + min-width: 40rem; + max-width: 100rem; + + @media (max-width: 768px) { + min-width: 0rem; + width: 100%; + } } .SpreadContainer { @@ -344,14 +361,24 @@ a { } .AspectRatio { - display: flex; - place-items: center; + position: relative; /* Establishes a new positioning context */ + width: 100%; /* Adjust based on your layout needs */ +} + +.image-fitted { + position: absolute; + top: 0; + left: 0; + width: 100%; height: 100%; + object-fit: contain; /* Scales the image down to fit the container while preserving aspect ratio */ } /* General */ .Button { + transition: background-color 150ms ease; + transition: border-color 150ms ease; display: flex; align-items: center; justify-content: center; @@ -361,7 +388,7 @@ a { height: 3rem; cursor: pointer; @media (max-width: 768px) { - font-size: .75rem; + font-size: 0.75rem; height: 2rem; } } @@ -382,6 +409,8 @@ a { } .Input { + transition: background-color 150ms ease; + transition: border-color 150ms ease; flex: 1; align-items: center; justify-content: center; @@ -392,7 +421,7 @@ a { height: 2rem; min-width: 10rem; @media (max-width: 768px) { - font-size: .9rem; + font-size: 0.9rem; height: 1.5rem; min-width: 5rem; } @@ -408,7 +437,6 @@ a { .icon { min-width: 1rem; - width: 10%; text-align: left; div { @@ -418,6 +446,11 @@ a { } } +.item-desc { + display: flex; + justify-content: space-between; +} + /* Table */ .Table { @@ -469,19 +502,19 @@ a { .date { min-width: 5.5rem; } - + .time { min-width: 3.25rem; } - + .balance { min-width: 5rem; } - + .amount { min-width: 3rem; } - + .name { min-width: 5rem; } @@ -496,7 +529,7 @@ a { min-width: 21rem; width: 100%; @media (max-width: 768px) { - font-size: .75rem; + font-size: 0.75rem; } } .table-entry th { @@ -547,9 +580,9 @@ a { min-height: 44px; } -/* Separato */ +/* Separator */ .Separator { - background-color: var(--violet-11); + background-color: var(--primary); } .Separator[data-orientation="horizontal"] { height: 2px; @@ -562,16 +595,22 @@ a { /* Switch */ .SwitchRoot { + transition: background-color 150ms ease; + transition: border-color 150ms ease; position: relative; width: 2.3rem; height: 1.4rem; border-radius: 9999px; + cursor: pointer; } .SwitchThumb { + transition: background-color 150ms ease; + transition: border-color 150ms ease; + cursor: pointer; display: block; - width: 1rem; - height: 1rem; - transition: transform 100ms; + width: 14px; + height: 14px; + transition: transform 200ms; will-change: transform; transform: translateX(4px); border-radius: 9999px; @@ -650,9 +689,10 @@ a { margin: 0 0.5rem; padding: 0.3rem; min-width: 2.5rem; + transition: background-color 150ms ease; + transition: border-color 150ms ease; } - /* Progress */ @@ -684,12 +724,231 @@ Progress * { font-family: "Montserrat", sans-serif; font-optical-sizing: auto; - font-style: normal; } .bold { font-family: "Montserrat", sans-serif; font-optical-sizing: auto; font-weight: 600; - font-style: bold; +} + +/* Theme-Template */ + +body { + background: var(--background); +} + +div { + color: var(--primary); +} + +a:visited { + color: var(--primary); +} + +#tab-changer { + background-color: var(--shadow); + box-shadow: 0 2px 18px var(--shadow); + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; +} + +#main-search { + color: var(--element-bg); + background-color: var(--shadow); + box-shadow: 0 2px 18px var(--shadow); + border: 4px solid var(--shadow); + border-radius: 12px; +} +#main-search:focus { + border: 4px dashed var(--shadow); +} +#main-search::selection { + background-color: var(--shadow); + color: var(--element-bg); +} + +.Button { + background-color: var(--element-bg); + color: var(--primary); + border: 4px solid var(--element-border); + box-shadow: 0 2px 4px var(--shadow); + border-radius: 12px; +} +.Button:hover { + border: 4px solid var(--on-hover); +} +.Button:active { + background-color: var(--on-active); + box-shadow: 0 0 0 2px var(--on-click-border); +} + +.PageButton { + background-color: var(--element-bg); + color: var(--primary); + border: 4px solid var(--element-border); + box-shadow: 0 2px 4px var(--shadow); + border-radius: 10000px; +} +.PageButton:hover { + border: 4px solid var(--on-hover); +} +.PageButton:active { + background-color: var(--on-active); + box-shadow: 0 0 0 2px var(--shadow); +} + +.CheckBox { + background-color: var(--element-bg); + border-radius: 100%; + box-shadow: 0 2px 10px var(--shadow); + border: 2px solid var(--shadow); +} +.CheckBox:hover { + background-color: var(--on-hover); +} +.CheckBox:active { + background-color: var(--on-active); + box-shadow: 0 0 0 2px var(--shadow); +} + +.Input { + color: var(--primary); + border: 3px solid var(--on-hover); +} +.Input:focus { + border: 3px solid var(--primary); +} + +.DisplayCard { + border: 2px solid var(--primary); + background-color: var(--element-bg); + box-shadow: 0 2px 10px var(--shadow); + border-radius: 12px; +} + +.Scrollbar { + background: var(--shadow); +} +.ScrollbarThumb { + background: var(--primary); + border-radius: 12px; +} + +/* +Invoice +*/ + +.Table { + background-color: var(--table); + color: var(--primary); + border-radius: 12px; + border-collapse: collapse; + + tr { + border-bottom: 1px solid var(--tr-split); + } + tr:last-child { + border-bottom: none; + } +} + +/* +Tabs +*/ + +.TabsTrigger { + background-color: var(--element-bg); + color: var(--primary); + border: 2px solid var(--element-bg); + background-clip: border-box; + margin: 4px 0; + border-bottom: 4px solid var(--element-bg); +} +.TabsTrigger:first-child { + border-top-left-radius: 12px; + border-bottom-left-radius: 12px; +} +.TabsTrigger:last-child { + border-top-right-radius: 12px; + border-bottom-right-radius: 12px; +} +.TabsTrigger:hover { + border: 2px solid var(--on-hover); + border-bottom: 4px solid var(--on-hover); +} +.TabsTrigger[data-state="active"] { + color: var(--primary); + border-bottom: 4px solid currentColor; +} +.TabsTrigger:active { + box-shadow: 0 0 0 2px var(--on-click-border); +} + +/* +Switch +*/ + +.SwitchRoot { + background-color: var(--element-bg); + box-shadow: 0 2px 10px var(--shadow); + -webkit-tap-highlight-color: var(--shadow); + border: 3px solid var(--on-hover); +} +.SwitchRoot[data-state="checked"] { + background-color: var(--primary); + border: 3px solid var(--primary); +} +.SwitchRoot:hover{ + background-color: var(--on-hover); + .SwitchThumb { + background-color: var(--element-bg); + } +} +.SwitchThumb { + background-color: var(--element-bg); +} +.SwitchThumb[data-state="unchecked"] { + background-color: var(--on-hover); +} + +/* +Dialog +*/ +.DialogOverlay { + background-color: var(--black-a9); +} + +.DialogContent { + background-color: var(--element-bg); + box-shadow: hsl(206 22% 7% / 35%) 0 10px 38px -10px, + hsl(206 22% 7% / 20%) 0 10px 20px -15px; +} + +.DialogTitle { + color: var(--primary); +} + +/* +Progress +*/ +.progress-bar { + border-radius: 99999px; +} + +.progress-bar-background { + background: var(--secondary); +} + +.progress-bar-foreground { + background-color: var(--primary); +} + +/* +Colors +*/ + +.Selected { + color: var(--primary); + border-color: var(--primary); } diff --git a/img/buymenu.png b/img/buymenu.png new file mode 100644 index 0000000..313b734 Binary files /dev/null and b/img/buymenu.png differ diff --git a/ldif/openldap-init.ldif b/ldif/openldap-init.ldif index e0816a3..87a3040 100644 --- a/ldif/openldap-init.ldif +++ b/ldif/openldap-init.ldif @@ -6,7 +6,13 @@ o: FSinfo dn: ou=users,dc=fsinfo,dc=fim,dc=uni-passau,dc=de changetype: add -ou: groups +ou: users +objectClass: organizationalUnit +objectClass: top + +dn: ou=serviceAccounts,dc=fsinfo,dc=fim,dc=uni-passau,dc=de +changetype: add +ou: serviceAccounts objectClass: organizationalUnit objectClass: top @@ -30,14 +36,12 @@ objectClass: top objectClass: uidObject userPassword:: cXVpZXRzY2hpZXBhc3N3b3Jk -dn: uid=prostkiosk,ou=users,dc=fsinfo,dc=fim,dc=uni-passau,dc=de +dn: uid=prostkiosk,ou=serviceAccounts,dc=fsinfo,dc=fim,dc=uni-passau,dc=de changetype: add uid: prostkiosk -cn: Kiosk -sn: Prost -objectClass: person +objectClass: account objectClass: top -objectClass: uidObject +objectClass: simpleSecurityObject userPassword:: cXVpZXRzY2hpZXBhc3N3b3Jk dn: ou=groups,dc=fsinfo,dc=fim,dc=uni-passau,dc=de @@ -50,7 +54,6 @@ dn: cn=fsinfo,ou=groups,dc=fsinfo,dc=fim,dc=uni-passau,dc=de changetype: add member: uid=quietschie,ou=users,dc=fsinfo,dc=fim,dc=uni-passau,dc=de member: uid=prostadmin,ou=users,dc=fsinfo,dc=fim,dc=uni-passau,dc=de -member: uid=prostkiosk,ou=users,dc=fsinfo,dc=fim,dc=uni-passau,dc=de cn: fsinfo objectClass: groupOfNames objectClass: top @@ -61,10 +64,3 @@ member: uid=prostadmin,ou=users,dc=fsinfo,dc=fim,dc=uni-passau,dc=de cn: kaffeekasse objectClass: groupOfNames objectClass: top - -dn: cn=kiosk,ou=groups,dc=fsinfo,dc=fim,dc=uni-passau,dc=de -changetype: add -member: uid=prostkiosk,ou=users,dc=fsinfo,dc=fim,dc=uni-passau,dc=de -cn: kiosk -objectClass: groupOfNames -objectClass: top