diff --git a/src/main/java/ch/wisv/chpay/admin/controller/AdminConfettiCreateController.java b/src/main/java/ch/wisv/chpay/admin/controller/AdminConfettiCreateController.java index 3de85e6..af93195 100644 --- a/src/main/java/ch/wisv/chpay/admin/controller/AdminConfettiCreateController.java +++ b/src/main/java/ch/wisv/chpay/admin/controller/AdminConfettiCreateController.java @@ -4,6 +4,7 @@ import ch.wisv.chpay.admin.service.ConfettiFormParser; import ch.wisv.chpay.admin.service.ConfettiFormParser.ConfettiFormResult; import ch.wisv.chpay.core.model.Confetti; +import ch.wisv.chpay.core.model.LedPattern; import ch.wisv.chpay.core.service.NotificationService; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -38,7 +39,7 @@ public AdminConfettiCreateController( @GetMapping public String showCreateForm(Model model) { model.addAttribute("confettiName", ""); - model.addAttribute("colorsCsv", ""); + model.addAttribute("colorValues", List.of("#ff0000")); model.addAttribute("scalarValue", Confetti.DEFAULT_SCALAR); model.addAttribute("minTransactionsValue", 0); model.addAttribute("groupValue", ""); @@ -47,6 +48,8 @@ public String showCreateForm(Model model) { model.addAttribute("defaultValue", false); model.addAttribute("shapeTypes", List.of("SQUARE")); model.addAttribute("shapeValues", List.of("")); + model.addAttribute("ledColorValue", "#ffffff"); + model.addAttribute("ledPatternValue", LedPattern.oplopen.name()); model.addAttribute(MODEL_ATTR_URL_PAGE, "adminConfetti"); return "admin-confetti-create"; } @@ -54,7 +57,7 @@ public String showCreateForm(Model model) { @PostMapping public String createConfetti( @RequestParam("name") String name, - @RequestParam("colors") String colorsInput, + @RequestParam(value = "colors", required = false) List colorsInput, @RequestParam("scalar") String scalarInput, @RequestParam("minTransactions") String minTransactionsInput, @RequestParam("group") String groupInput, @@ -63,6 +66,8 @@ public String createConfetti( @RequestParam(value = "isDefault", defaultValue = "false") boolean isDefault, @RequestParam(value = "shapeType", required = false) List shapeTypes, @RequestParam(value = "shapeValue", required = false) List shapeValues, + @RequestParam("ledColor") String ledColor, + @RequestParam("ledPattern") String ledPattern, RedirectAttributes redirectAttributes) { ConfettiFormResult result = @@ -76,7 +81,9 @@ public String createConfetti( hidden, isDefault, shapeTypes, - shapeValues); + shapeValues, + ledColor, + ledPattern); if (!result.isValid()) { notificationService.addErrorMessage(redirectAttributes, result.getErrorMessage()); return "redirect:/admin/confetti/new"; @@ -92,7 +99,9 @@ public String createConfetti( result.getGroup(), result.isGroupStartsWith(), result.isHidden(), - result.isDefault()); + result.isDefault(), + result.getLedColor(), + result.getLedPattern()); notificationService.addSuccessMessage(redirectAttributes, "Confetti created successfully"); return "redirect:/admin/confetti/" + confetti.getId(); } diff --git a/src/main/java/ch/wisv/chpay/admin/controller/AdminConfettiDetailController.java b/src/main/java/ch/wisv/chpay/admin/controller/AdminConfettiDetailController.java index f0f8edb..d8527ec 100644 --- a/src/main/java/ch/wisv/chpay/admin/controller/AdminConfettiDetailController.java +++ b/src/main/java/ch/wisv/chpay/admin/controller/AdminConfettiDetailController.java @@ -49,7 +49,7 @@ public String showConfetti(@PathVariable("id") UUID id, Model model, RedirectAtt Confetti current = confetti.get(); model.addAttribute(MODEL_ATTR_CONFETTI, current); model.addAttribute("confettiUserCount", adminConfettiService.getUsageCount(current)); - model.addAttribute("colorsCsv", String.join(", ", current.getColors())); + model.addAttribute("colorValues", current.getColors()); model.addAttribute("scalarValue", current.getScalar()); model.addAttribute("minTransactionsValue", current.getMinimumTransactions()); model.addAttribute("groupValue", current.getGroup()); @@ -65,6 +65,8 @@ public String showConfetti(@PathVariable("id") UUID id, Model model, RedirectAtt .map(shape -> shape.getValue() == null ? "" : shape.getValue().trim()) .toList()); + model.addAttribute("ledColorValue", current.getLedColor()); + model.addAttribute("ledPatternValue", current.getLedPattern().name()); model.addAttribute(MODEL_ATTR_URL_PAGE, "adminConfetti"); return "admin-confetti"; } @@ -73,7 +75,7 @@ public String showConfetti(@PathVariable("id") UUID id, Model model, RedirectAtt public String updateConfetti( @PathVariable("id") UUID id, @RequestParam("name") String name, - @RequestParam("colors") String colorsInput, + @RequestParam(value = "colors", required = false) List colorsInput, @RequestParam("scalar") String scalarInput, @RequestParam("minTransactions") String minTransactionsInput, @RequestParam("group") String groupInput, @@ -82,6 +84,8 @@ public String updateConfetti( @RequestParam(value = "isDefault", defaultValue = "false") boolean isDefault, @RequestParam(value = "shapeType", required = false) List shapeTypes, @RequestParam(value = "shapeValue", required = false) List shapeValues, + @RequestParam("ledColor") String ledColor, + @RequestParam("ledPattern") String ledPattern, RedirectAttributes redirectAttributes) { Optional confetti = adminConfettiService.getById(id); @@ -101,7 +105,9 @@ public String updateConfetti( hidden, isDefault, shapeTypes, - shapeValues); + shapeValues, + ledColor, + ledPattern); if (!result.isValid()) { notificationService.addErrorMessage(redirectAttributes, result.getErrorMessage()); return "redirect:/admin/confetti/" + id; @@ -118,7 +124,9 @@ public String updateConfetti( result.getGroup(), result.isGroupStartsWith(), result.isHidden(), - result.isDefault()); + result.isDefault(), + result.getLedColor(), + result.getLedPattern()); } catch (IllegalStateException ex) { notificationService.addErrorMessage(redirectAttributes, ex.getMessage()); return "redirect:/admin/confetti/" + id; diff --git a/src/main/java/ch/wisv/chpay/admin/service/AdminConfettiService.java b/src/main/java/ch/wisv/chpay/admin/service/AdminConfettiService.java index 260a91f..015e117 100644 --- a/src/main/java/ch/wisv/chpay/admin/service/AdminConfettiService.java +++ b/src/main/java/ch/wisv/chpay/admin/service/AdminConfettiService.java @@ -2,6 +2,7 @@ import ch.wisv.chpay.core.model.Confetti; import ch.wisv.chpay.core.model.ConfettiShape; +import ch.wisv.chpay.core.model.LedPattern; import ch.wisv.chpay.core.repository.ConfettiRepository; import ch.wisv.chpay.core.repository.ConfettiUsageCount; import ch.wisv.chpay.core.repository.UserRepository; @@ -78,7 +79,9 @@ public Confetti create( String group, boolean groupStartsWith, boolean hidden, - boolean isDefault) { + boolean isDefault, + String ledColor, + LedPattern ledPattern) { boolean shouldBeDefault = isDefault || confettiRepository.countByDefaultConfettiTrue() == 0; Confetti confetti = new Confetti( @@ -90,7 +93,9 @@ public Confetti create( group, groupStartsWith, hidden, - shouldBeDefault); + shouldBeDefault, + ledColor, + ledPattern); Confetti saved = confettiRepository.save(confetti); enforceSingleDefault(saved.getId()); return saved; @@ -108,7 +113,9 @@ public Confetti update( String group, boolean groupStartsWith, boolean hidden, - boolean isDefault) { + boolean isDefault, + String ledColor, + LedPattern ledPattern) { if (!isDefault && confetti.isDefaultConfetti() && confettiRepository.countByDefaultConfettiTrue() == 1) { @@ -123,7 +130,9 @@ public Confetti update( group, groupStartsWith, hidden, - isDefault); + isDefault, + ledColor, + ledPattern); Confetti saved = confettiRepository.save(confetti); enforceSingleDefault(saved.getId()); return saved; diff --git a/src/main/java/ch/wisv/chpay/admin/service/ConfettiFormParser.java b/src/main/java/ch/wisv/chpay/admin/service/ConfettiFormParser.java index 8b8aafa..7b1def1 100644 --- a/src/main/java/ch/wisv/chpay/admin/service/ConfettiFormParser.java +++ b/src/main/java/ch/wisv/chpay/admin/service/ConfettiFormParser.java @@ -2,8 +2,8 @@ import ch.wisv.chpay.core.model.ConfettiShape; import ch.wisv.chpay.core.model.ConfettiShapeType; +import ch.wisv.chpay.core.model.LedPattern; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Objects; @@ -15,7 +15,7 @@ public class ConfettiFormParser { public ConfettiFormResult parse( String name, - String colorsInput, + List colorsInput, String scalarInput, String minimumTransactionsInput, String groupInput, @@ -23,14 +23,22 @@ public ConfettiFormResult parse( boolean hidden, boolean isDefault, List shapeTypes, - List shapeValues) { + List shapeValues, + String ledColorInput, + String ledPatternInput) { String trimmedName = name == null ? "" : name.trim(); if (trimmedName.isEmpty()) { return ConfettiFormResult.error("Name is required"); } - List colors = parseColors(colorsInput); + List colors = + colorsInput == null + ? List.of() + : colorsInput.stream() + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); if (colors.isEmpty() || colors.stream().anyMatch(color -> !isValidColor(color))) { return ConfettiFormResult.error("Please provide at least one valid hex color (e.g. #FF0000)"); } @@ -57,6 +65,18 @@ public ConfettiFormResult parse( return ConfettiFormResult.error("Add at least one shape"); } + String ledColor = ledColorInput == null ? "" : ledColorInput.trim(); + if (!isValidLedColor(ledColor)) { + return ConfettiFormResult.error("Please provide a valid LED color (e.g. #FF0000)"); + } + + LedPattern ledPattern; + try { + ledPattern = LedPattern.valueOf(ledPatternInput == null ? "" : ledPatternInput.trim()); + } catch (IllegalArgumentException e) { + return ConfettiFormResult.error("Please select a valid LED pattern"); + } + return ConfettiFormResult.success( trimmedName, colors, @@ -66,23 +86,19 @@ public ConfettiFormResult parse( normalizeGroup(groupInput), normalizeGroupStartsWith(groupStartsWith, groupInput), hidden, - isDefault); - } - - private List parseColors(String colorsInput) { - if (colorsInput == null) { - return List.of(); - } - return Arrays.stream(colorsInput.split(",")) - .map(String::trim) - .filter(value -> !value.isEmpty()) - .collect(Collectors.toList()); + isDefault, + ledColor, + ledPattern); } private boolean isValidColor(String color) { return color.matches("^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"); } + private boolean isValidLedColor(String color) { + return color.matches("^#[0-9a-fA-F]{6}$"); + } + private ScalarParseResult parseScalar(String scalarInput) { if (scalarInput == null || scalarInput.trim().isEmpty()) { return ScalarParseResult.invalid(); @@ -197,6 +213,8 @@ public static final class ConfettiFormResult { private final boolean groupStartsWith; private final boolean hidden; private final boolean isDefault; + private final String ledColor; + private final LedPattern ledPattern; private final String errorMessage; private ConfettiFormResult( @@ -209,6 +227,8 @@ private ConfettiFormResult( boolean groupStartsWith, boolean hidden, boolean isDefault, + String ledColor, + LedPattern ledPattern, String errorMessage) { this.name = name; this.colors = colors; @@ -219,6 +239,8 @@ private ConfettiFormResult( this.groupStartsWith = groupStartsWith; this.hidden = hidden; this.isDefault = isDefault; + this.ledColor = ledColor; + this.ledPattern = ledPattern; this.errorMessage = errorMessage; } @@ -231,7 +253,9 @@ public static ConfettiFormResult success( String group, boolean groupStartsWith, boolean hidden, - boolean isDefault) { + boolean isDefault, + String ledColor, + LedPattern ledPattern) { return new ConfettiFormResult( name, colors, @@ -242,12 +266,14 @@ public static ConfettiFormResult success( groupStartsWith, hidden, isDefault, + ledColor, + ledPattern, null); } public static ConfettiFormResult error(String message) { return new ConfettiFormResult( - null, List.of(), List.of(), 0.0, 0, null, false, false, false, message); + null, List.of(), List.of(), 0.0, 0, null, false, false, false, null, null, message); } public boolean isValid() { @@ -290,6 +316,14 @@ public boolean isDefault() { return isDefault; } + public String getLedColor() { + return ledColor; + } + + public LedPattern getLedPattern() { + return ledPattern; + } + public String getErrorMessage() { return errorMessage; } diff --git a/src/main/java/ch/wisv/chpay/api/ledstrip/controller/LedStripController.java b/src/main/java/ch/wisv/chpay/api/ledstrip/controller/LedStripController.java index 56159bf..a9a8ddd 100644 --- a/src/main/java/ch/wisv/chpay/api/ledstrip/controller/LedStripController.java +++ b/src/main/java/ch/wisv/chpay/api/ledstrip/controller/LedStripController.java @@ -1,5 +1,6 @@ package ch.wisv.chpay.api.ledstrip.controller; +import ch.wisv.chpay.core.model.Confetti; import ch.wisv.chpay.core.model.LedPattern; import ch.wisv.chpay.core.model.User; import ch.wisv.chpay.core.model.transaction.Transaction; @@ -53,10 +54,15 @@ public ResponseEntity getLatestTransaction() { .map( t -> { User user = t.getUser(); - Integer r = user != null ? user.getLedR() : null; - Integer g = user != null ? user.getLedG() : null; - Integer b = user != null ? user.getLedB() : null; - LedPattern ledPattern = user != null ? user.getLedPattern() : null; + Confetti confetti = user != null ? user.getConfetti() : null; + String ledColor = confetti != null ? confetti.getLedColor() : null; + LedPattern ledPattern = confetti != null ? confetti.getLedPattern() : null; + Integer r = null, g = null, b = null; + if (ledColor != null && ledColor.length() == 7) { + r = Integer.parseInt(ledColor.substring(1, 3), 16); + g = Integer.parseInt(ledColor.substring(3, 5), 16); + b = Integer.parseInt(ledColor.substring(5, 7), 16); + } String pattern = ledPattern != null ? ledPattern.name() : null; return ResponseEntity.ok( new LatestTransactionResponse( diff --git a/src/main/java/ch/wisv/chpay/core/model/Confetti.java b/src/main/java/ch/wisv/chpay/core/model/Confetti.java index f858302..0d30caa 100644 --- a/src/main/java/ch/wisv/chpay/core/model/Confetti.java +++ b/src/main/java/ch/wisv/chpay/core/model/Confetti.java @@ -57,6 +57,15 @@ public class Confetti { @Column(nullable = false) private LocalDateTime createdAt; + @Setter + @Column(name = "led_color", nullable = false, length = 7) + private String ledColor; + + @Setter + @Enumerated(EnumType.STRING) + @Column(name = "led_pattern", nullable = false) + private LedPattern ledPattern; + public Confetti( String name, List shapes, @@ -66,7 +75,9 @@ public Confetti( String group, boolean groupStartsWith, boolean hidden, - boolean defaultConfetti) { + boolean defaultConfetti, + String ledColor, + LedPattern ledPattern) { this.name = name; this.colors.addAll(colors); this.shapes.addAll(shapes); @@ -77,6 +88,8 @@ public Confetti( this.hidden = hidden; this.defaultConfetti = defaultConfetti; this.createdAt = LocalDateTime.now(); + this.ledColor = ledColor; + this.ledPattern = ledPattern; } public void updateDefinition( @@ -88,7 +101,9 @@ public void updateDefinition( String group, boolean groupStartsWith, boolean hidden, - boolean defaultConfetti) { + boolean defaultConfetti, + String ledColor, + LedPattern ledPattern) { this.name = name; this.shapes.clear(); this.shapes.addAll(shapes); @@ -100,6 +115,8 @@ public void updateDefinition( this.groupStartsWith = normalizeGroupStartsWith(groupStartsWith, this.group); this.hidden = hidden; this.defaultConfetti = defaultConfetti; + this.ledColor = ledColor; + this.ledPattern = ledPattern; } public boolean hasShapeType(ConfettiShapeType type) { diff --git a/src/main/java/ch/wisv/chpay/core/model/User.java b/src/main/java/ch/wisv/chpay/core/model/User.java index 425c0d0..37d2628 100644 --- a/src/main/java/ch/wisv/chpay/core/model/User.java +++ b/src/main/java/ch/wisv/chpay/core/model/User.java @@ -42,23 +42,6 @@ public class User { @JoinColumn(name = "confetti_id") private Confetti confetti; - @Setter - @Column(name = "led_r") - private Integer ledR; - - @Setter - @Column(name = "led_g") - private Integer ledG; - - @Setter - @Column(name = "led_b") - private Integer ledB; - - @Setter - @Enumerated(EnumType.STRING) - @Column(name = "led_pattern") - private LedPattern ledPattern; - @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "user_groups", joinColumns = @JoinColumn(name = "user_id")) @Column(name = "group_name", nullable = false, length = 255) diff --git a/src/main/java/ch/wisv/chpay/customer/controller/LedController.java b/src/main/java/ch/wisv/chpay/customer/controller/LedController.java deleted file mode 100644 index d145919..0000000 --- a/src/main/java/ch/wisv/chpay/customer/controller/LedController.java +++ /dev/null @@ -1,81 +0,0 @@ -package ch.wisv.chpay.customer.controller; - -import ch.wisv.chpay.core.model.LedPattern; -import ch.wisv.chpay.core.model.User; -import ch.wisv.chpay.core.repository.UserRepository; -import ch.wisv.chpay.core.service.NotificationService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.stereotype.Controller; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; - -@Controller -@RequestMapping("/led") -public class LedController extends CustomerController { - - private final UserRepository userRepository; - private final NotificationService notificationService; - - @Autowired - public LedController(UserRepository userRepository, NotificationService notificationService) { - this.userRepository = userRepository; - this.notificationService = notificationService; - } - - @PreAuthorize("hasAnyRole('USER', 'BANNED')") - @GetMapping - public String showLedPage(@ModelAttribute("currentUser") User currentUser, Model model) { - if (currentUser == null) { - return "redirect:/login"; - } - - model.addAttribute("patterns", LedPattern.values()); - model.addAttribute(MODEL_ATTR_URL_PAGE, "led"); - return "led"; - } - - @PreAuthorize("hasAnyRole('USER', 'BANNED')") - @PostMapping("/save") - @Transactional - public String saveLedPreferences( - @RequestParam("r") int r, - @RequestParam("g") int g, - @RequestParam("b") int b, - @RequestParam("pattern") String pattern, - @ModelAttribute("currentUser") User currentUser, - RedirectAttributes redirectAttributes) { - if (currentUser == null) { - return "redirect:/login"; - } - - if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) { - notificationService.addErrorMessage(redirectAttributes, "Invalid color values"); - return "redirect:/led"; - } - - LedPattern ledPattern; - try { - ledPattern = LedPattern.valueOf(pattern); - } catch (IllegalArgumentException e) { - notificationService.addErrorMessage(redirectAttributes, "Invalid pattern"); - return "redirect:/led"; - } - - User user = userRepository.findAndLockByOpenID(currentUser.getOpenID()).orElse(currentUser); - user.setLedR(r); - user.setLedG(g); - user.setLedB(b); - user.setLedPattern(ledPattern); - userRepository.save(user); - - notificationService.addSuccessMessage(redirectAttributes, "LED preferences saved"); - return "redirect:/led"; - } -} diff --git a/src/main/java/db/migration/V20260530_1__Remove_user_led_preferences.java b/src/main/java/db/migration/V20260530_1__Remove_user_led_preferences.java new file mode 100644 index 0000000..62a308d --- /dev/null +++ b/src/main/java/db/migration/V20260530_1__Remove_user_led_preferences.java @@ -0,0 +1,22 @@ +package db.migration; + +import java.sql.Statement; +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; + +public class V20260530_1__Remove_user_led_preferences extends BaseJavaMigration { + + @Override + public void migrate(Context context) throws Exception { + try (Statement stmt = context.getConnection().createStatement()) { + stmt.execute( + """ + ALTER TABLE users + DROP COLUMN IF EXISTS led_r, + DROP COLUMN IF EXISTS led_g, + DROP COLUMN IF EXISTS led_b, + DROP COLUMN IF EXISTS led_pattern + """); + } + } +} diff --git a/src/main/java/db/migration/V20260530_2__Add_led_to_confetti.java b/src/main/java/db/migration/V20260530_2__Add_led_to_confetti.java new file mode 100644 index 0000000..b185550 --- /dev/null +++ b/src/main/java/db/migration/V20260530_2__Add_led_to_confetti.java @@ -0,0 +1,42 @@ +package db.migration; + +import java.sql.Statement; +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; + +public class V20260530_2__Add_led_to_confetti extends BaseJavaMigration { + + @Override + public void migrate(Context context) throws Exception { + try (Statement stmt = context.getConnection().createStatement()) { + stmt.execute( + """ + ALTER TABLE confetti + ADD COLUMN IF NOT EXISTS led_color VARCHAR(7) NOT NULL DEFAULT '#ffffff', + ADD COLUMN IF NOT EXISTS led_pattern VARCHAR(32) NOT NULL DEFAULT 'oplopen' + """); + + stmt.execute( + """ + UPDATE confetti c + SET led_color = ( + SELECT cc.color + FROM confetti_colors cc + WHERE cc.confetti_id = c.id + ORDER BY cc.ctid + LIMIT 1 + ) + WHERE EXISTS ( + SELECT 1 FROM confetti_colors cc WHERE cc.confetti_id = c.id + ) + """); + + stmt.execute( + """ + ALTER TABLE confetti + ALTER COLUMN led_color DROP DEFAULT, + ALTER COLUMN led_pattern DROP DEFAULT + """); + } + } +} diff --git a/src/main/resources/static/js/confetti-admin.js b/src/main/resources/static/js/confetti-admin.js index c746716..728ed89 100644 --- a/src/main/resources/static/js/confetti-admin.js +++ b/src/main/resources/static/js/confetti-admin.js @@ -5,12 +5,6 @@ document.addEventListener('DOMContentLoaded', function () { const TEXT_PLACEHOLDER = 'Text for confetti shape'; const DEFAULT_PLACEHOLDER = 'Value'; - const parseColors = (value) => value.split(',') - .map(item => item.trim()) - .filter(item => item.length > 0); - - const isValidColor = (value) => /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(value); - const getShapeWrapper = (form) => form.querySelector('[data-confetti-shape-list]'); const getShapeRows = (form) => { @@ -19,6 +13,42 @@ document.addEventListener('DOMContentLoaded', function () { return Array.from(wrapper.querySelectorAll('[data-confetti-shape-row]')); }; + const getColorWrapper = (form) => form.querySelector('[data-confetti-color-list]'); + + const getColorRows = (form) => { + const wrapper = getColorWrapper(form); + if (!wrapper) return []; + return Array.from(wrapper.querySelectorAll('[data-confetti-color-row]')); + }; + + const initializeColorRows = (form) => { + const wrapper = getColorWrapper(form); + if (!wrapper) return; + wrapper.querySelectorAll('[data-confetti-color-row]').forEach(row => { + if (row.hasAttribute('data-confetti-color-template')) { + row.removeAttribute('data-confetti-color-template'); + row.removeAttribute('id'); + row.classList.remove('hidden'); + row.querySelectorAll('[disabled]').forEach(el => { el.disabled = false; }); + } + const picker = row.querySelector('input[type="color"]'); + const hex = row.querySelector('[data-confetti-color-hex]'); + if (picker && hex) { + hex.textContent = picker.value; + picker.addEventListener('input', () => { hex.textContent = picker.value; }); + } + }); + }; + + const validateColors = (form) => { + const colorSelection = form.querySelector('#colorSelection'); + if (!colorSelection) return true; + const isValid = getColorRows(form).length > 0; + colorSelection.setCustomValidity(isValid ? '' : 'Add at least one color'); + colorSelection.value = isValid ? 'ok' : ''; + return isValid; + }; + const updateShapeRow = (row) => { const select = row.querySelector('[data-confetti-shape-type]'); const input = row.querySelector('[data-confetti-shape-value]'); @@ -80,7 +110,6 @@ document.addEventListener('DOMContentLoaded', function () { forms.forEach(form => { const nameInput = form.querySelector('#name'); - const colorsInput = form.querySelector('#colors'); const scalarInput = form.querySelector('#scalar'); const minTransactionsInput = form.querySelector('#minTransactions'); @@ -91,14 +120,6 @@ document.addEventListener('DOMContentLoaded', function () { return isValid; }; - const validateColors = () => { - if (!colorsInput) return true; - const colors = parseColors(colorsInput.value); - const isValid = colors.length > 0 && colors.every(isValidColor); - colorsInput.setCustomValidity(isValid ? '' : 'Enter at least one valid hex color'); - return isValid; - }; - const validateScalar = () => { if (!scalarInput) return true; const value = Number.parseFloat(scalarInput.value); @@ -115,13 +136,22 @@ document.addEventListener('DOMContentLoaded', function () { return isValid; }; + const ledColorInput = form.querySelector('#ledColor'); + const ledColorHex = form.querySelector('#led-color-hex'); + if (ledColorInput && ledColorHex) { + ledColorInput.addEventListener('input', () => { + ledColorHex.textContent = ledColorInput.value; + }); + } + initializeShapeRows(form); validateShapes(form); + initializeColorRows(form); + validateColors(form); validateScalar(); validateMinTransactions(); nameInput?.addEventListener('input', validateName); - colorsInput?.addEventListener('input', validateColors); scalarInput?.addEventListener('input', validateScalar); minTransactionsInput?.addEventListener('input', validateMinTransactions); @@ -145,38 +175,43 @@ document.addEventListener('DOMContentLoaded', function () { } }); - const copyTrigger = form.querySelector('[data-copy-markup]'); - copyTrigger?.addEventListener('click', () => { + form.querySelector('[data-confetti-add-shape]')?.addEventListener('click', () => { setTimeout(() => { initializeShapeRows(form); validateShapes(form); }, 0); }); + form.querySelector('[data-confetti-add-color]')?.addEventListener('click', () => { + setTimeout(() => { + initializeColorRows(form); + validateColors(form); + }, 0); + }); + form.addEventListener('click', (event) => { const target = event.target; if (!(target instanceof HTMLElement)) return; - if (target.closest('[data-copy-markup-delete-item]')) { - setTimeout(() => { - validateShapes(form); - }, 0); + if (target.closest('[data-confetti-remove-shape]')) { + setTimeout(() => { validateShapes(form); }, 0); + } + if (target.closest('[data-confetti-remove-color]')) { + setTimeout(() => { validateColors(form); }, 0); } }); form.addEventListener('submit', (event) => { const nameValid = validateName(); - const colorsValid = validateColors(); const scalarValid = validateScalar(); const minTransactionsValid = validateMinTransactions(); const shapesValid = validateShapes(form); + const colorsValid = validateColors(form); if (!nameValid || !colorsValid || !scalarValid || !minTransactionsValid || !shapesValid) { event.preventDefault(); event.stopPropagation(); if (!nameValid && nameInput) { nameInput.focus(); - } else if (!colorsValid && colorsInput) { - colorsInput.focus(); } else if (!scalarValid && scalarInput) { scalarInput.focus(); } else if (!minTransactionsValid && minTransactionsInput) { diff --git a/src/main/resources/templates/admin-confetti-create.html b/src/main/resources/templates/admin-confetti-create.html index 769aebf..fabf8e3 100644 --- a/src/main/resources/templates/admin-confetti-create.html +++ b/src/main/resources/templates/admin-confetti-create.html @@ -43,7 +43,7 @@

Confetti Configuration

-
+
diff --git a/src/main/resources/templates/admin-confetti-table.html b/src/main/resources/templates/admin-confetti-table.html index 5ac37e6..cd4105c 100644 --- a/src/main/resources/templates/admin-confetti-table.html +++ b/src/main/resources/templates/admin-confetti-table.html @@ -29,11 +29,11 @@

Confetti ConfigurationsConfetti Configurations

+ Colors Actions ID @@ -141,6 +142,18 @@

Confetti Configurations + + +
+
+
+
+ +

-
+
diff --git a/src/main/resources/templates/confetti.html b/src/main/resources/templates/confetti.html index 62fc1e9..3c0d0c5 100644 --- a/src/main/resources/templates/confetti.html +++ b/src/main/resources/templates/confetti.html @@ -23,6 +23,20 @@

Confetti

Confetti

+ +
+
+
+
+
+
+
+
+

diff --git a/src/main/resources/templates/fragments/confetti-form.html b/src/main/resources/templates/fragments/confetti-form.html index d02e750..a404048 100644 --- a/src/main/resources/templates/fragments/confetti-form.html +++ b/src/main/resources/templates/fragments/confetti-form.html @@ -1,5 +1,8 @@ -

- +
+ + +

General

+
Please provide a name
-
-
+
- -
- - - Enter at least one valid hex color -
+ +

Particle configuration

- -
- - - Enter a scalar greater than 0 -
- - +
@@ -190,4 +163,92 @@ Add at least one shape and provide values for Path/Text shapes
+ + +
+ +
+
+ + #ff0000 + +
+
+ + #ffffff + +
+
+ +

+ +

+ + Add at least one color +
+ + +
+ + + Enter a scalar greater than 0 +
+ +
+ + +

LED configuration

+ +
+ +
+ + #ffffff +
+
+ +
+ + +
+
diff --git a/src/main/resources/templates/layouts/layout.html b/src/main/resources/templates/layouts/layout.html index ee8d80b..0b96f18 100644 --- a/src/main/resources/templates/layouts/layout.html +++ b/src/main/resources/templates/layouts/layout.html @@ -75,12 +75,6 @@
Confetti - -
  • - - - LED -
  • -
    - -
    - - - - - - - - - diff --git a/src/test/java/ch/wisv/chpay/core/service/ConfettiEligibilityServiceTest.java b/src/test/java/ch/wisv/chpay/core/service/ConfettiEligibilityServiceTest.java index c3f6023..902858b 100644 --- a/src/test/java/ch/wisv/chpay/core/service/ConfettiEligibilityServiceTest.java +++ b/src/test/java/ch/wisv/chpay/core/service/ConfettiEligibilityServiceTest.java @@ -10,6 +10,7 @@ import static org.mockito.Mockito.when; import ch.wisv.chpay.core.model.Confetti; +import ch.wisv.chpay.core.model.LedPattern; import ch.wisv.chpay.core.model.User; import ch.wisv.chpay.core.model.transaction.Transaction.TransactionStatus; import ch.wisv.chpay.core.model.transaction.Transaction.TransactionType; @@ -175,7 +176,9 @@ private Confetti buildConfetti( group, groupStartsWith, false, - isDefault); + isDefault, + "#ffffff", + LedPattern.oplopen); } private User buildUserWithId() {