diff --git a/README.md b/README.md index 245ff01e0..7bbf0f042 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,12 @@ - https://cabi.oopy.io/d208e0c9-1022-4c88-be6d-94f191899111 +### μˆ˜μƒ λ‚΄μ—­ πŸ† + +- μ΄λ…Έλ² μ΄μ…˜μ•„μΉ΄λ°λ―Έ μ„±κ³Ό 곡유 컨퍼런슀 2024Β **κ³Όν•™κΈ°μˆ μ •λ³΄ν†΅μ‹ λΆ€ μž₯관상(πŸ₯‡λŒ€μƒ)**Β μˆ˜μƒ (2024) +- μ΄λ…Έλ² μ΄μ…˜μ•„μΉ΄λ°λ―Έ μ„±κ³Ό 곡유 컨퍼런슀 2023Β **μ •λ³΄ν†΅μ‹ κΈ°νšν‰κ°€μ› 원μž₯상(πŸ₯ˆμ΅œμš°μˆ˜μƒ)**Β μˆ˜μƒ (2023) +- μ΄λ…Έλ² μ΄μ…˜μ•„μΉ΄λ°λ―Έ μ„±κ³Ό 곡유 컨퍼런슀 2022 **μ΄λ…Έλ² μ΄μ…˜ 아카데미 ν•™μž₯상(πŸ₯‰μš°μˆ˜μƒ)** μˆ˜μƒ (2022) + ### 기술적 도전 - 지속할 수 있고, ν™•μž₯ν•  수 μžˆλŠ” μ„œλΉ„μŠ€λ₯Ό 지ν–₯ν•˜κ³ , ν•œμ •λœ μžμ›μœΌλ‘œ **μ¦κ°€ν•˜λŠ” μ‚¬μš©μž**λ“€μ—κ²Œ μ–‘μ§ˆμ˜ μ„œλΉ„μŠ€λ₯Ό μ œκ³΅ν•˜κΈ° μœ„ν•΄ **Cabi νŒ€**은 λ‹€μŒκ³Ό 같이 λ…Έλ ₯ν–ˆμŠ΅λ‹ˆλ‹€: diff --git a/backend/database/cabi_local.sql b/backend/database/cabi_local.sql index 668508da5..e0861ae15 100644 --- a/backend/database/cabi_local.sql +++ b/backend/database/cabi_local.sql @@ -121,22 +121,22 @@ LOCK TABLES `cabinet` WRITE; /*!40000 ALTER TABLE `cabinet` DISABLE KEYS */; INSERT INTO `cabinet` -VALUES (1, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 5, '', '', 1), - (2, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 90, 5, '', '', 1), - (3, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 91, 5, '', '', 1), - (4, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 92, 5, '', '', 1), - (5, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 93, 5, '', '', 1), - (6, 5, 0, 'SHARE', 3, 'AVAILABLE', '', 94, 5, '', '', 1), - (7, 6, 0, 'SHARE', 3, 'AVAILABLE', '', 95, 5, '', '', 1), - (8, 7, 0, 'SHARE', 3, 'AVAILABLE', '', 96, 5, '', '', 1), - (9, 8, 0, 'SHARE', 3, 'AVAILABLE', '', 97, 5, '', '', 1), - (10, 9, 0, 'SHARE', 3, 'AVAILABLE', '', 98, 5, '', '', 1), - (11, 10, 0, 'SHARE', 3, 'AVAILABLE', '', 99, 5, '', '', 1), - (12, 11, 0, 'SHARE', 3, 'AVAILABLE', '', 100, 5, '', '', 1), - (13, 12, 0, 'SHARE', 3, 'AVAILABLE', '', 101, 5, '', '', 1), - (14, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 102, 5, '', '', 1), - (15, 1, 1, 'SHARE', 3, 'AVAILABLE', '', 103, 5, '', '', 1), - (16, 2, 1, 'SHARE', 3, 'AVAILABLE', '', 104, 5, '', '', 1), +VALUES (1, 0, 0, 'SHARE', 4, 'AVAILABLE', '', 89, 5, '', '', 1), + (2, 1, 0, 'SHARE', 4, 'AVAILABLE', '', 90, 5, '', '', 1), + (3, 2, 0, 'SHARE', 4, 'AVAILABLE', '', 91, 5, '', '', 1), + (4, 3, 0, 'SHARE', 4, 'AVAILABLE', '', 92, 5, '', '', 1), + (5, 4, 0, 'SHARE', 4, 'AVAILABLE', '', 93, 5, '', '', 1), + (6, 5, 0, 'SHARE', 4, 'AVAILABLE', '', 94, 5, '', '', 1), + (7, 6, 0, 'SHARE', 4, 'AVAILABLE', '', 95, 5, '', '', 1), + (8, 7, 0, 'SHARE', 4, 'AVAILABLE', '', 96, 5, '', '', 1), + (9, 8, 0, 'SHARE', 4, 'AVAILABLE', '', 97, 5, '', '', 1), + (10, 9, 0, 'SHARE', 4, 'AVAILABLE', '', 98, 5, '', '', 1), + (11, 10, 0, 'SHARE', 4, 'AVAILABLE', '', 99, 5, '', '', 1), + (12, 11, 0, 'SHARE', 4, 'AVAILABLE', '', 100, 5, '', '', 1), + (13, 12, 0, 'SHARE', 4, 'AVAILABLE', '', 101, 5, '', '', 1), + (14, 0, 1, 'SHARE', 4, 'AVAILABLE', '', 102, 5, '', '', 1), + (15, 1, 1, 'SHARE', 4, 'AVAILABLE', '', 103, 5, '', '', 1), + (16, 2, 1, 'SHARE', 4, 'AVAILABLE', '', 104, 5, '', '', 1), (17, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 105, 5, '', '', 1), (18, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 106, 5, '', '', 1), (19, 5, 1, 'PRIVATE', 1, 'AVAILABLE', '', 107, 5, '', '', 1), @@ -173,9 +173,9 @@ VALUES (1, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 5, '', '', 1), (50, 10, 3, 'PRIVATE', 1, 'AVAILABLE', '', 138, 5, '', '', 1), (51, 11, 3, 'PRIVATE', 1, 'AVAILABLE', '', 139, 5, '', '', 1), (52, 12, 3, 'PRIVATE', 1, 'AVAILABLE', '', 140, 5, '', '', 1), - (53, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 141, 6, '', '', 1), - (54, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 142, 6, '', '', 1), - (55, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 143, 6, '', '', 1), + (53, 0, 0, 'SHARE', 4, 'AVAILABLE', '', 141, 6, '', '', 1), + (54, 1, 0, 'SHARE', 4, 'AVAILABLE', '', 142, 6, '', '', 1), + (55, 0, 1, 'SHARE', 4, 'AVAILABLE', '', 143, 6, '', '', 1), (56, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 144, 6, '', '', 1), (57, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 145, 6, '', '', 1), (58, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 146, 6, '', '', 1), @@ -201,11 +201,11 @@ VALUES (1, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 5, '', '', 1), (78, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 34, 2, '', '', 1), (79, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 35, 2, '', '', 1), (80, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 36, 2, '', '', 1), - (81, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 1, 1, '', '', 1), - (82, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 2, 1, '', '', 1), - (83, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 3, 1, '', '', 1), - (84, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 4, 1, '', '', 1), - (85, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 5, 1, '', '', 1), + (81, 0, 0, 'SHARE', 4, 'AVAILABLE', '', 1, 1, '', '', 1), + (82, 1, 0, 'SHARE', 4, 'AVAILABLE', '', 2, 1, '', '', 1), + (83, 2, 0, 'SHARE', 4, 'AVAILABLE', '', 3, 1, '', '', 1), + (84, 3, 0, 'SHARE', 4, 'AVAILABLE', '', 4, 1, '', '', 1), + (85, 0, 1, 'SHARE', 4, 'AVAILABLE', '', 5, 1, '', '', 1), (86, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 6, 1, '', '', 1), (87, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 7, 1, '', '', 1), (88, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 8, 1, '', '', 1), @@ -217,22 +217,22 @@ VALUES (1, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 5, '', '', 1), (94, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 14, 1, '', '', 1), (95, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 15, 1, '', '', 1), (96, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 16, 1, '', '', 1), - (97, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 37, 3, '', '', 1), - (98, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 38, 3, '', '', 1), - (99, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 39, 3, '', '', 1), - (100, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 40, 3, '', '', 1), - (101, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 41, 3, '', '', 1), - (102, 5, 0, 'SHARE', 3, 'AVAILABLE', '', 42, 3, '', '', 1), - (103, 6, 0, 'SHARE', 3, 'AVAILABLE', '', 43, 3, '', '', 1), - (104, 7, 0, 'SHARE', 3, 'AVAILABLE', '', 44, 3, '', '', 1), - (105, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 45, 3, '', '', 1), - (106, 1, 1, 'SHARE', 3, 'AVAILABLE', '', 46, 3, '', '', 1), - (107, 2, 1, 'SHARE', 3, 'AVAILABLE', '', 47, 3, '', '', 1), - (108, 3, 1, 'SHARE', 3, 'AVAILABLE', '', 48, 3, '', '', 1), - (109, 4, 1, 'SHARE', 3, 'AVAILABLE', '', 49, 3, '', '', 1), - (110, 5, 1, 'SHARE', 3, 'AVAILABLE', '', 50, 3, '', '', 1), - (111, 6, 1, 'SHARE', 3, 'AVAILABLE', '', 51, 3, '', '', 1), - (112, 7, 1, 'SHARE', 3, 'AVAILABLE', '', 52, 3, '', '', 1), + (97, 0, 0, 'SHARE', 4, 'AVAILABLE', '', 37, 3, '', '', 1), + (98, 1, 0, 'SHARE', 4, 'AVAILABLE', '', 38, 3, '', '', 1), + (99, 2, 0, 'SHARE', 4, 'AVAILABLE', '', 39, 3, '', '', 1), + (100, 3, 0, 'SHARE', 4, 'AVAILABLE', '', 40, 3, '', '', 1), + (101, 4, 0, 'SHARE', 4, 'AVAILABLE', '', 41, 3, '', '', 1), + (102, 5, 0, 'SHARE', 4, 'AVAILABLE', '', 42, 3, '', '', 1), + (103, 6, 0, 'SHARE', 4, 'AVAILABLE', '', 43, 3, '', '', 1), + (104, 7, 0, 'SHARE', 4, 'AVAILABLE', '', 44, 3, '', '', 1), + (105, 0, 1, 'SHARE', 4, 'AVAILABLE', '', 45, 3, '', '', 1), + (106, 1, 1, 'SHARE', 4, 'AVAILABLE', '', 46, 3, '', '', 1), + (107, 2, 1, 'SHARE', 4, 'AVAILABLE', '', 47, 3, '', '', 1), + (108, 3, 1, 'SHARE', 4, 'AVAILABLE', '', 48, 3, '', '', 1), + (109, 4, 1, 'SHARE', 4, 'AVAILABLE', '', 49, 3, '', '', 1), + (110, 5, 1, 'SHARE', 4, 'AVAILABLE', '', 50, 3, '', '', 1), + (111, 6, 1, 'SHARE', 4, 'AVAILABLE', '', 51, 3, '', '', 1), + (112, 7, 1, 'SHARE', 4, 'AVAILABLE', '', 52, 3, '', '', 1), (113, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 53, 3, '', '', 1), (114, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 54, 3, '', '', 1), (115, 2, 2, 'PRIVATE', 1, 'AVAILABLE', '', 55, 3, '', '', 1), @@ -269,22 +269,22 @@ VALUES (1, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 5, '', '', 1), (146, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 86, 4, '', '', 1), (147, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 87, 4, '', '', 1), (148, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 88, 4, '', '', 1), - (149, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 37, 16, '', '', 1), - (150, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 38, 16, '', '', 1), - (151, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 39, 16, '', '', 1), - (152, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 40, 16, '', '', 1), - (153, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 41, 16, '', '', 1), - (154, 5, 0, 'SHARE', 3, 'AVAILABLE', '', 42, 16, '', '', 1), - (155, 6, 0, 'SHARE', 3, 'AVAILABLE', '', 43, 16, '', '', 1), - (156, 7, 0, 'SHARE', 3, 'AVAILABLE', '', 44, 16, '', '', 1), - (157, 8, 0, 'SHARE', 3, 'AVAILABLE', '', 45, 16, '', '', 1), - (158, 9, 0, 'SHARE', 3, 'AVAILABLE', '', 46, 16, '', '', 1), - (159, 10, 0, 'SHARE', 3, 'AVAILABLE', '', 47, 16, '', '', 1), - (160, 11, 0, 'SHARE', 3, 'AVAILABLE', '', 48, 16, '', '', 1), - (161, 12, 0, 'SHARE', 3, 'AVAILABLE', '', 49, 16, '', '', 1), - (162, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 50, 16, '', '', 1), - (163, 1, 1, 'SHARE', 3, 'AVAILABLE', '', 51, 16, '', '', 1), - (164, 2, 1, 'SHARE', 3, 'AVAILABLE', '', 52, 16, '', '', 1), + (149, 0, 0, 'SHARE', 4, 'AVAILABLE', '', 37, 16, '', '', 1), + (150, 1, 0, 'SHARE', 4, 'AVAILABLE', '', 38, 16, '', '', 1), + (151, 2, 0, 'SHARE', 4, 'AVAILABLE', '', 39, 16, '', '', 1), + (152, 3, 0, 'SHARE', 4, 'AVAILABLE', '', 40, 16, '', '', 1), + (153, 4, 0, 'SHARE', 4, 'AVAILABLE', '', 41, 16, '', '', 1), + (154, 5, 0, 'SHARE', 4, 'AVAILABLE', '', 42, 16, '', '', 1), + (155, 6, 0, 'SHARE', 4, 'AVAILABLE', '', 43, 16, '', '', 1), + (156, 7, 0, 'SHARE', 4, 'AVAILABLE', '', 44, 16, '', '', 1), + (157, 8, 0, 'SHARE', 4, 'AVAILABLE', '', 45, 16, '', '', 1), + (158, 9, 0, 'SHARE', 4, 'AVAILABLE', '', 46, 16, '', '', 1), + (159, 10, 0, 'SHARE', 4, 'AVAILABLE', '', 47, 16, '', '', 1), + (160, 11, 0, 'SHARE', 4, 'AVAILABLE', '', 48, 16, '', '', 1), + (161, 12, 0, 'SHARE', 4, 'AVAILABLE', '', 49, 16, '', '', 1), + (162, 0, 1, 'SHARE', 4, 'AVAILABLE', '', 50, 16, '', '', 1), + (163, 1, 1, 'SHARE', 4, 'AVAILABLE', '', 51, 16, '', '', 1), + (164, 2, 1, 'SHARE', 4, 'AVAILABLE', '', 52, 16, '', '', 1), (165, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 53, 16, '', '', 1), (166, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 54, 16, '', '', 1), (167, 5, 1, 'PRIVATE', 1, 'AVAILABLE', '', 55, 16, '', '', 1), @@ -321,10 +321,10 @@ VALUES (1, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 5, '', '', 1), (198, 10, 3, 'PRIVATE', 1, 'AVAILABLE', '', 86, 16, '', '', 1), (199, 11, 3, 'PRIVATE', 1, 'AVAILABLE', '', 87, 16, '', '', 1), (200, 12, 3, 'PRIVATE', 1, 'AVAILABLE', '', 88, 16, '', '', 1), - (201, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 17, '', '', 1), - (202, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 90, 17, '', '', 1), - (203, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 91, 17, '', '', 1), - (204, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 92, 17, '', '', 1), + (201, 0, 0, 'SHARE', 4, 'AVAILABLE', '', 89, 17, '', '', 1), + (202, 1, 0, 'SHARE', 4, 'AVAILABLE', '', 90, 17, '', '', 1), + (203, 2, 0, 'SHARE', 4, 'AVAILABLE', '', 91, 17, '', '', 1), + (204, 0, 1, 'SHARE', 4, 'AVAILABLE', '', 92, 17, '', '', 1), (205, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 93, 17, '', '', 1), (206, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 94, 17, '', '', 1), (207, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 95, 17, '', '', 1), @@ -333,12 +333,12 @@ VALUES (1, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 5, '', '', 1), (210, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 98, 17, '', '', 1), (211, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 99, 17, '', '', 1), (212, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 100, 17, '', '', 1), - (213, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 17, 15, '', '', 1), - (214, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 18, 15, '', '', 1), - (215, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 19, 15, '', '', 1), - (216, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 20, 15, '', '', 1), - (217, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 21, 15, '', '', 1), - (218, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 22, 15, '', '', 1), + (213, 0, 0, 'SHARE', 4, 'AVAILABLE', '', 17, 15, '', '', 1), + (214, 1, 0, 'SHARE', 4, 'AVAILABLE', '', 18, 15, '', '', 1), + (215, 2, 0, 'SHARE', 4, 'AVAILABLE', '', 19, 15, '', '', 1), + (216, 3, 0, 'SHARE', 4, 'AVAILABLE', '', 20, 15, '', '', 1), + (217, 4, 0, 'SHARE', 4, 'AVAILABLE', '', 21, 15, '', '', 1), + (218, 0, 1, 'SHARE', 4, 'AVAILABLE', '', 22, 15, '', '', 1), (219, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 23, 15, '', '', 1), (220, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 24, 15, '', '', 1), (221, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 25, 15, '', '', 1), @@ -353,11 +353,11 @@ VALUES (1, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 5, '', '', 1), (230, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 34, 15, '', '', 1), (231, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 35, 15, '', '', 1), (232, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 36, 15, '', '', 1), - (233, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 1, 14, '', '', 1), - (234, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 2, 14, '', '', 1), - (235, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 3, 14, '', '', 1), - (236, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 4, 14, '', '', 1), - (237, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 5, 14, '', '', 1), + (233, 0, 0, 'SHARE', 4, 'AVAILABLE', '', 1, 14, '', '', 1), + (234, 1, 0, 'SHARE', 4, 'AVAILABLE', '', 2, 14, '', '', 1), + (235, 2, 0, 'SHARE', 4, 'AVAILABLE', '', 3, 14, '', '', 1), + (236, 3, 0, 'SHARE', 4, 'AVAILABLE', '', 4, 14, '', '', 1), + (237, 0, 1, 'SHARE', 4, 'AVAILABLE', '', 5, 14, '', '', 1), (238, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 6, 14, '', '', 1), (239, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 7, 14, '', '', 1), (240, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 8, 14, '', '', 1), @@ -369,22 +369,22 @@ VALUES (1, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 5, '', '', 1), (246, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 14, 14, '', '', 1), (247, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 15, 14, '', '', 1), (248, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 16, 14, '', '', 1), - (249, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 37, 20, '', '', 1), - (250, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 38, 20, '', '', 1), - (251, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 39, 20, '', '', 1), - (252, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 40, 20, '', '', 1), - (253, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 41, 20, '', '', 1), - (254, 5, 0, 'SHARE', 3, 'AVAILABLE', '', 42, 20, '', '', 1), - (255, 6, 0, 'SHARE', 3, 'AVAILABLE', '', 43, 20, '', '', 1), - (256, 7, 0, 'SHARE', 3, 'AVAILABLE', '', 44, 20, '', '', 1), - (257, 8, 0, 'SHARE', 3, 'AVAILABLE', '', 45, 20, '', '', 1), - (258, 9, 0, 'SHARE', 3, 'AVAILABLE', '', 46, 20, '', '', 1), - (259, 10, 0, 'SHARE', 3, 'AVAILABLE', '', 47, 20, '', '', 1), - (260, 11, 0, 'SHARE', 3, 'AVAILABLE', '', 48, 20, '', '', 1), - (261, 12, 0, 'SHARE', 3, 'AVAILABLE', '', 49, 20, '', '', 1), - (262, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 50, 20, '', '', 1), - (263, 1, 1, 'SHARE', 3, 'AVAILABLE', '', 51, 20, '', '', 1), - (264, 2, 1, 'SHARE', 3, 'AVAILABLE', '', 52, 20, '', '', 1), + (249, 0, 0, 'SHARE', 4, 'AVAILABLE', '', 37, 20, '', '', 1), + (250, 1, 0, 'SHARE', 4, 'AVAILABLE', '', 38, 20, '', '', 1), + (251, 2, 0, 'SHARE', 4, 'AVAILABLE', '', 39, 20, '', '', 1), + (252, 3, 0, 'SHARE', 4, 'AVAILABLE', '', 40, 20, '', '', 1), + (253, 4, 0, 'SHARE', 4, 'AVAILABLE', '', 41, 20, '', '', 1), + (254, 5, 0, 'SHARE', 4, 'AVAILABLE', '', 42, 20, '', '', 1), + (255, 6, 0, 'SHARE', 4, 'AVAILABLE', '', 43, 20, '', '', 1), + (256, 7, 0, 'SHARE', 4, 'AVAILABLE', '', 44, 20, '', '', 1), + (257, 8, 0, 'SHARE', 4, 'AVAILABLE', '', 45, 20, '', '', 1), + (258, 9, 0, 'SHARE', 4, 'AVAILABLE', '', 46, 20, '', '', 1), + (259, 10, 0, 'SHARE', 4, 'AVAILABLE', '', 47, 20, '', '', 1), + (260, 11, 0, 'SHARE', 4, 'AVAILABLE', '', 48, 20, '', '', 1), + (261, 12, 0, 'SHARE', 4, 'AVAILABLE', '', 49, 20, '', '', 1), + (262, 0, 1, 'SHARE', 4, 'AVAILABLE', '', 50, 20, '', '', 1), + (263, 1, 1, 'SHARE', 4, 'AVAILABLE', '', 51, 20, '', '', 1), + (264, 2, 1, 'SHARE', 4, 'AVAILABLE', '', 52, 20, '', '', 1), (265, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 53, 20, '', '', 1), (266, 4, 1, 'PRIVATE', 1, 'AVAILABLE', '', 54, 20, '', '', 1), (267, 5, 1, 'PRIVATE', 1, 'AVAILABLE', '', 55, 20, '', '', 1), @@ -421,12 +421,12 @@ VALUES (1, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 5, '', '', 1), (298, 10, 3, 'PRIVATE', 1, 'AVAILABLE', '', 86, 20, '', '', 1), (299, 11, 3, 'PRIVATE', 1, 'AVAILABLE', '', 87, 20, '', '', 1), (300, 12, 3, 'PRIVATE', 1, 'AVAILABLE', '', 88, 20, '', '', 1), - (301, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 17, 19, '', '', 1), - (302, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 18, 19, '', '', 1), - (303, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 19, 19, '', '', 1), - (304, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 20, 19, '', '', 1), - (305, 4, 0, 'SHARE', 3, 'AVAILABLE', '', 21, 19, '', '', 1), - (306, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 22, 19, '', '', 1), + (301, 0, 0, 'SHARE', 4, 'AVAILABLE', '', 17, 19, '', '', 1), + (302, 1, 0, 'SHARE', 4, 'AVAILABLE', '', 18, 19, '', '', 1), + (303, 2, 0, 'SHARE', 4, 'AVAILABLE', '', 19, 19, '', '', 1), + (304, 3, 0, 'SHARE', 4, 'AVAILABLE', '', 20, 19, '', '', 1), + (305, 4, 0, 'SHARE', 4, 'AVAILABLE', '', 21, 19, '', '', 1), + (306, 0, 1, 'SHARE', 4, 'AVAILABLE', '', 22, 19, '', '', 1), (307, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 23, 19, '', '', 1), (308, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 24, 19, '', '', 1), (309, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 25, 19, '', '', 1), @@ -441,11 +441,11 @@ VALUES (1, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 5, '', '', 1), (318, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 34, 19, '', '', 1), (319, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 35, 19, '', '', 1), (320, 4, 3, 'PRIVATE', 1, 'AVAILABLE', '', 36, 19, '', '', 1), - (321, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 1, 18, '', '', 1), - (322, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 2, 18, '', '', 1), - (323, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 3, 18, '', '', 1), - (324, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 4, 18, '', '', 1), - (325, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 5, 18, '', '', 1), + (321, 0, 0, 'SHARE', 4, 'AVAILABLE', '', 1, 18, '', '', 1), + (322, 1, 0, 'SHARE', 4, 'AVAILABLE', '', 2, 18, '', '', 1), + (323, 2, 0, 'SHARE', 4, 'AVAILABLE', '', 3, 18, '', '', 1), + (324, 3, 0, 'SHARE', 4, 'AVAILABLE', '', 4, 18, '', '', 1), + (325, 0, 1, 'SHARE', 4, 'AVAILABLE', '', 5, 18, '', '', 1), (326, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 6, 18, '', '', 1), (327, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 7, 18, '', '', 1), (328, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 8, 18, '', '', 1), @@ -457,10 +457,10 @@ VALUES (1, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 5, '', '', 1), (334, 1, 3, 'PRIVATE', 1, 'AVAILABLE', '', 14, 18, '', '', 1), (335, 2, 3, 'PRIVATE', 1, 'AVAILABLE', '', 15, 18, '', '', 1), (336, 3, 3, 'PRIVATE', 1, 'AVAILABLE', '', 16, 18, '', '', 1), - (337, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 21, '', '', 1), - (338, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 90, 21, '', '', 1), - (339, 0, 1, 'SHARE', 3, 'AVAILABLE', '', 91, 21, '', '', 1), - (340, 1, 1, 'SHARE', 3, 'AVAILABLE', '', 92, 21, '', '', 1), + (337, 0, 0, 'SHARE', 4, 'AVAILABLE', '', 89, 21, '', '', 1), + (338, 1, 0, 'SHARE', 4, 'AVAILABLE', '', 90, 21, '', '', 1), + (339, 0, 1, 'SHARE', 4, 'AVAILABLE', '', 91, 21, '', '', 1), + (340, 1, 1, 'SHARE', 4, 'AVAILABLE', '', 92, 21, '', '', 1), (341, 0, 2, 'PRIVATE', 1, 'AVAILABLE', '', 93, 21, '', '', 1), (342, 1, 2, 'PRIVATE', 1, 'AVAILABLE', '', 94, 21, '', '', 1), (343, 0, 3, 'PRIVATE', 1, 'AVAILABLE', '', 95, 21, '', '', 1), @@ -490,32 +490,32 @@ VALUES (1, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 89, 5, '', '', 1), (367, 2, 1, 'CLUB', 1, 'AVAILABLE', '', 23, 9, '', '', 1), (368, 3, 1, 'CLUB', 1, 'AVAILABLE', '', 24, 9, '', '', 1), (369, 0, 0, 'CLUB', 1, 'AVAILABLE', '', 25, 10, '', '', 1), - (370, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 26, 10, '', '', 1), - (371, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 27, 10, '', '', 1), - (372, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 28, 10, '', '', 1), + (370, 1, 0, 'SHARE', 4, 'AVAILABLE', '', 26, 10, '', '', 1), + (371, 2, 0, 'SHARE', 4, 'AVAILABLE', '', 27, 10, '', '', 1), + (372, 3, 0, 'SHARE', 4, 'AVAILABLE', '', 28, 10, '', '', 1), (373, 0, 1, 'CLUB', 1, 'AVAILABLE', '', 29, 10, '', '', 1), (374, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 30, 10, '', '', 1), (375, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 31, 10, '', '', 1), (376, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 32, 10, '', '', 1), - (377, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 33, 11, '', '', 1), - (378, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 34, 11, '', '', 1), - (379, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 35, 11, '', '', 1), - (380, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 36, 11, '', '', 1), + (377, 0, 0, 'SHARE', 4, 'AVAILABLE', '', 33, 11, '', '', 1), + (378, 1, 0, 'SHARE', 4, 'AVAILABLE', '', 34, 11, '', '', 1), + (379, 2, 0, 'SHARE', 4, 'AVAILABLE', '', 35, 11, '', '', 1), + (380, 3, 0, 'SHARE', 4, 'AVAILABLE', '', 36, 11, '', '', 1), (381, 0, 1, 'PRIVATE', 1, 'AVAILABLE', '', 37, 11, '', '', 1), (382, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 38, 11, '', '', 1), (383, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 39, 11, '', '', 1), (384, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 40, 11, '', '', 1), - (385, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 41, 12, '', '', 1), - (386, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 42, 12, '', '', 1), - (387, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 43, 12, '', '', 1), - (388, 3, 0, 'SHARE', 3, 'AVAILABLE', '', 44, 12, '', '', 1), + (385, 0, 0, 'SHARE', 4, 'AVAILABLE', '', 41, 12, '', '', 1), + (386, 1, 0, 'SHARE', 4, 'AVAILABLE', '', 42, 12, '', '', 1), + (387, 2, 0, 'SHARE', 4, 'AVAILABLE', '', 43, 12, '', '', 1), + (388, 3, 0, 'SHARE', 4, 'AVAILABLE', '', 44, 12, '', '', 1), (389, 0, 1, 'PRIVATE', 1, 'AVAILABLE', '', 45, 12, '', '', 1), (390, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 46, 12, '', '', 1), (391, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 47, 12, '', '', 1), (392, 3, 1, 'PRIVATE', 1, 'AVAILABLE', '', 48, 12, '', '', 1), - (393, 0, 0, 'SHARE', 3, 'AVAILABLE', '', 49, 13, '', '', 1), - (394, 1, 0, 'SHARE', 3, 'AVAILABLE', '', 50, 13, '', '', 1), - (395, 2, 0, 'SHARE', 3, 'AVAILABLE', '', 51, 13, '', '', 1), + (393, 0, 0, 'SHARE', 4, 'AVAILABLE', '', 49, 13, '', '', 1), + (394, 1, 0, 'SHARE', 4, 'AVAILABLE', '', 50, 13, '', '', 1), + (395, 2, 0, 'SHARE', 4, 'AVAILABLE', '', 51, 13, '', '', 1), (396, 0, 1, 'PRIVATE', 1, 'AVAILABLE', '', 52, 13, '', '', 1), (397, 1, 1, 'PRIVATE', 1, 'AVAILABLE', '', 53, 13, '', '', 1), (398, 2, 1, 'PRIVATE', 1, 'AVAILABLE', '', 54, 13, '', '', 1); @@ -742,7 +742,8 @@ VALUES (1, 0, 'EXTENSION_PREV', 'EXTENSION'), (17, 200, 'ADMIN_REWARD_200', 'ADMIN_REWARD'), (18, 500, 'ADMIN_REWARD_500', 'ADMIN_REWARD'), (19, 1000, 'ADMIN_REWARD_1000', 'ADMIN_REWARD'), - (20, 2000, 'ADMIN_REWARD_2000', 'ADMIN_REWARD'); + (20, 2000, 'ADMIN_REWARD_2000', 'ADMIN_REWARD'), + (21, 10, 'ADMIN_REWARD_COIN', 'ADMIN_REWARD'); /*!40000 ALTER TABLE `item` ENABLE KEYS */; UNLOCK TABLES; @@ -758,6 +759,7 @@ CREATE TABLE `item_history` `user_id` bigint(20) NOT NULL, `purchase_at` datetime(6) NOT NULL, `used_at` datetime(6) DEFAULT NULL, + `amount` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`), CONSTRAINT `item_history_item_id` FOREIGN KEY (`item_id`) REFERENCES `item` (`id`), CONSTRAINT `item_history_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/dto/AdminCoinAssignRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/admin/dto/AdminCoinAssignRequestDto.java new file mode 100644 index 000000000..beb456125 --- /dev/null +++ b/backend/src/main/java/org/ftclub/cabinet/admin/dto/AdminCoinAssignRequestDto.java @@ -0,0 +1,15 @@ +package org.ftclub.cabinet.admin.dto; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.ftclub.cabinet.item.domain.Sku; + +@Getter +@AllArgsConstructor +public class AdminCoinAssignRequestDto { + + private List userIds; + private Sku itemSku; + private Long amount; +} diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/item/controller/AdminItemController.java b/backend/src/main/java/org/ftclub/cabinet/admin/item/controller/AdminItemController.java index f3932160e..46035ef85 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/item/controller/AdminItemController.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/item/controller/AdminItemController.java @@ -1,14 +1,26 @@ package org.ftclub.cabinet.admin.item.controller; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.admin.dto.AdminItemHistoryPaginationDto; import org.ftclub.cabinet.admin.item.service.AdminItemFacadeService; -import org.ftclub.cabinet.admin.statistics.service.AdminStatisticsFacadeService; import org.ftclub.cabinet.auth.domain.AuthGuard; import org.ftclub.cabinet.auth.domain.AuthLevel; import org.ftclub.cabinet.dto.ItemAssignRequestDto; import org.ftclub.cabinet.dto.ItemCreateDto; +import org.ftclub.cabinet.dto.ItemDetailsDto; +import org.ftclub.cabinet.dto.ItemStoreDto; +import org.ftclub.cabinet.dto.ItemStoreResponseDto; +import org.ftclub.cabinet.item.domain.Item; +import org.ftclub.cabinet.item.domain.ItemType; +import org.ftclub.cabinet.item.service.ItemQueryService; import org.ftclub.cabinet.log.Logging; +import org.ftclub.cabinet.mapper.ItemMapper; import org.springframework.data.domain.Pageable; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -24,8 +36,8 @@ public class AdminItemController { private final AdminItemFacadeService adminItemFacadeService; - private final AdminStatisticsFacadeService adminStatisticsFacadeService; - + private final ItemQueryService itemQueryService; + private final ItemMapper itemMapper; @PostMapping("") @AuthGuard(level = AuthLevel.ADMIN_ONLY) @@ -34,11 +46,29 @@ public void createItem(@RequestBody ItemCreateDto itemCreateDto) { itemCreateDto.getSku(), itemCreateDto.getType()); } + @GetMapping("") + @AuthGuard(level = AuthLevel.ADMIN_ONLY) + public ItemStoreResponseDto getAllItems() { + List allItems = itemQueryService.getAllItems(); + Map> itemMap = allItems.stream() + .collect(groupingBy(Item::getType, + mapping(itemMapper::toItemDetailsDto, Collectors.toList()))); + List result = itemMap.entrySet().stream() + .map(entry -> { + ItemStoreDto itemStoreDto = itemMapper.toItemStoreDto(entry.getKey(), + entry.getValue()); + itemStoreDto.sortBySkuASC(); + return itemStoreDto; + }) + .collect(Collectors.toList()); + return new ItemStoreResponseDto(result); + } + @PostMapping("/assign") @AuthGuard(level = AuthLevel.ADMIN_ONLY) public void assignItem(@RequestBody ItemAssignRequestDto itemAssignRequestDto) { adminItemFacadeService.assignItem(itemAssignRequestDto.getUserIds(), - itemAssignRequestDto.getItemSku()); + itemAssignRequestDto.getItemSku(), itemAssignRequestDto.getAmount()); } /** diff --git a/backend/src/main/java/org/ftclub/cabinet/admin/item/service/AdminItemFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/admin/item/service/AdminItemFacadeService.java index d8c456f03..dda0e8fed 100644 --- a/backend/src/main/java/org/ftclub/cabinet/admin/item/service/AdminItemFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/admin/item/service/AdminItemFacadeService.java @@ -15,11 +15,10 @@ import org.ftclub.cabinet.item.service.ItemCommandService; import org.ftclub.cabinet.item.service.ItemHistoryCommandService; import org.ftclub.cabinet.item.service.ItemHistoryQueryService; +import org.ftclub.cabinet.item.service.ItemPolicyService; import org.ftclub.cabinet.item.service.ItemQueryService; -import org.ftclub.cabinet.item.service.ItemRedisService; import org.ftclub.cabinet.mapper.ItemMapper; import org.ftclub.cabinet.user.service.UserCommandService; -import org.ftclub.cabinet.user.service.UserQueryService; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -33,11 +32,10 @@ public class AdminItemFacadeService { private final ItemCommandService itemCommandService; private final ItemHistoryQueryService itemHistoryQueryService; private final ItemHistoryCommandService itemHistoryCommandService; + private final ItemPolicyService itemPolicyService; private final ItemMapper itemMapper; - private final ItemRedisService itemRedisService; private final UserCommandService userCommandService; - private final UserQueryService userQueryService; @Transactional public void createItem(Integer Price, Sku sku, ItemType type) { @@ -45,22 +43,19 @@ public void createItem(Integer Price, Sku sku, ItemType type) { } @Transactional - public void assignItem(List userIds, Sku sku) { + public void assignItem(List userIds, Sku sku, Long amount) { Item item = itemQueryService.getBySku(sku); - Long price = item.getPrice(); LocalDateTime now = null; - if (price > 0) { + Long coinAmount = item.getPrice(); + if (sku.equals(Sku.ADMIN_REWARD_COIN)) { + itemPolicyService.verifyCoinAmount(amount); + coinAmount = amount; + } + if (coinAmount > 0) { now = LocalDateTime.now(); - userIds.forEach(userId -> { - long coinAmount = userQueryService.getUser(userId).getCoin(); - itemRedisService.saveCoinCount(userId, coinAmount + item.getPrice()); - - long totalCoinSupply = itemRedisService.getTotalCoinSupply(); - itemRedisService.saveTotalCoinSupply(totalCoinSupply + item.getPrice()); - userCommandService.updateCoinAmount(userId, price); - }); + userCommandService.addBulkCoin(userIds, coinAmount); } - itemHistoryCommandService.createItemHistories(userIds, item.getId(), now); + itemHistoryCommandService.createItemHistories(userIds, item.getId(), now, coinAmount); } @Transactional(readOnly = true) diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/CoinHistoryDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/CoinHistoryDto.java index 023469920..ef2b1afa3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/CoinHistoryDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/CoinHistoryDto.java @@ -13,7 +13,7 @@ public class CoinHistoryDto { private LocalDateTime date; - private Integer amount; + private Long amount; private String history; private String itemDetails; } diff --git a/backend/src/main/java/org/ftclub/cabinet/dto/ItemAssignRequestDto.java b/backend/src/main/java/org/ftclub/cabinet/dto/ItemAssignRequestDto.java index 20770c617..e8708378f 100644 --- a/backend/src/main/java/org/ftclub/cabinet/dto/ItemAssignRequestDto.java +++ b/backend/src/main/java/org/ftclub/cabinet/dto/ItemAssignRequestDto.java @@ -15,4 +15,5 @@ public class ItemAssignRequestDto { private Sku itemSku; private List userIds; + private Long amount; } diff --git a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java index 39c570da2..f5047d2c3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java +++ b/backend/src/main/java/org/ftclub/cabinet/exception/ExceptionStatus.java @@ -84,7 +84,8 @@ public enum ExceptionStatus { INVALID_JWT_TOKEN(HttpStatus.BAD_REQUEST, "토큰이 μ—†κ±°λ‚˜, μœ νš¨ν•˜μ§€ μ•Šμ€ JWT ν† ν°μž…λ‹ˆλ‹€."), NOT_FOUND_SECTION(HttpStatus.BAD_REQUEST, "사물함 ꡬ역 정보λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."), ITEM_NOT_OWNED(HttpStatus.BAD_REQUEST, "ν•΄λ‹Ή μ•„μ΄ν…œμ„ λ³΄μœ ν•˜κ³  μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€"), - ITEM_USE_DUPLICATED(HttpStatus.FORBIDDEN, "μ•„μ΄ν…œμ΄ 쀑볡 μ‚¬μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€."); + ITEM_USE_DUPLICATED(HttpStatus.FORBIDDEN, "μ•„μ΄ν…œμ΄ 쀑볡 μ‚¬μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€."), + INVALID_AMOUNT(HttpStatus.BAD_REQUEST, "코인 지급양은 λΉ„μ–΄μžˆμ„ 수 μ—†μŠ΅λ‹ˆλ‹€."); final private int statusCode; final private String message; diff --git a/backend/src/main/java/org/ftclub/cabinet/item/domain/ItemHistory.java b/backend/src/main/java/org/ftclub/cabinet/item/domain/ItemHistory.java index 2784a5f72..8f85c32c4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/item/domain/ItemHistory.java +++ b/backend/src/main/java/org/ftclub/cabinet/item/domain/ItemHistory.java @@ -75,11 +75,14 @@ public class ItemHistory { @Column(name = "USER_ID", nullable = false) private Long userId; + @Column(name = "AMOUNT", nullable = true) + private Long amount; - protected ItemHistory(long userId, long itemId, LocalDateTime usedAt) { + protected ItemHistory(Long userId, Long itemId, LocalDateTime assignedAt, Long amount) { this.userId = userId; this.itemId = itemId; - this.usedAt = usedAt; + this.usedAt = assignedAt; + this.amount = amount; } /** @@ -88,8 +91,8 @@ protected ItemHistory(long userId, long itemId, LocalDateTime usedAt) { * @param usedAt μ•„μ΄ν…œ μ‚¬μš©μΌμž * @return μ•„μ΄ν…œ νžˆμŠ€ν† λ¦¬ 객체 {@link ItemHistory} */ - public static ItemHistory of(long userId, long itemId, LocalDateTime usedAt) { - ItemHistory itemHistory = new ItemHistory(userId, itemId, usedAt); + public static ItemHistory of(Long userId, Long itemId, LocalDateTime usedAt, Long amount) { + ItemHistory itemHistory = new ItemHistory(userId, itemId, usedAt, amount); if (!itemHistory.isValid()) { throw ExceptionStatus.INVALID_ARGUMENT.asDomainException(); } diff --git a/backend/src/main/java/org/ftclub/cabinet/item/domain/Sku.java b/backend/src/main/java/org/ftclub/cabinet/item/domain/Sku.java index bda3b9beb..64b383a7a 100644 --- a/backend/src/main/java/org/ftclub/cabinet/item/domain/Sku.java +++ b/backend/src/main/java/org/ftclub/cabinet/item/domain/Sku.java @@ -25,11 +25,12 @@ public enum Sku { COIN_REWARD_1000("동전 쀍기 20일 보상"), COIN_REWARD_2000("동전 쀍기 20일 보상"), - ADMIN_REWARD_100("보상"), - ADMIN_REWARD_200("보상"), - ADMIN_REWARD_500("보상"), - ADMIN_REWARD_1000("보상"), - ADMIN_REWARD_2000("보상"), + ADMIN_REWARD_100("100"), + ADMIN_REWARD_200("200"), + ADMIN_REWARD_500("500"), + ADMIN_REWARD_1000("1000"), + ADMIN_REWARD_2000("2000"), + ADMIN_REWARD_COIN("지정 보상"), ; private final String details; diff --git a/backend/src/main/java/org/ftclub/cabinet/item/service/ItemFacadeService.java b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemFacadeService.java index b6f0e5546..384977313 100644 --- a/backend/src/main/java/org/ftclub/cabinet/item/service/ItemFacadeService.java +++ b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemFacadeService.java @@ -195,7 +195,8 @@ public CoinCollectionRewardResponseDto collectCoinAndIssueReward(Long userId) { // DB에 코인 μ €μž₯ Item coinCollect = itemQueryService.getBySku(Sku.COIN_COLLECT); int reward = (int) (coinCollect.getPrice().longValue()); - itemHistoryCommandService.createCoinItemHistory(userId, coinCollect.getId()); + itemHistoryCommandService.createCoinItemHistory(userId, coinCollect.getId(), + coinCollect.getPrice()); // μΆœμ„ μΌμžμ— λ”°λ₯Έ 랜덀 λ¦¬μ›Œλ“œ 지급 Long coinCollectionCountInMonth = @@ -206,7 +207,8 @@ public CoinCollectionRewardResponseDto collectCoinAndIssueReward(Long userId) { Sku coinSku = itemPolicyService.getRewardSku(randomPercentage); Item coinReward = itemQueryService.getBySku(coinSku); - itemHistoryCommandService.createCoinItemHistory(userId, coinReward.getId()); + itemHistoryCommandService.createCoinItemHistory(userId, coinReward.getId(), + coinReward.getPrice()); reward += coinReward.getPrice(); } @@ -310,7 +312,7 @@ public void purchaseItem(Long userId, Sku sku) { itemPolicyService.verifyIsAffordable(userCoin, price); // μ•„μ΄ν…œ ꡬ맀 처리 - itemHistoryCommandService.createItemHistory(user.getId(), item.getId()); + itemHistoryCommandService.createItemHistory(user.getId(), item.getId(), price); LockUtil.lockRedisCoin(userId, () -> { // 코인 차감 diff --git a/backend/src/main/java/org/ftclub/cabinet/item/service/ItemHistoryCommandService.java b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemHistoryCommandService.java index 2863043c4..f76562608 100644 --- a/backend/src/main/java/org/ftclub/cabinet/item/service/ItemHistoryCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemHistoryCommandService.java @@ -17,20 +17,39 @@ public class ItemHistoryCommandService { private final ItemHistoryRepository itemHistoryRepository; - public void createItemHistory(Long userId, Long itemId) { - ItemHistory itemHistory = ItemHistory.of(userId, itemId, null); + public void createItemHistory(Long userId, Long itemId, Long amount) { + ItemHistory itemHistory = ItemHistory.of(userId, itemId, null, amount); itemHistoryRepository.save(itemHistory); } - public void createItemHistories(List userIds, Long itemId, LocalDateTime usedAt) { + public void createItemHistories(List userIds, Long itemId, LocalDateTime usedAt, + Long amount) { List itemHistories = userIds.stream() - .map(userId -> ItemHistory.of(userId, itemId, usedAt)) + .map(userId -> ItemHistory.of(userId, itemId, usedAt, amount)) .collect(Collectors.toList()); itemHistoryRepository.saveAll(itemHistories); } - public void createCoinItemHistory(Long userId, Long itemId) { - ItemHistory coinCollectItemHistory = ItemHistory.of(userId, itemId, LocalDateTime.now()); + public void createCoinItemHistory(Long userId, Long itemId, Long amount) { + ItemHistory coinCollectItemHistory = ItemHistory.of(userId, itemId, LocalDateTime.now(), + amount); itemHistoryRepository.save(coinCollectItemHistory); } + + /** + * κ΄€λ¦¬μžκ°€ 코인 지급 μ‹œ ItemHistory에 기둝을 λ‚¨κΉλ‹ˆλ‹€ + * + * @param userIds + * @param itemId + * @param usedAt + * @param amount + */ + public void createCoinAssignHistory(List userIds, Long itemId, LocalDateTime usedAt, + Long amount) { + List itemHistories = userIds.stream() + .map(userId -> ItemHistory.of(userId, itemId, usedAt, amount)) + .collect(Collectors.toList()); + + itemHistoryRepository.saveAll(itemHistories); + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/item/service/ItemPolicyService.java b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemPolicyService.java index f27d4bf75..49c7dd4e3 100644 --- a/backend/src/main/java/org/ftclub/cabinet/item/service/ItemPolicyService.java +++ b/backend/src/main/java/org/ftclub/cabinet/item/service/ItemPolicyService.java @@ -64,4 +64,10 @@ public Sku getRewardSku(int randomPercentage) { return Sku.COIN_REWARD_2000; } } + + public void verifyCoinAmount(Long amount) { + if (amount == null) { + throw ExceptionStatus.INVALID_AMOUNT.asServiceException(); + } + } } diff --git a/backend/src/main/java/org/ftclub/cabinet/mapper/ItemMapper.java b/backend/src/main/java/org/ftclub/cabinet/mapper/ItemMapper.java index 71939ee98..2649d97d4 100644 --- a/backend/src/main/java/org/ftclub/cabinet/mapper/ItemMapper.java +++ b/backend/src/main/java/org/ftclub/cabinet/mapper/ItemMapper.java @@ -37,9 +37,9 @@ public interface ItemMapper { ItemMapper INSTANCE = Mappers.getMapper(ItemMapper.class); @Mapping(target = "date", source = "itemHistory.purchaseAt") - @Mapping(target = "amount", source = "item.price") @Mapping(target = "history", source = "item.type.name") @Mapping(target = "itemDetails", source = "item.sku.details") + @Mapping(target = "amount", source = "itemHistory.amount") CoinHistoryDto toCoinHistoryDto(ItemHistory itemHistory, Item item); @Mapping(target = "itemSku", source = "item.sku") diff --git a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java index 0a384bb73..cd495bac0 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/repository/UserRepository.java @@ -140,4 +140,10 @@ public interface UserRepository extends JpaRepository { @Query("SELECT u FROM User u WHERE u.deletedAt IS NULL") List findAllDeletedAtIsNull(); + + @Modifying(clearAutomatically = true) + @Query("UPDATE User u " + + "SET u.coin = u.coin + :amount " + + "WHERE u.id IN :userIds") + void updateBulkUserCoin(@Param("userIds") List userIds, @Param("amount") Long amount); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionManager.java b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionManager.java index cb569e574..380efefc8 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionManager.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/LentExtensionManager.java @@ -46,7 +46,8 @@ public void issueLentExtension() { users.forEach(user -> { Long userId = user.getId(); user.addCoin(coinRewardItem.getPrice()); - itemHistoryCommandService.createCoinItemHistory(userId, coinRewardItem.getId()); + itemHistoryCommandService.createCoinItemHistory(userId, coinRewardItem.getId(), + coinRewardItem.getPrice()); }); } diff --git a/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java b/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java index 994e9f7b8..c94c56710 100644 --- a/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java +++ b/backend/src/main/java/org/ftclub/cabinet/user/service/UserCommandService.java @@ -1,6 +1,7 @@ package org.ftclub.cabinet.user.service; import java.time.LocalDateTime; +import java.util.List; import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.ftclub.cabinet.auth.domain.FtProfile; @@ -115,4 +116,8 @@ public void updateCoinAmount(Long userId, Long reward) { .orElseThrow(ExceptionStatus.NOT_FOUND_USER::asServiceException); user.addCoin(reward); } + + public void addBulkCoin(List userIds, Long amount) { + userRepository.updateBulkUserCoin(userIds, amount); + } } diff --git a/config b/config index 0d3244f5b..36f81ff28 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 0d3244f5b700c2b735c13a73ed4e8868139b144f +Subproject commit 36f81ff2864e6c3a7ef5af1fd752fab31e9b4edc diff --git a/frontend/index.html b/frontend/index.html index 768d40f31..c396ca30e 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -6,14 +6,34 @@ src="/src/Cabinet/components/Card/DisplayStyleCard/displayStyleInitializer.ts" > + + + - - - + + + + + + + + + + =10.10.0" @@ -2278,14 +2331,15 @@ } }, "node_modules/@firebase/firestore-compat": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.18.tgz", - "integrity": "sha512-hkqv4mb1oScKbEtzfcK8Go8c0VpDWmbAvbD6B6XnphLqi27pkXgo9Rp+aSKlD7cBL29VMEekP5bEm9lSVfZpNw==", - "dependencies": { - "@firebase/component": "0.6.4", - "@firebase/firestore": "4.2.0", - "@firebase/firestore-types": "3.0.0", - "@firebase/util": "1.9.3", + "version": "0.3.38", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.38.tgz", + "integrity": "sha512-GoS0bIMMkjpLni6StSwRJarpu2+S5m346Na7gr9YZ/BZ/W3/8iHGNr9PxC+f0rNZXqS4fGRn88pICjrZEgbkqQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/firestore": "4.7.3", + "@firebase/firestore-types": "3.0.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2293,50 +2347,55 @@ } }, "node_modules/@firebase/firestore-compat/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/@firebase/firestore-types": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.0.tgz", - "integrity": "sha512-Meg4cIezHo9zLamw0ymFYBD4SMjLb+ZXIbuN7T7ddXN6MGoICmOTq3/ltdCGoDCS2u+H1XJs2u/cYp75jsX9Qw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.2.tgz", + "integrity": "sha512-wp1A+t5rI2Qc/2q7r2ZpjUXkRVPtGMd6zCLsiWurjsQpqPgFin3AhNibKcIzoF2rnToNa/XYtyWXuifjOOwDgg==", + "license": "Apache-2.0", "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "node_modules/@firebase/firestore/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/@firebase/functions": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.10.0.tgz", - "integrity": "sha512-2U+fMNxTYhtwSpkkR6WbBcuNMOVaI7MaH3cZ6UAeNfj7AgEwHwMIFLPpC13YNZhno219F0lfxzTAA0N62ndWzA==", - "dependencies": { - "@firebase/app-check-interop-types": "0.3.0", - "@firebase/auth-interop-types": "0.2.1", - "@firebase/component": "0.6.4", - "@firebase/messaging-interop-types": "0.2.0", - "@firebase/util": "1.9.3", - "node-fetch": "2.6.7", - "tslib": "^2.1.0" + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.11.8.tgz", + "integrity": "sha512-Lo2rTPDn96naFIlSZKVd1yvRRqqqwiJk7cf9TZhUerwnPKgBzXy+aHE22ry+6EjCaQusUoNai6mU6p+G8QZT1g==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.9", + "@firebase/messaging-interop-types": "0.2.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" }, "peerDependencies": { "@firebase/app": "0.x" } }, "node_modules/@firebase/functions-compat": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.5.tgz", - "integrity": "sha512-uD4jwgwVqdWf6uc3NRKF8cSZ0JwGqSlyhPgackyUPe+GAtnERpS4+Vr66g0b3Gge0ezG4iyHo/EXW/Hjx7QhHw==", - "dependencies": { - "@firebase/component": "0.6.4", - "@firebase/functions": "0.10.0", - "@firebase/functions-types": "0.6.0", - "@firebase/util": "1.9.3", + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.14.tgz", + "integrity": "sha512-dZ0PKOKQFnOlMfcim39XzaXonSuPPAVuzpqA4ONTIdyaJK/OnBaIEVs/+BH4faa1a2tLeR+Jy15PKqDRQoNIJw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/functions": "0.11.8", + "@firebase/functions-types": "0.6.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2344,28 +2403,32 @@ } }, "node_modules/@firebase/functions-compat/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/@firebase/functions-types": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.0.tgz", - "integrity": "sha512-hfEw5VJtgWXIRf92ImLkgENqpL6IWpYaXVYiRkFY1jJ9+6tIhWM7IzzwbevwIIud/jaxKVdRzD7QBWfPmkwCYw==" + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.2.tgz", + "integrity": "sha512-0KiJ9lZ28nS2iJJvimpY4nNccV21rkQyor5Iheu/nq8aKXJqtJdeSlZDspjPSBBiHRzo7/GMUttegnsEITqR+w==", + "license": "Apache-2.0" }, "node_modules/@firebase/functions/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/@firebase/installations": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.4.tgz", - "integrity": "sha512-u5y88rtsp7NYkCHC3ElbFBrPtieUybZluXyzl7+4BsIz4sqb4vSAuwHEUgCgCeaQhvsnxDEU6icly8U9zsJigA==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.9.tgz", + "integrity": "sha512-hlT7AwCiKghOX3XizLxXOsTFiFCQnp/oj86zp1UxwDGmyzsyoxtX+UIZyVyH/oBF5+XtblFG9KZzZQ/h+dpy+Q==", + "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.4", - "@firebase/util": "1.9.3", - "idb": "7.0.1", + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "idb": "7.1.1", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2373,14 +2436,15 @@ } }, "node_modules/@firebase/installations-compat": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.4.tgz", - "integrity": "sha512-LI9dYjp0aT9Njkn9U4JRrDqQ6KXeAmFbRC0E7jI7+hxl5YmRWysq5qgQl22hcWpTk+cm3es66d/apoDU/A9n6Q==", - "dependencies": { - "@firebase/component": "0.6.4", - "@firebase/installations": "0.6.4", - "@firebase/installations-types": "0.5.0", - "@firebase/util": "1.9.3", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.9.tgz", + "integrity": "sha512-2lfdc6kPXR7WaL4FCQSQUhXcPbI7ol3wF+vkgtU25r77OxPf8F/VmswQ7sgIkBBWtymn5ZF20TIKtnOj9rjb6w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/installations-types": "0.5.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2388,51 +2452,52 @@ } }, "node_modules/@firebase/installations-compat/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/@firebase/installations-types": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.0.tgz", - "integrity": "sha512-9DP+RGfzoI2jH7gY4SlzqvZ+hr7gYzPODrbzVD82Y12kScZ6ZpRg/i3j6rleto8vTFC8n6Len4560FnV1w2IRg==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.2.tgz", + "integrity": "sha512-que84TqGRZJpJKHBlF2pkvc1YcXrtEDOVGiDjovP/a3s6W4nlbohGXEsBJo0JCeeg/UG9A+DEZVDUV9GpklUzA==", + "license": "Apache-2.0", "peerDependencies": { "@firebase/app-types": "0.x" } }, - "node_modules/@firebase/installations/node_modules/idb": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", - "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" - }, "node_modules/@firebase/installations/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/@firebase/logger": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", - "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.2.tgz", + "integrity": "sha512-Q1VuA5M1Gjqrwom6I6NUU4lQXdo9IAQieXlujeHZWvRt1b7qQ0KwBaNAjgxG27jgF9/mUwsNmO8ptBCGVYhB0A==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@firebase/logger/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/@firebase/messaging": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.4.tgz", - "integrity": "sha512-6JLZct6zUaex4g7HI3QbzeUrg9xcnmDAPTWpkoMpd/GoSVWH98zDoWXMGrcvHeCAIsLpFMe4MPoZkJbrPhaASw==", - "dependencies": { - "@firebase/component": "0.6.4", - "@firebase/installations": "0.6.4", - "@firebase/messaging-interop-types": "0.2.0", - "@firebase/util": "1.9.3", - "idb": "7.0.1", + "version": "0.12.12", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.12.tgz", + "integrity": "sha512-6q0pbzYBJhZEtUoQx7hnPhZvAbuMNuBXKQXOx2YlWhSrlv9N1m0ZzlNpBbu/ItTzrwNKTibdYzUyaaxdWLg+4w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/messaging-interop-types": "0.2.2", + "@firebase/util": "1.10.0", + "idb": "7.1.1", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2440,13 +2505,14 @@ } }, "node_modules/@firebase/messaging-compat": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.4.tgz", - "integrity": "sha512-lyFjeUhIsPRYDPNIkYX1LcZMpoVbBWXX4rPl7c/rqc7G+EUea7IEtSt4MxTvh6fDfPuzLn7+FZADfscC+tNMfg==", - "dependencies": { - "@firebase/component": "0.6.4", - "@firebase/messaging": "0.12.4", - "@firebase/util": "1.9.3", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.12.tgz", + "integrity": "sha512-pKsiUVZrbmRgdImYqhBNZlkKJbqjlPkVdQRZGRbkTyX4OSGKR0F/oJeCt1a8jEg5UnBp4fdVwSWSp4DuCovvEQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/messaging": "0.12.12", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2454,34 +2520,33 @@ } }, "node_modules/@firebase/messaging-compat/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/@firebase/messaging-interop-types": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.0.tgz", - "integrity": "sha512-ujA8dcRuVeBixGR9CtegfpU4YmZf3Lt7QYkcj693FFannwNuZgfAYaTmbJ40dtjB81SAu6tbFPL9YLNT15KmOQ==" - }, - "node_modules/@firebase/messaging/node_modules/idb": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", - "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.2.tgz", + "integrity": "sha512-l68HXbuD2PPzDUOFb3aG+nZj5KA3INcPwlocwLZOzPp9rFM9yeuI9YLl6DQfguTX5eAGxO0doTR+rDLDvQb5tA==", + "license": "Apache-2.0" }, "node_modules/@firebase/messaging/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/@firebase/performance": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.4.tgz", - "integrity": "sha512-HfTn/bd8mfy/61vEqaBelNiNnvAbUtME2S25A67Nb34zVuCSCRIX4SseXY6zBnOFj3oLisaEqhVcJmVPAej67g==", - "dependencies": { - "@firebase/component": "0.6.4", - "@firebase/installations": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.9.tgz", + "integrity": "sha512-PnVaak5sqfz5ivhua+HserxTJHtCar/7zM0flCX6NkzBNzJzyzlH4Hs94h2Il0LQB99roBqoE5QT1JqWqcLJHQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2489,15 +2554,16 @@ } }, "node_modules/@firebase/performance-compat": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.4.tgz", - "integrity": "sha512-nnHUb8uP9G8islzcld/k6Bg5RhX62VpbAb/Anj7IXs/hp32Eb2LqFPZK4sy3pKkBUO5wcrlRWQa6wKOxqlUqsg==", - "dependencies": { - "@firebase/component": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/performance": "0.6.4", - "@firebase/performance-types": "0.2.0", - "@firebase/util": "1.9.3", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.9.tgz", + "integrity": "sha512-dNl95IUnpsu3fAfYBZDCVhXNkASE0uo4HYaEPd2/PKscfTvsgqFAOxfAXzBEDOnynDWiaGUnb5M1O00JQ+3FXA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/performance": "0.6.9", + "@firebase/performance-types": "0.2.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2505,29 +2571,33 @@ } }, "node_modules/@firebase/performance-compat/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/@firebase/performance-types": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.0.tgz", - "integrity": "sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA==" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.2.tgz", + "integrity": "sha512-gVq0/lAClVH5STrIdKnHnCo2UcPLjJlDUoEB/tB4KM+hAeHUxWKnpT0nemUPvxZ5nbdY/pybeyMe8Cs29gEcHA==", + "license": "Apache-2.0" }, "node_modules/@firebase/performance/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/@firebase/remote-config": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.4.tgz", - "integrity": "sha512-x1ioTHGX8ZwDSTOVp8PBLv2/wfwKzb4pxi0gFezS5GCJwbLlloUH4YYZHHS83IPxnua8b6l0IXUaWd0RgbWwzQ==", - "dependencies": { - "@firebase/component": "0.6.4", - "@firebase/installations": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.9.tgz", + "integrity": "sha512-EO1NLCWSPMHdDSRGwZ73kxEEcTopAxX1naqLJFNApp4hO8WfKfmEpmjxmP5TrrnypjIf2tUkYaKsfbEA7+AMmA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2535,15 +2605,16 @@ } }, "node_modules/@firebase/remote-config-compat": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.4.tgz", - "integrity": "sha512-FKiki53jZirrDFkBHglB3C07j5wBpitAaj8kLME6g8Mx+aq7u9P7qfmuSRytiOItADhWUj7O1JIv7n9q87SuwA==", - "dependencies": { - "@firebase/component": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/remote-config": "0.4.4", - "@firebase/remote-config-types": "0.3.0", - "@firebase/util": "1.9.3", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.9.tgz", + "integrity": "sha512-AxzGpWfWFYejH2twxfdOJt5Cfh/ATHONegTd/a0p5flEzsD5JsxXgfkFToop+mypEL3gNwawxrxlZddmDoNxyA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/remote-config": "0.4.9", + "@firebase/remote-config-types": "0.3.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2551,43 +2622,48 @@ } }, "node_modules/@firebase/remote-config-compat/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/@firebase/remote-config-types": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.0.tgz", - "integrity": "sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA==" + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.2.tgz", + "integrity": "sha512-0BC4+Ud7y2aPTyhXJTMTFfrGGLqdYXrUB9sJVAB8NiqJswDTc4/2qrE/yfUbnQJhbSi6ZaTTBKyG3n1nplssaA==", + "license": "Apache-2.0" }, "node_modules/@firebase/remote-config/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/@firebase/storage": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.11.2.tgz", - "integrity": "sha512-CtvoFaBI4hGXlXbaCHf8humajkbXhs39Nbh6MbNxtwJiCqxPy9iH3D3CCfXAvP0QvAAwmJUTK3+z9a++Kc4nkA==", + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.2.tgz", + "integrity": "sha512-fxuJnHshbhVwuJ4FuISLu+/76Aby2sh+44ztjF2ppoe0TELIDxPW6/r1KGlWYt//AD0IodDYYA8ZTN89q8YqUw==", + "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.4", - "@firebase/util": "1.9.3", - "node-fetch": "2.6.7", - "tslib": "^2.1.0" + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" }, "peerDependencies": { "@firebase/app": "0.x" } }, "node_modules/@firebase/storage-compat": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.2.tgz", - "integrity": "sha512-wvsXlLa9DVOMQJckbDNhXKKxRNNewyUhhbXev3t8kSgoCotd1v3MmqhKKz93ePhDnhHnDs7bYHy+Qa8dRY6BXw==", - "dependencies": { - "@firebase/component": "0.6.4", - "@firebase/storage": "0.11.2", - "@firebase/storage-types": "0.8.0", - "@firebase/util": "1.9.3", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.12.tgz", + "integrity": "sha512-hA4VWKyGU5bWOll+uwzzhEMMYGu9PlKQc1w4DWxB3aIErWYzonrZjF0icqNQZbwKNIdh8SHjZlFeB2w6OSsjfg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/storage": "0.13.2", + "@firebase/storage-types": "0.8.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2595,46 +2671,79 @@ } }, "node_modules/@firebase/storage-compat/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/@firebase/storage-types": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.0.tgz", - "integrity": "sha512-isRHcGrTs9kITJC0AVehHfpraWFui39MPaU7Eo8QfWlqW7YPymBmRgjDrlOgFdURh6Cdeg07zmkLP5tzTKRSpg==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.2.tgz", + "integrity": "sha512-0vWu99rdey0g53lA7IShoA2Lol1jfnPovzLDUBuon65K7uKG9G+L5uO05brD9pMw+l4HRFw23ah3GwTGpEav6g==", + "license": "Apache-2.0", "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "node_modules/@firebase/storage/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/@firebase/util": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", - "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.0.tgz", + "integrity": "sha512-xKtx4A668icQqoANRxyDLBLz51TAbDP9KRfpbKGxiCAW346d0BeJe5vN6/hKxxmWwnZ0mautyv39JxviwwQMOQ==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@firebase/util/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" + }, + "node_modules/@firebase/vertexai-preview": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@firebase/vertexai-preview/-/vertexai-preview-0.0.4.tgz", + "integrity": "sha512-EBSqyu9eg8frQlVU9/HjKtHN7odqbh9MtAcVz3WwHj4gLCLOoN9F/o+oxlq3CxvFrd3CNTZwu6d2mZtVlEInng==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/vertexai-preview/node_modules/tslib": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/@firebase/webchannel-wrapper": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.10.3.tgz", - "integrity": "sha512-+ZplYUN3HOpgCfgInqgdDAbkGGVzES1cs32JJpeqoh87SkRobGXElJx+1GZSaDqzFL+bYiX18qEcBK76mYs8uA==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.1.tgz", + "integrity": "sha512-jmEnr/pk0yVkA7mIlHNnxCi+wWzOFUg0WyIotgkKAb2u1J7fAeDBcVNSTjTihbAYNusCLQdW5s9IJ5qwnEufcQ==", + "license": "Apache-2.0" }, "node_modules/@grpc/grpc-js": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.3.tgz", - "integrity": "sha512-b8iWtdrYIeT5fdZdS4Br/6h/kuk0PW5EVBUGk1amSbrpL8DlktJD43CdcCWwRdd6+jgwHhADSbL9CsNnm6EUPA==", + "version": "1.9.15", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", + "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==", + "license": "Apache-2.0", "dependencies": { "@grpc/proto-loader": "^0.7.8", "@types/node": ">=12.12.47" @@ -2644,13 +2753,14 @@ } }, "node_modules/@grpc/proto-loader": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.10.tgz", - "integrity": "sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ==", + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "license": "Apache-2.0", "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", - "protobufjs": "^7.2.4", + "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { @@ -3093,27 +3203,32 @@ "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" @@ -3122,27 +3237,32 @@ "node_modules/@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" }, "node_modules/@react-spring/animated": { "version": "9.4.5", @@ -6598,6 +6718,7 @@ "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", "dependencies": { "websocket-driver": ">=0.5.1" }, @@ -6646,36 +6767,39 @@ } }, "node_modules/firebase": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.4.0.tgz", - "integrity": "sha512-3Z8WsNwA7kbcKGZ+nrTZ/ES518pk0K440ZJYD8nUNKN5hV6ll+unhUw30t1msedN6yIFjhsC/9OwT4Z0ohwO2w==", - "dependencies": { - "@firebase/analytics": "0.10.0", - "@firebase/analytics-compat": "0.2.6", - "@firebase/app": "0.9.19", - "@firebase/app-check": "0.8.0", - "@firebase/app-check-compat": "0.3.7", - "@firebase/app-compat": "0.2.19", - "@firebase/app-types": "0.9.0", - "@firebase/auth": "1.3.0", - "@firebase/auth-compat": "0.4.6", - "@firebase/database": "1.0.1", - "@firebase/database-compat": "1.0.1", - "@firebase/firestore": "4.2.0", - "@firebase/firestore-compat": "0.3.18", - "@firebase/functions": "0.10.0", - "@firebase/functions-compat": "0.3.5", - "@firebase/installations": "0.6.4", - "@firebase/installations-compat": "0.2.4", - "@firebase/messaging": "0.12.4", - "@firebase/messaging-compat": "0.2.4", - "@firebase/performance": "0.6.4", - "@firebase/performance-compat": "0.2.4", - "@firebase/remote-config": "0.4.4", - "@firebase/remote-config-compat": "0.2.4", - "@firebase/storage": "0.11.2", - "@firebase/storage-compat": "0.3.2", - "@firebase/util": "1.9.3" + "version": "10.14.1", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.14.1.tgz", + "integrity": "sha512-0KZxU+Ela9rUCULqFsUUOYYkjh7OM1EWdIfG6///MtXd0t2/uUIf0iNV5i0KariMhRQ5jve/OY985nrAXFaZeQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/analytics": "0.10.8", + "@firebase/analytics-compat": "0.2.14", + "@firebase/app": "0.10.13", + "@firebase/app-check": "0.8.8", + "@firebase/app-check-compat": "0.3.15", + "@firebase/app-compat": "0.2.43", + "@firebase/app-types": "0.9.2", + "@firebase/auth": "1.7.9", + "@firebase/auth-compat": "0.5.14", + "@firebase/data-connect": "0.1.0", + "@firebase/database": "1.0.8", + "@firebase/database-compat": "1.0.8", + "@firebase/firestore": "4.7.3", + "@firebase/firestore-compat": "0.3.38", + "@firebase/functions": "0.11.8", + "@firebase/functions-compat": "0.3.14", + "@firebase/installations": "0.6.9", + "@firebase/installations-compat": "0.2.9", + "@firebase/messaging": "0.12.12", + "@firebase/messaging-compat": "0.2.12", + "@firebase/performance": "0.6.9", + "@firebase/performance-compat": "0.2.9", + "@firebase/remote-config": "0.4.9", + "@firebase/remote-config-compat": "0.2.9", + "@firebase/storage": "0.13.2", + "@firebase/storage-compat": "0.3.12", + "@firebase/util": "1.10.0", + "@firebase/vertexai-preview": "0.0.4" } }, "node_modules/flat-cache": { @@ -7031,7 +7155,8 @@ "node_modules/http-parser-js": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "license": "MIT" }, "node_modules/http-proxy-agent": { "version": "5.0.0", @@ -7075,7 +7200,8 @@ "node_modules/idb": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "license": "ISC" }, "node_modules/ignore": { "version": "5.2.1", @@ -8153,7 +8279,8 @@ "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" }, "node_modules/lodash.debounce": { "version": "4.0.8", @@ -8170,7 +8297,8 @@ "node_modules/long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "license": "Apache-2.0" }, "node_modules/loose-envify": { "version": "1.4.0", @@ -8379,44 +8507,6 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", @@ -8873,10 +8963,11 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/protobufjs": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", - "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", "hasInstallScript": true, + "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -9350,7 +9441,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/safe-regex-test": { "version": "1.0.0", @@ -10002,6 +10094,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "6.19.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.7.tgz", + "integrity": "sha512-HR3W/bMGPSr90i8AAp2C4DM3wChFdJPLrWYpIS++LxS8K+W535qftjt+4MyjNYHeWabMj1nvtmLIi7l++iq91A==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -10286,6 +10387,7 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", @@ -10299,6 +10401,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", "engines": { "node": ">=0.8.0" } diff --git a/frontend/package.json b/frontend/package.json index 6940a7a7b..cfc592445 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,7 +21,7 @@ "@sentry/react": "^8.18.0", "@types/react-color": "^3.0.7", "axios": "^1.7.2", - "firebase": "^10.4.0", + "firebase": "^10.14.1", "react": "^18.2.0", "react-color": "^2.19.3", "react-cookie": "^4.1.1", diff --git a/frontend/public/icons/icon-192x192.webp b/frontend/public/icons/icon-192x192.webp new file mode 100644 index 000000000..ce1d91f22 Binary files /dev/null and b/frontend/public/icons/icon-192x192.webp differ diff --git a/frontend/public/icons/icon-256x256.webp b/frontend/public/icons/icon-256x256.webp new file mode 100644 index 000000000..b58103234 Binary files /dev/null and b/frontend/public/icons/icon-256x256.webp differ diff --git a/frontend/public/icons/icon-384x384.webp b/frontend/public/icons/icon-384x384.webp new file mode 100644 index 000000000..38e007182 Binary files /dev/null and b/frontend/public/icons/icon-384x384.webp differ diff --git a/frontend/public/icons/icon-512x512.webp b/frontend/public/icons/icon-512x512.webp new file mode 100644 index 000000000..08af58b6a Binary files /dev/null and b/frontend/public/icons/icon-512x512.webp differ diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt new file mode 100644 index 000000000..ab3947092 --- /dev/null +++ b/frontend/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Allow : / diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4dc792545..5b9566cb7 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2,32 +2,34 @@ import PageTracker from "@/api/analytics/PageTracker"; import * as Sentry from "@sentry/react"; import React, { Suspense, lazy } from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; -import AvailablePage from "@/Cabinet/pages/AvailablePage"; -import ClubPage from "@/Cabinet/pages/ClubPage"; -import CoinLogPage from "@/Cabinet/pages/CoinLogPage"; import HomePage from "@/Cabinet/pages/HomePage"; -import InventoryPage from "@/Cabinet/pages/InventoryPage"; -import ItemUsageLogPage from "@/Cabinet/pages/ItemUsageLogPage"; import Layout from "@/Cabinet/pages/Layout"; -import LogPage from "@/Cabinet/pages/LogPage"; import LoginPage from "@/Cabinet/pages/LoginPage"; -import MainPage from "@/Cabinet/pages/MainPage"; import PostLogin from "@/Cabinet/pages/PostLogin"; -import ProfilePage from "@/Cabinet/pages/ProfilePage"; -import StoreMainPage from "@/Cabinet/pages/StoreMainPage"; -import AdminMainPage from "@/Cabinet/pages/admin/AdminMainPage"; -import AdminSlackNotiPage from "@/Cabinet/pages/admin/AdminSlackNotiPage"; -import AdminStorePage from "@/Cabinet/pages/admin/AdminStorePage"; import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; -import DetailPage from "@/Presentation/pages/DetailPage"; -import PresentationHomePage from "@/Presentation/pages/HomePage"; -import PresentationLayout from "@/Presentation/pages/Layout"; -import PresentationLogPage from "@/Presentation/pages/LogPage"; -import RegisterPage from "@/Presentation/pages/RegisterPage"; -import AdminPresentationLayout from "@/Presentation/pages/admin/AdminLayout"; -const NotFoundPage = lazy(() => import("@/Cabinet/pages/NotFoundPage")); const LoginFailurePage = lazy(() => import("@/Cabinet/pages/LoginFailurePage")); +const NotFoundPage = lazy(() => import("@/Cabinet/pages/NotFoundPage")); +const AvailablePage = lazy(() => import("@/Cabinet/pages/AvailablePage")); +const ClubPage = lazy(() => import("@/Cabinet/pages/ClubPage")); +const CoinLogPage = lazy(() => import("@/Cabinet/pages/CoinLogPage")); +const InventoryPage = lazy(() => import("@/Cabinet/pages/InventoryPage")); +const ItemUsageLogPage = lazy(() => import("@/Cabinet/pages/ItemUsageLogPage")); +const LogPage = lazy(() => import("@/Cabinet/pages/LogPage")); +const MainPage = lazy(() => import("@/Cabinet/pages/MainPage")); +const ProfilePage = lazy(() => import("@/Cabinet/pages/ProfilePage")); +const StoreMainPage = lazy(() => import("@/Cabinet/pages/StoreMainPage")); + +// NOTE : μˆ˜μš”μ§€μ‹νšŒ +const PresentationHomePage = lazy( + () => import("@/Presentation/pages/HomePage") +); +const PresentationLayout = lazy(() => import("@/Presentation/pages/Layout")); +const DetailPage = lazy(() => import("@/Presentation/pages/DetailPage")); +const PresentationLogPage = lazy(() => import("@/Presentation/pages/LogPage")); +const RegisterPage = lazy(() => import("@/Presentation/pages/RegisterPage")); + +// NOTE : admin const AdminLayout = lazy(() => import("@/Cabinet/pages/admin/AdminLayout")); const AdminLoginPage = lazy( () => import("@/Cabinet/pages/admin/AdminLoginPage") @@ -38,6 +40,16 @@ const AdminLoginFailurePage = lazy( () => import("@/Cabinet/pages/admin/AdminLoginFailurePage") ); const AdminHomePage = lazy(() => import("@/Cabinet/pages/admin/AdminHomePage")); +const AdminMainPage = lazy(() => import("@/Cabinet/pages/admin/AdminMainPage")); +const AdminSlackAlarmPage = lazy( + () => import("@/Cabinet/pages/admin/AdminSlackAlarmPage") +); +const AdminStorePage = lazy( + () => import("@/Cabinet/pages/admin/AdminStorePage") +); +const AdminPresentationLayout = lazy( + () => import("@/Presentation/pages/admin/AdminLayout") +); function App(): React.ReactElement { const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes); @@ -74,7 +86,7 @@ function App(): React.ReactElement { } /> } /> } /> - } /> + } /> } /> } /> diff --git a/frontend/src/Cabinet/api/axios/axios.custom.ts b/frontend/src/Cabinet/api/axios/axios.custom.ts index 514135b75..99f84ca47 100644 --- a/frontend/src/Cabinet/api/axios/axios.custom.ts +++ b/frontend/src/Cabinet/api/axios/axios.custom.ts @@ -28,10 +28,15 @@ export const axiosMyInfo = async (): Promise => { } }; -const axiosUpdateAlarmURL = "/v4/users/me/alarms"; -export const axiosUpdateAlarm = async (alarm: AlarmInfo): Promise => { +const axiosUpdateAlarmReceptionPathURL = "/v4/users/me/alarms"; +export const axiosUpdateAlarmReceptionPath = async ( + alarm: AlarmInfo +): Promise => { try { - const response = await instance.put(axiosUpdateAlarmURL, alarm); + const response = await instance.put( + axiosUpdateAlarmReceptionPathURL, + alarm + ); return response; } catch (error) { throw error; @@ -371,6 +376,23 @@ export const axiosItems = async (): Promise => { const response = await instance.get(axiosItemsURL); return response; } catch (error) { + console.log(error); + logAxiosError( + error, + ErrorType.STORE, + "상점 μ•„μ΄ν…œ λͺ©λ‘ λΆˆλŸ¬μ˜€λŠ”μ€‘ 였λ₯˜ λ°œμƒ" + ); + throw error; + } +}; + +const axiosAdminItemsURL = "/v5/admin/items"; +export const axiosAdminItems = async (): Promise => { + try { + const response = await instance.get(axiosAdminItemsURL); + return response; + } catch (error) { + console.log(error); logAxiosError( error, ErrorType.STORE, @@ -743,9 +765,9 @@ export const axiosLentClubCabinet = async ( try { const response = await instance.post( axiosLentClubCabinetURL + - clubId.toString() + - "/cabinets/" + - cabinetId.toString() + clubId.toString() + + "/cabinets/" + + cabinetId.toString() ); return response; } catch (error) { @@ -874,13 +896,13 @@ export const axiosGetAvailableCabinets = async (): Promise => { } }; -const axiosSendSlackNotificationToUserURL = "/slack/send"; -export const axiosSendSlackNotificationToUser = async ( +const axiosSendSlackAlarmToUserURL = "/slack/send"; +export const axiosSendSlackAlarmToUser = async ( receiverName: string, message: string ): Promise => { try { - const response = await instance.post(axiosSendSlackNotificationToUserURL, { + const response = await instance.post(axiosSendSlackAlarmToUserURL, { receiverName: receiverName, message: message, }); @@ -890,13 +912,13 @@ export const axiosSendSlackNotificationToUser = async ( } }; -export const axiosSendSlackNotificationToChannel = async ( +export const axiosSendSlackAlarmToChannel = async ( receiverName: string, message: string, channel: string | undefined ): Promise => { try { - await instance.post(axiosSendSlackNotificationToUserURL + `/${channel}`, { + await instance.post(axiosSendSlackAlarmToUserURL + `/${channel}`, { receiverName: receiverName, message: message, }); @@ -905,6 +927,7 @@ export const axiosSendSlackNotificationToChannel = async ( } }; +// TODO: ν™•μΈν•˜κ³  ν•„μš”μ—†μœΌλ©΄ μ§€μš°κΈ° const axiosItemAssignURL = "v5/admin/items/assign"; export const axiosItemAssign = async ( itemSku: string, @@ -922,6 +945,25 @@ export const axiosItemAssign = async ( } }; +const axiosCoinAssignURL = "v5/admin/items/assign"; +export const axiosCoinAssign = async ( + itemSku: string, + userIds: number[], + amount: number +): Promise => { + try { + const response = await instance.post(axiosCoinAssignURL, { + itemSku, + userIds, + amount, + }); + return response; + } catch (error) { + logAxiosError(error, ErrorType.STORE, "μ•„μ΄ν…œ 지급 쀑 였λ₯˜ λ°œμƒ", true); + throw error; + } +}; + const axiosGetUserItemsURL = "/v5/admin/items/users/"; export const axiosGetUserItems = async ( userId: number, diff --git a/frontend/src/Cabinet/api/axios/axios.log.ts b/frontend/src/Cabinet/api/axios/axios.log.ts index 19d81ed13..9db940f29 100644 --- a/frontend/src/Cabinet/api/axios/axios.log.ts +++ b/frontend/src/Cabinet/api/axios/axios.log.ts @@ -1,4 +1,5 @@ import { captureException } from "@sentry/react"; +import { HttpStatusCode } from "axios"; import ErrorType from "@/Cabinet/types/enum/error.type.enum"; import { getCookie } from "@/Cabinet/api/react_cookie/cookies"; @@ -12,6 +13,7 @@ export const logAxiosError = ( errorMsg: string, isAdmin = false ) => { + if (error.response?.status === HttpStatusCode.BadRequest) return; error.message = (isAdmin ? "[Admin] " : "") + errorMsg; captureException(error, { tags: { diff --git a/frontend/src/Cabinet/assets/images/slack-notification.svg b/frontend/src/Cabinet/assets/images/slack-alarm.svg similarity index 100% rename from frontend/src/Cabinet/assets/images/slack-notification.svg rename to frontend/src/Cabinet/assets/images/slack-alarm.svg diff --git a/frontend/src/Cabinet/assets/images/storeCoin.svg b/frontend/src/Cabinet/assets/images/storeCoin.svg index 2fe40696a..ccf55c26a 100644 --- a/frontend/src/Cabinet/assets/images/storeCoin.svg +++ b/frontend/src/Cabinet/assets/images/storeCoin.svg @@ -1,9 +1,8 @@ - - - - - - - - + + + + + + + diff --git a/frontend/src/Cabinet/assets/images/webp/desktopLogo.webp b/frontend/src/Cabinet/assets/images/webp/desktopLogo.webp new file mode 100644 index 000000000..4f0ae1c03 Binary files /dev/null and b/frontend/src/Cabinet/assets/images/webp/desktopLogo.webp differ diff --git a/frontend/src/Cabinet/assets/images/webp/happyCcabi.webp b/frontend/src/Cabinet/assets/images/webp/happyCcabi.webp new file mode 100644 index 000000000..6bf95da2b Binary files /dev/null and b/frontend/src/Cabinet/assets/images/webp/happyCcabi.webp differ diff --git a/frontend/src/Cabinet/assets/images/webp/happyCcabiWhite.webp b/frontend/src/Cabinet/assets/images/webp/happyCcabiWhite.webp new file mode 100644 index 000000000..32c8397f8 Binary files /dev/null and b/frontend/src/Cabinet/assets/images/webp/happyCcabiWhite.webp differ diff --git a/frontend/src/Cabinet/assets/images/webp/logo.webp b/frontend/src/Cabinet/assets/images/webp/logo.webp new file mode 100644 index 000000000..0cacefac0 Binary files /dev/null and b/frontend/src/Cabinet/assets/images/webp/logo.webp differ diff --git a/frontend/src/Cabinet/assets/images/webp/sadCcabiWhite.webp b/frontend/src/Cabinet/assets/images/webp/sadCcabiWhite.webp new file mode 100644 index 000000000..b8eea0769 Binary files /dev/null and b/frontend/src/Cabinet/assets/images/webp/sadCcabiWhite.webp differ diff --git a/frontend/src/Cabinet/assets/images/webp/storeCoin.webp b/frontend/src/Cabinet/assets/images/webp/storeCoin.webp new file mode 100644 index 000000000..f07f01bdd Binary files /dev/null and b/frontend/src/Cabinet/assets/images/webp/storeCoin.webp differ diff --git a/frontend/src/Cabinet/components/Announce/AnnounceTemplate.tsx b/frontend/src/Cabinet/components/Announce/AnnounceTemplate.tsx index 352bebd98..8ccb3eb95 100644 --- a/frontend/src/Cabinet/components/Announce/AnnounceTemplate.tsx +++ b/frontend/src/Cabinet/components/Announce/AnnounceTemplate.tsx @@ -36,9 +36,27 @@ const AnnounceTemplate = (props: Itext) => { {title} {type === "ERROR" ? ( - + + + sad cabi + ) : ( - + + + happy cabi + )} {subTitle} diff --git a/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx b/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx index 9159f504c..6830f6e62 100644 --- a/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx +++ b/frontend/src/Cabinet/components/CabinetInfoArea/CabinetInfoArea.tsx @@ -51,8 +51,7 @@ const CabinetInfoArea: React.FC<{ closeModal, isSwappable, }) => { - const isExtensionVisible = isMine && selectedCabinetInfo; - // selectedCabinetInfo.status !== "IN_SESSION"; + const isExtensionVisible = isMine && selectedCabinetInfo && selectedCabinetInfo.status !== "IN_SESSION"; const isHoverBoxVisible = selectedCabinetInfo && selectedCabinetInfo.lentsLength <= 1 && diff --git a/frontend/src/Cabinet/components/CabinetList/CabinetList.container.tsx b/frontend/src/Cabinet/components/CabinetList/CabinetList.container.tsx index 1e9dd4fc2..89ebca973 100644 --- a/frontend/src/Cabinet/components/CabinetList/CabinetList.container.tsx +++ b/frontend/src/Cabinet/components/CabinetList/CabinetList.container.tsx @@ -9,6 +9,7 @@ import { currentSectionCabinetState, currentSectionColNumState, } from "@/Cabinet/recoil/selectors"; +import { DISABLED_FLOOR } from "@/Cabinet/pages/AvailablePage"; import CabinetList from "@/Cabinet/components/CabinetList/CabinetList"; import EmptySection from "@/Cabinet/components/CabinetList/EmptySection/EmptySection"; import RealViewNotification from "@/Cabinet/components/CabinetList/RealViewNotification/RealViewNotification"; @@ -19,10 +20,11 @@ import useMultiSelect from "@/Cabinet/hooks/useMultiSelect"; interface ICabinetListContainer { isAdmin: boolean; + currentFloor: number; } const CabinetListContainer = ({ - isAdmin, + isAdmin, currentFloor }: ICabinetListContainer): JSX.Element => { const colNum = useRecoilValue(currentSectionColNumState); const currentSectionCabinets = useRecoilValue( @@ -50,17 +52,23 @@ const CabinetListContainer = ({ /> )} - {currentFloorSectionNames.includes(currentSectionName) && ( + {currentFloorSectionNames.includes(currentSectionName) && !(DISABLED_FLOOR.includes(currentFloor.toString())) && ( )} - - {(currentSectionName === SectionType.elevator || - currentSectionName === SectionType.stairs) && ( - + {(!isAdmin && DISABLED_FLOOR.includes(currentFloor.toString())) ? ( + + ) : ( + <> + + {(currentSectionName === SectionType.elevator || + currentSectionName === SectionType.stairs) && ( + + )} + )} ); diff --git a/frontend/src/Cabinet/components/CabinetList/EmptySection/EmptySection.tsx b/frontend/src/Cabinet/components/CabinetList/EmptySection/EmptySection.tsx index 5f570a72a..543327d13 100644 --- a/frontend/src/Cabinet/components/CabinetList/EmptySection/EmptySection.tsx +++ b/frontend/src/Cabinet/components/CabinetList/EmptySection/EmptySection.tsx @@ -1,17 +1,31 @@ import styled from "styled-components"; +import { ReactComponent as CabiImage } from "@/Cabinet/assets/images/happyCcabi.svg"; const EmptySection = ({ message }: { message: string }): JSX.Element => { return ( - + + + {message} ); }; +const CabinetTypeIconStyled = styled.div` + width: 200px; + height: 200px; + + & g { + fill: var(--normal-text-color); + } + + & > svg { + width: 200px; + height: 200px; + } +`; + const EmptySectionStyled = styled.div` display: flex; justify-content: center; diff --git a/frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.container.tsx b/frontend/src/Cabinet/components/Card/AlarmCard/AlarmCard.container.tsx similarity index 74% rename from frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.container.tsx rename to frontend/src/Cabinet/components/Card/AlarmCard/AlarmCard.container.tsx index 94b2048cd..4f3ce8a65 100644 --- a/frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.container.tsx +++ b/frontend/src/Cabinet/components/Card/AlarmCard/AlarmCard.container.tsx @@ -3,7 +3,7 @@ import { requestFcmAndGetDeviceToken, } from "@/Cabinet/firebase/firebase-messaging-sw"; import { useEffect, useMemo, useState } from "react"; -import NotificationCard from "@/Cabinet/components/Card/NotificationCard/NotificationCard"; +import AlarmCard from "@/Cabinet/components/Card/AlarmCard/AlarmCard"; import ModalPortal from "@/Cabinet/components/Modals/ModalPortal"; import { FailResponseModal, @@ -11,61 +11,75 @@ import { } from "@/Cabinet/components/Modals/ResponseModal/ResponseModal"; import { AlarmInfo } from "@/Cabinet/types/dto/alarm.dto"; import { - axiosUpdateAlarm, + axiosUpdateAlarmReceptionPath, axiosUpdateDeviceToken, } from "@/Cabinet/api/axios/axios.custom"; +import useDebounce from "@/Cabinet/hooks/useDebounce"; -const NotificationCardContainer = ({ alarm }: { alarm: AlarmInfo | null }) => { +const AlarmCardContainer = ({ alarm }: { alarm: AlarmInfo | null }) => { const [showResponseModal, setShowResponseModal] = useState(false); const [hasErrorOnResponse, setHasErrorOnResponse] = useState(false); const [modalTitle, setModalTitle] = useState(""); + const [modalContents, setModalContents] = useState(null); const [alarms, setAlarms] = useState({ current: alarm, original: alarm }); + const [isLoading, setIsLoading] = useState(false); const isModified = useMemo( () => JSON.stringify(alarms.current) !== JSON.stringify(alarms.original), [alarms] ); + const { debounce } = useDebounce(); useEffect(() => { setAlarms({ current: alarm, original: alarm }); }, [alarm]); - const handleToggleChange = (type: keyof AlarmInfo, checked: boolean) => { - setAlarms((prev) => { - const current = prev.current - ? { ...prev.current, [type]: checked } - : null; - return { - ...prev, - current, - }; - }); - }; - - const handleSave = async () => { - if (!alarms.current) return; + const updateAlarmReceptionPath = async () => { try { - await axiosUpdateAlarm(alarms.current); // 푸쉬 μ•Œλ¦Ό 섀정이 λ³€κ²½λ˜μ—ˆμ„ 경우, 토큰을 μš”μ²­ν•˜κ±°λ‚˜ μ‚­μ œν•©λ‹ˆλ‹€. - if (alarms.current.push) { + if (alarms.current!.push) { const deviceToken = await requestFcmAndGetDeviceToken(); await axiosUpdateDeviceToken(deviceToken); } else { await deleteFcmToken(); await axiosUpdateDeviceToken(null); } + await axiosUpdateAlarmReceptionPath(alarms.current!); setAlarms({ current: alarms.current, original: alarms.current }); setModalTitle("섀정이 μ €μž₯λ˜μ—ˆμŠ΅λ‹ˆλ‹€"); } catch (error: any) { setAlarms((prev) => ({ ...prev, current: prev.original })); setHasErrorOnResponse(true); - setModalTitle(error.response.data.message); + if (error.response) setModalTitle(error.response.data.message); + else { + setModalTitle(error.name); + setModalContents(error.message); + } } finally { + setIsLoading(false); setShowResponseModal(true); } }; + const handleToggleChange = (type: keyof AlarmInfo, checked: boolean) => { + setAlarms((prev) => { + const current = prev.current + ? { ...prev.current, [type]: checked } + : null; + return { + ...prev, + current, + }; + }); + }; + + const handleSave = async () => { + setIsLoading(true); + if (!alarms.current) return; + debounce("alarmReceptionPath", updateAlarmReceptionPath, 300); + }; + const handleCancel = () => { - setAlarms((prev) => ({ ...prev, current: prev.original })); + !isLoading && setAlarms((prev) => ({ ...prev, current: prev.original })); }; const handleCloseModal = () => { @@ -74,7 +88,7 @@ const NotificationCardContainer = ({ alarm }: { alarm: AlarmInfo | null }) => { return ( <> - { fontColor: "var(--white-text-with-bg-color)", backgroundColor: "var(--sys-main-color)", isClickable: true, + isLoading: isLoading, }, { label: "μ·¨μ†Œ", onClick: handleCancel, - isClickable: true, + isClickable: !isLoading, + isLoading: isLoading, }, ] : [ @@ -104,17 +120,20 @@ const NotificationCardContainer = ({ alarm }: { alarm: AlarmInfo | null }) => { ] } onToggleChange={handleToggleChange} + isLoading={isLoading} /> {showResponseModal && (hasErrorOnResponse ? ( ) : ( ))} @@ -123,4 +142,4 @@ const NotificationCardContainer = ({ alarm }: { alarm: AlarmInfo | null }) => { ); }; -export default NotificationCardContainer; +export default AlarmCardContainer; diff --git a/frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.tsx b/frontend/src/Cabinet/components/Card/AlarmCard/AlarmCard.tsx similarity index 84% rename from frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.tsx rename to frontend/src/Cabinet/components/Card/AlarmCard/AlarmCard.tsx index c07812669..3c2ed2013 100644 --- a/frontend/src/Cabinet/components/Card/NotificationCard/NotificationCard.tsx +++ b/frontend/src/Cabinet/components/Card/AlarmCard/AlarmCard.tsx @@ -7,17 +7,19 @@ import { import ToggleSwitch from "@/Cabinet/components/Common/ToggleSwitch"; import { AlarmInfo } from "@/Cabinet/types/dto/alarm.dto"; -interface NotificationCardProps { +interface AlarmCardProps { alarm: AlarmInfo; buttons: IButtonProps[]; onToggleChange: (type: keyof AlarmInfo, checked: boolean) => void; + isLoading: boolean; } -const NotificationCard = ({ +const AlarmCard = ({ alarm, buttons, onToggleChange, -}: NotificationCardProps) => { + isLoading, +}: AlarmCardProps) => { const handleToggle = (type: keyof AlarmInfo) => (checked: boolean) => { onToggleChange(type, checked); }; @@ -26,9 +28,10 @@ const NotificationCard = ({ {label} ); @@ -36,7 +39,7 @@ const NotificationCard = ({ return ( > | null; // NOTE: icon 이 μžˆμ„ 경우, icon 을 ν‘œμ‹œ isClickable: boolean; isExtensible?: boolean; + isLoading?: boolean; } interface CardProps { @@ -41,7 +42,7 @@ const Card = ({ {onClickToolTip && } {buttons.length > 0 && ( - + {buttons?.map((button, index) => ( {!button.icon ? button.label : } ))} - + )} )} @@ -113,7 +115,7 @@ const ToolTipIcon = styled.div` } `; -export const CardButtonWrapper = styled.div` +export const CardButtonsWrapper = styled.div` display: flex; font-size: var(--size-base); `; @@ -124,6 +126,7 @@ export const CardButtonStyled = styled.div<{ icon?: React.FunctionComponent> | null; isClickable?: boolean; isExtensible?: boolean; + isLoading?: boolean; }>` ${(props) => props.icon @@ -151,7 +154,15 @@ export const CardButtonStyled = styled.div<{ font-weight: ${props.isClickable && 400}; } `} - cursor: ${(props) => (props.isClickable ? "pointer" : "default")}; + + cursor: ${(props) => { + if (props.isClickable) { + if (props.isLoading) return "wait"; // ex) ν”„λ‘œν•„ - μ•Œλ¦Ό μš”μ²­ ν›„ 응닡 μ „κΉŒμ§€ μ €μž₯ λ²„νŠΌ hoverμ‹œ + return "pointer"; + } + if (props.isLoading) return "not-allowed"; // ex) ν”„λ‘œν•„ - μ•Œλ¦Ό μš”μ²­ ν›„ 응닡 μ „κΉŒμ§€ μ·¨μ†Œ λ²„νŠΌ hoverμ‹œ + return "default"; + }}; & > svg { height: 20px; diff --git a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx index 0f99c12f3..81993b45e 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.container.tsx @@ -128,8 +128,8 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { closeAll(); }; - const onClickSlackNotiButton = () => { - navigator("slack-notification"); + const onClickSlackAlarmButton = () => { + navigator("slack-alarm"); closeAll(); }; @@ -182,7 +182,7 @@ const LeftMainNavContainer = ({ isAdmin }: { isAdmin?: boolean }) => { onClickFloorButton={onClickFloorButton} onClickSearchButton={onClickSearchButton} onClickLogoutButton={onClickLogoutButton} - onClickSlackNotiButton={onClickSlackNotiButton} + onClickSlackAlarmButton={onClickSlackAlarmButton} onClickAdminClubButton={onClickAdminClubButton} onClickMainClubButton={onClickMainClubButton} onClickProfileButton={onClickProfileButton} diff --git a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx index 2251e3ca4..38bdb1074 100644 --- a/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx +++ b/frontend/src/Cabinet/components/LeftNav/LeftMainNav/LeftMainNav.tsx @@ -2,9 +2,10 @@ import styled from "styled-components"; import { ReactComponent as LogoutImg } from "@/Cabinet/assets/images/close-square.svg"; import { ReactComponent as ClubImg } from "@/Cabinet/assets/images/clubIconGray.svg"; import { ReactComponent as ProfileImg } from "@/Cabinet/assets/images/profile-circle.svg"; -import { ReactComponent as SlackNotiImg } from "@/Cabinet/assets/images/slack-notification.svg"; +import { ReactComponent as SlackAlarmImg } from "@/Cabinet/assets/images/slack-alarm.svg"; import { ReactComponent as SlackImg } from "@/Cabinet/assets/images/slack.svg"; import { ReactComponent as StoreImg } from "@/Cabinet/assets/images/storeIconGray.svg"; +import { DISABLED_FLOOR } from "@/Cabinet/pages/AvailablePage"; interface ILeftMainNav { pathname: string; @@ -14,7 +15,7 @@ interface ILeftMainNav { currentFloor: number; onClickFloorButton: Function; onClickLogoutButton: React.MouseEventHandler; - onClickSlackNotiButton: React.MouseEventHandler; + onClickSlackAlarmButton: React.MouseEventHandler; onClickSearchButton: React.MouseEventHandler; onClickAdminClubButton: React.MouseEventHandler; onClickMainClubButton: React.MouseEventHandler; @@ -32,7 +33,7 @@ const LeftMainNav = ({ onClickHomeButton, onClickFloorButton, onClickLogoutButton, - onClickSlackNotiButton, + onClickSlackAlarmButton, onClickAdminClubButton, onClickMainClubButton, onClickProfileButton, @@ -57,19 +58,21 @@ const LeftMainNav = ({ Home {floors && - floors.map((floor, index) => ( - onClickFloorButton(floor)} - key={index} - > - {floor + "μΈ΅"} - - ))} + floors.map((floor, index) => + !(!isAdmin && DISABLED_FLOOR.includes(floor.toString())) ? ( + onClickFloorButton(floor)} + key={index} + > + {floor + "μΈ΅"} + + ) : null + )} - - Noti + + Alarm void }) => { const [currentFloorSection, setCurrentFloorSection] = useRecoilState( currentSectionNameState ); + const currentFloorNumber = useRecoilValue(currentFloorNumberState); const { pathname } = useLocation(); const isAdmin = pathname.includes("admin"); @@ -45,6 +47,7 @@ const LeftSectionNav = ({ closeLeftNav }: { closeLeftNav: () => void }) => { {!isAdmin && !isClubSection && + !DISABLED_FLOOR.includes(currentFloorNumber.toString()) && (section.alarmRegistered ? ( ) : ( diff --git a/frontend/src/Cabinet/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx b/frontend/src/Cabinet/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx index 241ed3484..9e7040198 100644 --- a/frontend/src/Cabinet/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx +++ b/frontend/src/Cabinet/components/MapInfo/MapFloorSelectOption/MapFloorSelectOption.tsx @@ -1,4 +1,5 @@ import styled from "styled-components"; +import { DISABLED_FLOOR } from "@/Cabinet/pages/AvailablePage"; const MapFloorSelectOption: React.FC<{ selectFloor: Function; @@ -7,6 +8,7 @@ const MapFloorSelectOption: React.FC<{ return ( {floorInfo.map((info, idx) => ( + !(DISABLED_FLOOR.includes(info.toString())) && selectFloor(info)} diff --git a/frontend/src/Cabinet/components/MapInfo/MapInfo.tsx b/frontend/src/Cabinet/components/MapInfo/MapInfo.tsx index fd82be807..0ffc4c014 100644 --- a/frontend/src/Cabinet/components/MapInfo/MapInfo.tsx +++ b/frontend/src/Cabinet/components/MapInfo/MapInfo.tsx @@ -1,7 +1,9 @@ import styled from "styled-components"; import MapFloorSelect from "@/Cabinet/components/MapInfo/MapFloorSelect/MapFloorSelect"; import MapGrid from "@/Cabinet/components/MapInfo/MapGrid/MapGrid"; +import { DISABLED_FLOOR } from "@/Cabinet/pages/AvailablePage"; +const DEFAULT_FLOOR = 2; const MapInfo = ({ touchStart, touchEnd, @@ -12,11 +14,16 @@ const MapInfo = ({ }: { touchStart: React.TouchEventHandler; touchEnd: React.TouchEventHandler; - floor: number; + floor: number | undefined; setFloor: React.Dispatch>; floorInfo: number[]; closeMap: React.MouseEventHandler; }) => { + const currentFloor = floor ?? DEFAULT_FLOOR; + const validFloor = DISABLED_FLOOR.includes(currentFloor.toString()) + ? DEFAULT_FLOOR + : currentFloor; + return ( - - + + ); }; diff --git a/frontend/src/Cabinet/components/Modals/Modal.tsx b/frontend/src/Cabinet/components/Modals/Modal.tsx index 545b606bb..50d1b7c87 100644 --- a/frontend/src/Cabinet/components/Modals/Modal.tsx +++ b/frontend/src/Cabinet/components/Modals/Modal.tsx @@ -97,7 +97,7 @@ const Modal: React.FC<{ modalContents: IModalContents }> = (props) => { {detail && ( )} - {renderAdditionalComponent && renderAdditionalComponent()} + {renderAdditionalComponent?.()} {type === "hasProceedBtn" && ( - - - {sortedItems.length ? ( - <> - {sortedItems.map((item, idx) => { - const hasTypes = - item.itemDetails !== convertToItemTypeLabel(itemsType); - return ( - - - - - - - {convertToItemTypeLabel(itemsType)} - - {hasTypes && {item.itemDetails}} - - - ); - })} - - ) : ( - !isToggled && ( - - ) - )} - - - + + + +

{convertToItemTypeLabel(itemsType)}

+ + + {showTooltip && ( + handleMouseEnter()} + onMouseLeave={() => handleMouseLeave()} + > + {itemTooltip} + + )} + +
+ +
+ + {sortedItems.length ? ( + <> + {sortedItems.map((item, idx) => { + const hasTypes = + item.itemDetails !== convertToItemTypeLabel(itemsType); + return ( + + + + + + {convertToItemTypeLabel(itemsType)} + {hasTypes && {item.itemDetails}} + + + ); + })} + + ) : ( + !isToggled && ( + + ) + )} + +
); }; diff --git a/frontend/src/Cabinet/components/Store/StoreInfo.tsx b/frontend/src/Cabinet/components/Store/StoreInfo.tsx index 28871c665..12aaa853f 100644 --- a/frontend/src/Cabinet/components/Store/StoreInfo.tsx +++ b/frontend/src/Cabinet/components/Store/StoreInfo.tsx @@ -17,6 +17,7 @@ import { axiosMyInfo, } from "@/Cabinet/api/axios/axios.custom"; import useMenu from "@/Cabinet/hooks/useMenu"; +import { HttpStatusCode } from "axios"; const StoreInfo = () => { // 처음 λ‚ κ°œ μ—΄μ—ˆμ„ λ•Œ getμš”μ²­ λ‘œλ”© ν•¨μˆ˜ @@ -58,7 +59,7 @@ const StoreInfo = () => { setTodayCoinCollection(true); } } catch (error: any) { - if (error.response && error.response.status === 400) { + if (error.response && error.response.status === HttpStatusCode.BadRequest) { setModalTitle("동전 쀍기 μ‹€νŒ¨"); } else { setModalTitle(error.data.message || error.response.data.message); diff --git a/frontend/src/Cabinet/components/TopNav/TopNav.container.tsx b/frontend/src/Cabinet/components/TopNav/TopNav.container.tsx index 8ec3690cf..2f8260110 100644 --- a/frontend/src/Cabinet/components/TopNav/TopNav.container.tsx +++ b/frontend/src/Cabinet/components/TopNav/TopNav.container.tsx @@ -1,80 +1,27 @@ -import React, { SetStateAction, useEffect, useState } from "react"; -import { useLocation, useNavigate } from "react-router-dom"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; -import { - buildingsFloorState, - currentBuildingNameState, - myCabinetInfoState, -} from "@/Cabinet/recoil/atoms"; +import React, { useEffect, useState } from "react"; +import { useRecoilState, useRecoilValue } from "recoil"; +import { currentBuildingNameState } from "@/Cabinet/recoil/atoms"; import { buildingsState } from "@/Cabinet/recoil/selectors"; import TopNav from "@/Cabinet/components/TopNav/TopNav"; -import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; -import { - axiosBuildingFloor, - axiosMyLentInfo, -} from "@/Cabinet/api/axios/axios.custom"; import useMenu from "@/Cabinet/hooks/useMenu"; -const TopNavContainer: React.FC<{ - setIsLoading: React.Dispatch>; -}> = (props) => { +const TopNavContainer: React.FC = () => { const [buildingClicked, setBuildingClicked] = useState(false); const [currentBuildingName, setCurrentBuildingName] = useRecoilState( currentBuildingNameState ); - const setMyLentInfo = - useSetRecoilState(myCabinetInfoState); - const setBuildingsFloor = useSetRecoilState(buildingsFloorState); const buildingsList = useRecoilValue>(buildingsState); - const { setIsLoading } = props; const { toggleLeftNav } = useMenu(); - const navigator = useNavigate(); - const isLocation = useLocation(); const onClickLogo = () => { toggleLeftNav(); }; - useEffect(() => { - function setTimeoutPromise(delay: number) { - return new Promise((resolve) => setTimeout(resolve, delay)); - } - const getBuildingsData = async () => { - try { - await setTimeoutPromise(500); - const buildingsFloorData = await axiosBuildingFloor(); - setBuildingsFloor([...buildingsFloorData.data]); - } catch (error) { - console.log(error); - } - }; - async function getMyLentInfo() { - try { - const { data: myLentInfo } = await axiosMyLentInfo(); - - setMyLentInfo(myLentInfo); - } catch (error) { - console.error(error); - } - } - - Promise.all([getBuildingsData(), getMyLentInfo()]).then(() => - setIsLoading(false) - ); - }, []); - useEffect(() => { if (buildingsList.length === 0) return; setCurrentBuildingName(buildingsList[0]); }, [buildingsList]); - useEffect(() => { - if (currentBuildingName === undefined) return; - else if (currentBuildingName === "μƒˆλ‘¬κ΄€") { - navigator(isLocation); - } - }, [currentBuildingName]); - return ( { + isApiSupported = result; + if ( + typeof window !== "undefined" && + typeof window.navigator !== "undefined" && + isApiSupported + ) { + messaging = getMessaging(app); + } +}); + +const unsupportedMsg = `μ‚¬μš© 쀑인 ν™˜κ²½μ—μ„œλŠ” ν‘Έμ‹œ μ•Œλ¦Ό κΈ°λŠ₯이 +μ§€μ›λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. +λ°μŠ€ν¬νƒ‘ μ΄μš©μ„ ꢌμž₯λ“œλ¦½λ‹ˆλ‹€.`; + +const checkBrowserSupport = () => { + if (!isApiSupported) { + let error = new Error(unsupportedMsg); + error.name = "λΈŒλΌμš°μ € μ•Œλ¦Ό 지원 μ œν•œ"; + throw error; + } +}; // FCM APP을 등둝 ν›„ λΈŒλΌμš°μ € μ•Œλ¦Ό κΆŒν•œμ„ μš”μ²­ν•˜κ³ , 토큰을 λ°˜ν™˜ export const requestFcmAndGetDeviceToken = async (): Promise => { - console.log("κΆŒν•œ μš”μ²­ 쀑..."); + checkBrowserSupport(); + console.log("κΆŒν•œ μš”μ²­ 쀑..."); const permission = await Notification.requestPermission(); if (permission === "denied") { console.log("μ•Œλ¦Ό κΆŒν•œ ν—ˆμš© μ•ˆλ¨"); @@ -32,14 +60,14 @@ export const requestFcmAndGetDeviceToken = async (): Promise => { console.log("μ•Œλ¦Ό κΆŒν•œμ΄ ν—ˆμš©λ¨"); - const token = await getToken(messaging, { + const token = await getToken(messaging!, { vapidKey: import.meta.env.VITE_FIREBASE_APP_VAPID_KEY, }); if (token) console.log("token: ", token); else console.log("Can not get Token"); - onMessage(messaging, (payload) => { + onMessage(messaging!, (payload) => { console.log("λ©”μ‹œμ§€κ°€ λ„μ°©ν–ˆμŠ΅λ‹ˆλ‹€.", payload); // ... }); @@ -49,6 +77,8 @@ export const requestFcmAndGetDeviceToken = async (): Promise => { // FCM 토큰 제거 및 λΈŒλΌμš°μ € μ•ŒλžŒ κΆŒν•œ ν•΄μ œ export const deleteFcmToken = async (): Promise => { - await deleteToken(messaging); + checkBrowserSupport(); + + await deleteToken(messaging!); console.log("Token deleted."); }; diff --git a/frontend/src/Cabinet/pages/Layout.tsx b/frontend/src/Cabinet/pages/Layout.tsx index 6062bc29a..3ec979485 100644 --- a/frontend/src/Cabinet/pages/Layout.tsx +++ b/frontend/src/Cabinet/pages/Layout.tsx @@ -4,6 +4,8 @@ import { useLocation, useNavigate } from "react-router-dom"; import { useSetRecoilState } from "recoil"; import styled, { css } from "styled-components"; import { + buildingsFloorState, + myCabinetInfoState, myClubListState, serverTimeState, targetClubInfoState, @@ -18,15 +20,18 @@ import OverduePenaltyModal from "@/Cabinet/components/Modals/OverduePenaltyModal import StoreInfo from "@/Cabinet/components/Store/StoreInfo"; import TopNavContainer from "@/Cabinet/components/TopNav/TopNav.container"; import { additionalModalType } from "@/Cabinet/assets/data/maps"; +import { MyCabinetInfoResponseDto } from "@/Cabinet/types/dto/cabinet.dto"; import { ClubPaginationResponseDto, ClubResponseDto, } from "@/Cabinet/types/dto/club.dto"; import { UserDto, UserInfo } from "@/Cabinet/types/dto/user.dto"; import { + axiosBuildingFloor, axiosMyClubList, axiosMyInfo, axiosMyItems, + axiosMyLentInfo, } from "@/Cabinet/api/axios/axios.custom"; import { getCookie } from "@/Cabinet/api/react_cookie/cookies"; import useMenu from "@/Cabinet/hooks/useMenu"; @@ -38,7 +43,6 @@ const token = getCookie("access_token"); const Layout = (): JSX.Element => { const [hasPenaltyItem, setHasPenaltyItem] = useState(true); const [isLoading, setIsLoading] = useState(true); - const [isValidToken, setIsValidToken] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); const [myInfoData, setMyInfoData] = useState(null); const setServerTime = useSetRecoilState(serverTimeState); @@ -47,6 +51,9 @@ const Layout = (): JSX.Element => { useSetRecoilState(myClubListState); const setTargetClubInfo = useSetRecoilState(targetClubInfoState); + const setBuildingsFloor = useSetRecoilState(buildingsFloorState); + const setMyLentInfo = + useSetRecoilState(myCabinetInfoState); const navigate = useNavigate(); const location = useLocation(); const { closeAll } = useMenu(); @@ -79,7 +86,6 @@ const Layout = (): JSX.Element => { setServerTime(new Date(formattedServerTime)); // 접속 ν›„ 졜초 μ„œλ²„ μ‹œκ°„μ„ κ°€μ Έμ˜΄ setMyInfoData(myInfo); setUser(myInfo); - setIsValidToken(true); if (data.penaltyItems.length == 0) { setHasPenaltyItem(false); } @@ -108,11 +114,33 @@ const Layout = (): JSX.Element => { } }; + const getBuildingsData = async () => { + try { + const buildingsFloorData = await axiosBuildingFloor(); + setBuildingsFloor([...buildingsFloorData.data]); + } catch (error) { + console.log(error); + } + }; + + async function getMyLentInfo() { + try { + const { data: myLentInfo } = await axiosMyLentInfo(); + + setMyLentInfo(myLentInfo); + } catch (error) { + console.error(error); + } + } + useEffect(() => { deleteOldPointColors(); if (!token && !isLoginPage) navigate("/login"); else if (token) { getMyInfo(); + Promise.all([getBuildingsData(), getMyLentInfo()]).then(() => + setIsLoading(false) + ); getMyClubList(); // μ„œλ²„ μ‹œκ°„ const serverTimer = setInterval(() => { @@ -152,34 +180,40 @@ const Layout = (): JSX.Element => { ) : ( - {isValidToken && } {isLoading ? ( - + + + ) : ( - - - - - - - - - - - - - {isModalOpen && myInfoData && myInfoData.unbannedAt !== undefined && ( - - )} - + <> + + + + + + + + + + + + + + {isModalOpen && + myInfoData && + myInfoData.unbannedAt !== undefined && ( + + )} + + )} ); @@ -227,4 +261,8 @@ const MenuBgStyled = styled.div` position: none; `; +const LoadingAnimationWrapper = styled.div` + height: 100vh; +`; + export default Layout; diff --git a/frontend/src/Cabinet/pages/MainPage.tsx b/frontend/src/Cabinet/pages/MainPage.tsx index c98123f84..0901deb30 100644 --- a/frontend/src/Cabinet/pages/MainPage.tsx +++ b/frontend/src/Cabinet/pages/MainPage.tsx @@ -152,9 +152,10 @@ const MainPage = () => { - + {currentSectionName !== SectionType.elevator && - currentSectionName !== SectionType.stairs && ( + currentSectionName !== SectionType.stairs && + !DISABLED_FLOOR.includes(currentFloor.toString()) && ( ` } `; -const AlertStyled = styled.div<{ currentFloor: number }>` - visibility: ${(props) => - DISABLED_FLOOR.includes(props.currentFloor.toString()) +const AlertStyled = styled.div<{ currentFloor: number | undefined }>` + visibility: ${({ currentFloor }) => + currentFloor && DISABLED_FLOOR.includes(currentFloor.toString()) ? "hidden" : "visible"}; height: 30px; diff --git a/frontend/src/Cabinet/pages/PostLogin.tsx b/frontend/src/Cabinet/pages/PostLogin.tsx index ef31dbbd8..8e352ed19 100644 --- a/frontend/src/Cabinet/pages/PostLogin.tsx +++ b/frontend/src/Cabinet/pages/PostLogin.tsx @@ -2,9 +2,9 @@ import { deleteFcmToken, requestFcmAndGetDeviceToken, } from "@/Cabinet/firebase/firebase-messaging-sw"; -import { useEffect, useState } from "react"; -import { useLocation, useNavigate } from "react-router-dom"; -import { useRecoilState, useSetRecoilState } from "recoil"; +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { useSetRecoilState } from "recoil"; import { userState } from "@/Cabinet/recoil/atoms"; import AnnounceTemplate from "@/Cabinet/components/Announce/AnnounceTemplate"; import { UserDto } from "@/Cabinet/types/dto/user.dto"; @@ -15,10 +15,7 @@ import { import { getCookie } from "@/Cabinet/api/react_cookie/cookies"; const PostLogin = (): JSX.Element => { - const [isLoading, setIsLoading] = useState(true); - const [isValidToken, setIsValidToken] = useState(false); - const [myInfo, setMyInfo] = useRecoilState(userState); - + const setMyInfo = useSetRecoilState(userState); const setUser = useSetRecoilState(userState); const navigate = useNavigate(); const token = getCookie("access_token"); @@ -27,7 +24,6 @@ const PostLogin = (): JSX.Element => { try { const { data: myInfo } = await axiosMyInfo(); setUser(myInfo); - setIsValidToken(true); if (myInfo.alarmTypes?.push && myInfo.isDeviceTokenExpired) { await deleteFcmToken(); const deviceToken = await requestFcmAndGetDeviceToken(); diff --git a/frontend/src/Cabinet/pages/ProfilePage.tsx b/frontend/src/Cabinet/pages/ProfilePage.tsx index a91eb6daf..087cccc32 100644 --- a/frontend/src/Cabinet/pages/ProfilePage.tsx +++ b/frontend/src/Cabinet/pages/ProfilePage.tsx @@ -7,9 +7,9 @@ import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; import styled from "styled-components"; import { userState } from "@/Cabinet/recoil/atoms"; +import AlarmCardContainer from "@/Cabinet/components/Card/AlarmCard/AlarmCard.container"; import DisplayStyleCardContainer from "@/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container"; import LentInfoCardContainer from "@/Cabinet/components/Card/LentInfoCard/LentInfoCard.container"; -import NotificationCardContainer from "@/Cabinet/components/Card/NotificationCard/NotificationCard.container"; import PointColorCardContainer from "@/Cabinet/components/Card/PointColorCard/PointColorCard.container"; import ProfileCardContainer from "@/Cabinet/components/Card/ProfileCard/ProfileCard.container"; import LoadingAnimation from "@/Cabinet/components/Common/LoadingAnimation"; @@ -56,7 +56,7 @@ const ProfilePage = () => { name={myInfo.name} unbannedAt={myInfo.unbannedAt} /> - + @@ -76,7 +76,7 @@ const CardGridWrapper = styled.div` grid-template-rows: 163px 183px 230px; grid-template-areas: "profile lentInfo" // h: 163px h: 366px "displayStyle lentInfo" // h: 183px - "pointColor notification"; // h: 230px h: 230px + "pointColor alarm"; // h: 230px h: 230px @media (max-width: 768px) { grid-template-columns: 350px; @@ -86,7 +86,7 @@ const CardGridWrapper = styled.div` "lentInfo" "displayStyle" "pointColor" - "notification"; + "alarm"; } `; diff --git a/frontend/src/Cabinet/pages/StoreMainPage.tsx b/frontend/src/Cabinet/pages/StoreMainPage.tsx index 626935f36..908044530 100644 --- a/frontend/src/Cabinet/pages/StoreMainPage.tsx +++ b/frontend/src/Cabinet/pages/StoreMainPage.tsx @@ -117,7 +117,7 @@ const StoreCoinGridWrapper = styled.div` grid-template-rows: 150px 150px; grid-template-areas: "coinPick EXTENSION SWAP" - "coinPick ALERT PENALTY"; + "coinPick ALARM PENALTY"; padding-bottom: 30px; @@ -127,7 +127,7 @@ const StoreCoinGridWrapper = styled.div` grid-template-areas: "coinPick EXTENSION " "coinPick SWAP" - "ALERT PENALTY"; + "ALARM PENALTY"; } // λ‚˜μ€‘μ— 고치기 @@ -139,7 +139,7 @@ const StoreCoinGridWrapper = styled.div` "coinPick" "EXTENSION" "SWAP" - "ALERT" + "ALARM" "PENALTY"; } `; diff --git a/frontend/src/Cabinet/pages/admin/AdminMainPage.tsx b/frontend/src/Cabinet/pages/admin/AdminMainPage.tsx index adc495cb1..e923113fc 100644 --- a/frontend/src/Cabinet/pages/admin/AdminMainPage.tsx +++ b/frontend/src/Cabinet/pages/admin/AdminMainPage.tsx @@ -118,7 +118,7 @@ const AdminMainPage = () => { /> - + { +const AdminSlackAlarmPage = () => { const receiverInputRef = useRef(null); const msgTextAreaRef = useRef(null); const [showResponseModal, setShowResponseModal] = useState(false); @@ -65,13 +65,13 @@ const AdminSlackNotiPage = () => { let channelId = SlackChannels.find((channel) => { return receiverInputRef.current!.value === channel.title; })?.channelId; - await axiosSendSlackNotificationToChannel( + await axiosSendSlackAlarmToChannel( receiverInputRef.current.value, msgTextAreaRef.current!.value, channelId ); } else { - await axiosSendSlackNotificationToUser( + await axiosSendSlackAlarmToUser( receiverInputRef.current.value, msgTextAreaRef.current!.value ); @@ -138,7 +138,7 @@ const AdminSlackNotiPage = () => { λ°›λŠ”μ΄(Intra ID/ Channel)* - @@ -320,4 +320,4 @@ const FormButtonStyled = styled.button<{ primary?: boolean }>` } `; -export default AdminSlackNotiPage; +export default AdminSlackAlarmPage; diff --git a/frontend/src/index.css b/frontend/src/index.css index 41efe0498..7d06409b8 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,4 +1,5 @@ -@import url("https://fonts.googleapis.com/css2?family=Do+Hyeon&family=Noto+Sans+KR:wght@300;400;700&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Do+Hyeon&display=swap&text=μƒˆλ‘¬κ΄€λ‘œκ·ΈμΈμ€‘μˆ˜μš”μ§€μ‹νšŒμ—¬κΈ°μ—”μ‚¬λ¬Όν•¨μ΄μ—†μ–΄4μΈ΅μ€ν˜„μž¬μš©λΆˆκ°€μž…λ‹ˆλ‹€!"); +@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;700&display=swap"); :root { /* white, gray, black */ @@ -95,7 +96,6 @@ --custom-green-100: var(--ref-green-300); --custom-green-200: var(--ref-green-400); - /* component variable */ --color-picker-hash-color: var(--ref-gray-450); --sys-sub-color: var(--ref-purple-300); @@ -120,6 +120,7 @@ font-size: 16px; line-height: 24px; font-weight: 400; + display: swap; } a { @@ -223,6 +224,7 @@ input { -ms-overflow-style: none; scrollbar-width: none; } + .noScrollbar::-webkit-scrollbar { display: none; } @@ -253,4 +255,4 @@ input { .domainButtonActive { color: var(--sys-main-color) !important; -} +} \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 1f4fddbe7..a8de74500 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -17,7 +17,11 @@ import { GlobalStyle } from "@/Cabinet/assets/data/ColorTheme"; Sentry.init({ dsn: import.meta.env.VITE_SENTRY_DSN, environment: - import.meta.env.VITE_IS_LOCAL === "true" ? "local" : "production", + import.meta.env.VITE_IS_LOCAL === "true" + ? "local" + : import.meta.env.VITE_BE_HOST.includes("dev") + ? "development" + : "production", release: "^8.18.0", integrations: [ // See docs for support of different versions of variation of react router @@ -48,7 +52,7 @@ Sentry.init({ // Capture Replay for 100% of all sessions, // plus for 100% of sessions with an error - replaysSessionSampleRate: 1.0, + replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0, }); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 7a1007709..18c858d56 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -38,6 +38,7 @@ export default defineConfig({ path: "/hmr/", }, }, + test: { include: [ "**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}", diff --git a/package-lock.json b/package-lock.json index c40d5e346..1d7dd2220 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "Cabi", + "name": "cabi", "lockfileVersion": 3, "requires": true, "packages": { "": { "devDependencies": { - "rollup-plugin-visualizer": "^5.9.0", + "rollup-plugin-visualizer": "^5.12.0", "webpack-bundle-analyzer": "^4.8.0" } }, @@ -288,10 +288,11 @@ } }, "node_modules/rollup-plugin-visualizer": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.0.tgz", - "integrity": "sha512-bbDOv47+Bw4C/cgs0czZqfm8L82xOZssk4ayZjG40y9zbXclNk7YikrZTDao6p7+HDiGxrN0b65SgZiVm9k1Cg==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz", + "integrity": "sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==", "dev": true, + "license": "MIT", "dependencies": { "open": "^8.4.0", "picomatch": "^2.3.1", @@ -305,7 +306,7 @@ "node": ">=14" }, "peerDependencies": { - "rollup": "2.x || 3.x" + "rollup": "2.x || 3.x || 4.x" }, "peerDependenciesMeta": { "rollup": { diff --git a/package.json b/package.json index 28e506cfd..c06c46c10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "devDependencies": { - "rollup-plugin-visualizer": "^5.9.0", + "rollup-plugin-visualizer": "^5.12.0", "webpack-bundle-analyzer": "^4.8.0" } }