From 100af40313b592ced9362a4768f7eea528b4c130 Mon Sep 17 00:00:00 2001 From: Manuel Meister Date: Fri, 21 Jun 2024 13:07:09 +0200 Subject: [PATCH] Add UserAvatar fallback color and abbreviation on profile - Improve avatar override field in CollaboratorEdit - Add api tests for color and abbreviation - Moved user translations to common as profile - Use campCollaboration for UserAvatar in camp context --- api/fixtures/campCollaborations.yml | 2 + api/fixtures/profiles.yml | 2 + .../dev-data/Version202404121950.php | 8 - .../dev-data/Version202406211251.php | 31 ++ api/migrations/dev-data/data.sql | 92 ++--- .../schema/Version20240621060047.php | 27 ++ api/src/Entity/Profile.php | 20 ++ api/src/Entity/User.php | 18 + .../UpdateCampCollaborationTest.php | 66 ++++ api/tests/Api/Profiles/ReadProfileTest.php | 6 + api/tests/Api/Profiles/UpdateProfileTest.php | 65 ++++ ... with data set camp_collaborations__1.json | 22 ++ ...esStructure with data set profiles__1.json | 10 + ... with data set camp_collaborations__1.json | 2 + ...tchesStructure with data set camps__1.json | 10 + ...esStructure with data set profiles__1.json | 2 + ...est__testOpenApiSpecMatchesSnapshot__1.yml | 339 ++++++++++++++++++ api/tests/Api/Users/ReadUserTest.php | 4 + .../campCollaborationInitials.spec.js | 6 + common/helpers/__tests__/colors.spec.js | 15 + common/helpers/__tests__/userInitials.spec.js | 6 +- common/helpers/campCollaborationInitials.js | 21 +- common/helpers/colors.js | 29 +- common/helpers/userInitials.js | 6 + common/locales/de.json | 13 + common/locales/en.json | 13 + common/locales/fr.json | 13 + common/locales/it.json | 13 + common/locales/rm.json | 17 +- .../collaborator/CollaboratorCreate.vue | 2 +- .../collaborator/CollaboratorEdit.vue | 10 +- .../collaborator/CollaboratorForm.vue | 60 +++- .../src/components/navigation/UserMeta.vue | 39 +- frontend/src/components/user/UserAvatar.vue | 18 +- frontend/src/locales/de.json | 15 +- frontend/src/locales/en.json | 15 +- frontend/src/locales/fr.json | 15 +- frontend/src/locales/it.json | 15 +- frontend/src/locales/rm.json | 14 - frontend/src/views/Profile.vue | 12 +- .../camp/navigation/desktop/NavTopbar.vue | 2 +- .../camp/navigation/mobile/NavSidebar.vue | 10 +- 42 files changed, 925 insertions(+), 180 deletions(-) create mode 100644 api/migrations/dev-data/Version202406211251.php create mode 100644 api/migrations/schema/Version20240621060047.php diff --git a/api/fixtures/campCollaborations.yml b/api/fixtures/campCollaborations.yml index 87e8088174..f345312b04 100644 --- a/api/fixtures/campCollaborations.yml +++ b/api/fixtures/campCollaborations.yml @@ -9,6 +9,8 @@ App\Entity\CampCollaboration: camp: '@camp1' status: established role: member + abbreviation: "🧑🏽‍🚀" + color: "#11a1e1" campCollaboration3guest: user: '@user3guest' camp: '@camp1' diff --git a/api/fixtures/profiles.yml b/api/fixtures/profiles.yml index d4b184a86a..024fffe900 100644 --- a/api/fixtures/profiles.yml +++ b/api/fixtures/profiles.yml @@ -6,6 +6,8 @@ App\Entity\Profile: surname: Baden-Powell nickname: Bi-Pi language: en + abbreviation: '⚜️' + color: '#6a209b' roles: [ 'ROLE_USER' ] profile2member: user: '@user2member' diff --git a/api/migrations/dev-data/Version202404121950.php b/api/migrations/dev-data/Version202404121950.php index 82166b1d9e..da25cf7867 100644 --- a/api/migrations/dev-data/Version202404121950.php +++ b/api/migrations/dev-data/Version202404121950.php @@ -16,14 +16,6 @@ public function getDescription(): string { public function up(Schema $schema): void { // START PHP CODE - $this->addSql(createTruncateDatabaseCommand()); - - $statements = getStatementsForMigrationFile(); - foreach ($statements as $statement) { - if (trim($statement)) { - $this->addSql($statement); - } - } // END PHP CODE } diff --git a/api/migrations/dev-data/Version202406211251.php b/api/migrations/dev-data/Version202406211251.php new file mode 100644 index 0000000000..a4ae84b459 --- /dev/null +++ b/api/migrations/dev-data/Version202406211251.php @@ -0,0 +1,31 @@ +addSql(createTruncateDatabaseCommand()); + + $statements = getStatementsForMigrationFile(); + foreach ($statements as $statement) { + if (trim($statement)) { + $this->addSql($statement); + } + } + // END PHP CODE + } + + public function down(Schema $schema): void {} +} diff --git a/api/migrations/dev-data/data.sql b/api/migrations/dev-data/data.sql index 807e1f3b54..dc7516a945 100644 --- a/api/migrations/dev-data/data.sql +++ b/api/migrations/dev-data/data.sql @@ -2,22 +2,22 @@ -INSERT INTO public.profile (id, email, firstname, surname, nickname, language, roles, createtime, updatetime, googleid, pbsmidataid, cevidbid, untrustedemail, untrustedemailkeyhash, jubladbid) VALUES - ('711ad2e96f9f', 'admin@example.com', 'Admi', 'Nistrator', 'Administrator', 'de', '["ROLE_USER", "ROLE_ADMIN"]', '2022-01-23 16:19:10', '2022-01-23 16:19:10', NULL, NULL, NULL, NULL, NULL, NULL), - ('5e387cad273d', 'test@example.com', 'Robert', 'Baden-Powell', 'Bi-Pi', 'de-CH-scout', '["ROLE_USER"]', '2022-01-23 16:19:10', '2023-08-08 09:11:11', NULL, NULL, NULL, NULL, NULL, NULL), - ('0870635edda6', 'idefix@example.com', 'Tremaine', 'Kohler', 'Idefix', 'en', '["ROLE_USER"]', '2022-01-23 16:19:10', '2022-02-04 19:18:43', NULL, NULL, NULL, NULL, NULL, NULL), - ('4cda72af2704', 'et@example.com', 'Karlie', 'Terry', 'ET', 'en', '["ROLE_USER"]', '2022-01-23 16:19:10', '2022-02-04 19:21:43', NULL, NULL, NULL, NULL, NULL, NULL), - ('22dce794d4e2', 'snoopy@example.com', 'Pat', 'Fadel', 'Snoopy', 'en', '["ROLE_USER"]', '2022-01-23 16:19:10', '2022-02-04 19:19:40', NULL, NULL, NULL, NULL, NULL, NULL), - ('d46337a76a2c', 'salamander@example.com', 'Fritz', 'Müller', 'Salamander', 'de', '["ROLE_USER"]', '2022-02-04 19:26:22', '2022-02-04 23:08:12', NULL, NULL, NULL, NULL, NULL, NULL), - ('f9f1a2f9af25', 'baghira@example.com', 'Zora', 'Steuber', 'Baghira', 'en', '["ROLE_USER"]', '2022-01-23 16:19:10', '2022-02-04 19:18:01', NULL, NULL, NULL, NULL, NULL, NULL), - ('7d03c967be7e', 'castor@example.com', 'Hans', 'Muster', 'Castor', 'de', '["ROLE_USER"]', '2022-02-04 19:25:07', '2022-02-04 23:07:57', NULL, NULL, NULL, NULL, NULL, NULL), - ('d36197370d44', 'sed@example.com', 'Clifford', 'Beier', 'sed', 'en', '["ROLE_USER"]', '2022-01-23 16:19:10', '2022-01-23 16:19:10', NULL, NULL, NULL, NULL, NULL, NULL), - ('e5433660140b', 'sit@example.com', 'Wanda', 'Koelpin', 'sit', 'en', '["ROLE_USER"]', '2022-01-23 16:19:10', '2022-01-23 16:19:10', NULL, NULL, NULL, NULL, NULL, NULL), - ('4bc441dc4b29', 'luke@skywalker.com', 'Luke', 'Skywalker', NULL, 'en', '["ROLE_USER"]', '2023-08-12 16:43:34', '2023-08-12 16:43:34', NULL, NULL, NULL, NULL, NULL, NULL), - ('5552108bf43e', 'john@wick.com', 'John', 'Wick', NULL, 'en', '["ROLE_USER"]', '2023-08-12 16:46:43', '2023-08-12 16:46:43', NULL, NULL, NULL, NULL, NULL, NULL), - ('abfcbcbd4566', 'clark@kent.com', 'Clark', 'Kent', NULL, 'en', '["ROLE_USER"]', '2023-08-12 16:49:27', '2023-08-12 16:49:27', NULL, NULL, NULL, NULL, NULL, NULL), - ('3f3fa9319dd2', 'bruce@wayne.com', 'Bruce', 'Wayne', NULL, 'en', '["ROLE_USER"]', '2023-08-12 16:55:28', '2023-08-12 16:55:28', NULL, NULL, NULL, NULL, NULL, NULL), - ('51245d0e2ad4', 'felicity@smoak.com', 'Felicity', 'Smoak', NULL, 'en', '["ROLE_USER"]', '2023-08-12 16:59:38', '2023-08-12 16:59:38', NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO public.profile (id, email, firstname, surname, nickname, language, color, abbreviation, roles, createtime, updatetime, googleid, pbsmidataid, cevidbid, untrustedemail, untrustedemailkeyhash, jubladbid) VALUES + ('711ad2e96f9f', 'admin@example.com', 'Admi', 'Nistrator', 'Administrator', 'de', NULL, NULL, '["ROLE_USER", "ROLE_ADMIN"]', '2022-01-23 16:19:10', '2022-01-23 16:19:10', NULL, NULL, NULL, NULL, NULL, NULL), + ('5e387cad273d', 'test@example.com', 'Robert', 'Baden-Powell', 'Bi-Pi', 'de-CH-scout', '#6a209b', '⚜️', '["ROLE_USER"]', '2022-01-23 16:19:10', '2023-08-08 09:11:11', NULL, NULL, NULL, NULL, NULL, NULL), + ('0870635edda6', 'idefix@example.com', 'Tremaine', 'Kohler', 'Idefix', 'en', NULL, NULL, '["ROLE_USER"]', '2022-01-23 16:19:10', '2022-02-04 19:18:43', NULL, NULL, NULL, NULL, NULL, NULL), + ('4cda72af2704', 'et@example.com', 'Karlie', 'Terry', 'ET', 'en', NULL, NULL, '["ROLE_USER"]', '2022-01-23 16:19:10', '2022-02-04 19:21:43', NULL, NULL, NULL, NULL, NULL, NULL), + ('22dce794d4e2', 'snoopy@example.com', 'Pat', 'Fadel', 'Snoopy', 'en', NULL, NULL, '["ROLE_USER"]', '2022-01-23 16:19:10', '2022-02-04 19:19:40', NULL, NULL, NULL, NULL, NULL, NULL), + ('d46337a76a2c', 'salamander@example.com', 'Fritz', 'Müller', 'Salamander', 'de', NULL, NULL, '["ROLE_USER"]', '2022-02-04 19:26:22', '2022-02-04 23:08:12', NULL, NULL, NULL, NULL, NULL, NULL), + ('f9f1a2f9af25', 'baghira@example.com', 'Zora', 'Steuber', 'Baghira', 'en', NULL, NULL, '["ROLE_USER"]', '2022-01-23 16:19:10', '2022-02-04 19:18:01', NULL, NULL, NULL, NULL, NULL, NULL), + ('7d03c967be7e', 'castor@example.com', 'Hans', 'Muster', 'Castor', 'de', NULL, 'C', '["ROLE_USER"]', '2022-02-04 19:25:07', '2022-02-04 23:07:57', NULL, NULL, NULL, NULL, NULL, NULL), + ('d36197370d44', 'sed@example.com', 'Clifford', 'Beier', 'sed', 'en', NULL, NULL, '["ROLE_USER"]', '2022-01-23 16:19:10', '2022-01-23 16:19:10', NULL, NULL, NULL, NULL, NULL, NULL), + ('e5433660140b', 'sit@example.com', 'Wanda', 'Koelpin', 'sit', 'en', NULL, NULL, '["ROLE_USER"]', '2022-01-23 16:19:10', '2022-01-23 16:19:10', NULL, NULL, NULL, NULL, NULL, NULL), + ('4bc441dc4b29', 'luke@skywalker.com', 'Luke', 'Skywalker', NULL, 'en', NULL, NULL, '["ROLE_USER"]', '2023-08-12 16:43:34', '2023-08-12 16:43:34', NULL, NULL, NULL, NULL, NULL, NULL), + ('5552108bf43e', 'john@wick.com', 'John', 'Wick', NULL, 'en', NULL, NULL, '["ROLE_USER"]', '2023-08-12 16:46:43', '2023-08-12 16:46:43', NULL, NULL, NULL, NULL, NULL, NULL), + ('abfcbcbd4566', 'clark@kent.com', 'Clark', 'Kent', NULL, 'en', NULL, NULL, '["ROLE_USER"]', '2023-08-12 16:49:27', '2023-08-12 16:49:27', NULL, NULL, NULL, NULL, NULL, NULL), + ('3f3fa9319dd2', 'bruce@wayne.com', 'Bruce', 'Wayne', NULL, 'en', NULL, NULL, '["ROLE_USER"]', '2023-08-12 16:55:28', '2023-08-12 16:55:28', NULL, NULL, NULL, NULL, NULL, NULL), + ('51245d0e2ad4', 'felicity@smoak.com', 'Felicity', 'Smoak', NULL, 'en', NULL, NULL, '["ROLE_USER"]', '2023-08-12 16:59:38', '2023-08-12 16:59:38', NULL, NULL, NULL, NULL, NULL, NULL); @@ -988,36 +988,36 @@ INSERT INTO public.activity (id, title, location, campid, categoryid, rootconten -INSERT INTO public.camp_collaboration (id, inviteemail, invitekeyhash, status, role, createtime, updatetime, userid, campid) VALUES - ('237abf0bd057', 'e.mail2@test.com', 'myInviteKey2', 'invited', 'member', '2022-01-23 16:19:10', '2022-01-23 16:19:10', NULL, '6430aecc5422'), - ('0e524d43e799', NULL, NULL, 'established', 'manager', '2022-01-23 16:19:10', '2022-01-23 16:19:10', 'e7b00084dabf', '6430aecc5422'), - ('2c313fa367b3', NULL, NULL, 'established', 'manager', '2022-01-23 16:19:10', '2022-01-23 16:19:10', '3b41dca5c568', 'e5027d852487'), - ('2b3cf1ce6341', 'x@z.com', 'd12ntPWBK0qmPxfMGg/QRWh98XE=', 'invited', 'member', '2023-08-08 08:03:06', '2023-08-08 08:03:06', NULL, '3c79b99ab424'), - ('dba39edd9910', NULL, NULL, 'established', 'member', '2023-08-08 07:59:24', '2023-08-08 08:11:28', 'bee7cf5b3871', '3c79b99ab424'), - ('c88fd78c90ea', NULL, NULL, 'established', 'manager', '2023-08-08 07:58:53', '2023-08-08 08:12:12', 'caeba9f7e728', '3c79b99ab424'), - ('5111b2972441', 'inactive@test.com', 'Vazgl+0DsuUKrcTezKxA+KmBgOA=', 'inactive', 'member', '2023-08-08 07:59:53', '2023-08-08 09:18:22', NULL, '3c79b99ab424'), - ('68694999fa8d', NULL, NULL, 'established', 'manager', '2023-08-08 09:22:58', '2023-08-08 09:22:58', '9145944210a7', '6973c230d6b1'), - ('3229d273decd', NULL, '5eO+mQd6F+AQwCITx5mV8dX9/6U=', 'invited', 'manager', '2023-08-08 09:38:27', '2023-08-08 09:43:11', '130684395770', '6973c230d6b1'), - ('7cae64a7800c', 'e.mail@test.com', '7FUv2lrEcRjMbW/Nb1mTkBaAPOs=', 'invited', 'member', '2023-08-08 09:37:38', '2023-08-08 09:43:21', NULL, '6973c230d6b1'), - ('878426563205', NULL, NULL, 'established', 'guest', '2023-08-08 09:36:41', '2023-08-08 09:43:52', '48f00685a292', '6973c230d6b1'), - ('d27ca1d0e6e4', NULL, NULL, 'established', 'member', '2023-08-08 09:36:01', '2023-08-08 09:44:19', 'bae69a1c9fcc', '6973c230d6b1'), - ('c463d2a19847', NULL, NULL, 'established', 'member', '2023-08-08 09:38:01', '2023-08-08 09:44:50', 'caeba9f7e728', '6973c230d6b1'), - ('763c0d181b63', NULL, NULL, 'established', 'manager', '2023-08-08 09:37:16', '2023-08-08 09:45:21', 'bee7cf5b3871', '6973c230d6b1'), - ('b7d93b2fa1be', NULL, 'mLdsTtaGGptPYSZLUDgX8sAFO54=', 'established', 'member', '2023-08-12 19:10:49', '2023-08-12 19:10:49', 'a2f4f3879c85', '9c2447aefe38'), - ('ac1cd0bcbd69', NULL, 'V30YTcBqBqs5xS7HrFM4ODRrzbw=', 'established', 'member', '2023-08-12 19:10:28', '2023-08-12 19:10:28', 'a3d9d86dc23b', '9c2447aefe38'), - ('8be6d2f6f7dc', NULL, 'ru6jsdD9fODk8+p8wmI909rJPkQ=', 'established', 'manager', '2023-08-12 19:11:03', '2023-08-12 19:11:03', '566aea2c2759', '9c2447aefe38'), - ('0e26982c9895', NULL, 'ao9OQRgXWBpCVaD4lGQaUFzvKwI=', 'established', 'member', '2023-08-12 19:10:14', '2023-08-12 19:10:14', 'dac7116e02ed', '9c2447aefe38'), - ('b2f127cb410f', NULL, NULL, 'established', 'manager', '2023-08-13 06:32:29', '2023-08-13 06:32:29', 'dac7116e02ed', '0969e3c95dfc'), - ('d806a59915f8', NULL, 'SuP47raE2s/2hGLsdBiO/icIU0E=', 'established', 'member', '2023-08-13 10:29:25', '2023-08-13 10:29:25', 'a3d9d86dc23b', '0969e3c95dfc'), - ('7db6a9ffc210', NULL, 'yyXTSd6kGh7kNENlcGDeYtq4ftQ=', 'established', 'guest', '2023-08-13 10:29:47', '2023-08-13 10:29:47', 'a2f4f3879c85', '0969e3c95dfc'), - ('60a470e1aff6', NULL, 'uGXywHjal9lK+rcdtRBcXh8y5qA=', 'established', 'manager', '2023-08-13 10:29:55', '2023-08-13 10:29:55', '566aea2c2759', '0969e3c95dfc'), - ('d1c0a4522283', NULL, NULL, 'established', 'manager', '2023-09-29 23:24:38', '2023-09-29 23:24:38', 'dac7116e02ed', '70ca971c992f'), - ('5b24ce470d9f', NULL, 'XisYzAgXUozfJA1M/y39ow8t5Vw=', 'established', 'member', '2023-09-29 23:27:54', '2023-09-29 23:27:54', 'a3d9d86dc23b', '70ca971c992f'), - ('46d14f7c072c', NULL, '4KCuIMWvkGVAjSBtAnG5QcesOrI=', 'established', 'manager', '2023-09-29 23:41:30', '2023-09-29 23:41:30', 'a2f4f3879c85', '70ca971c992f'), - ('b0bdb7202a9d', NULL, NULL, 'established', 'manager', '2023-08-08 07:53:12', '2023-08-08 07:53:12', '9145944210a7', '3c79b99ab424'), - ('b00054c3c03e', NULL, 'XC/b4erYO0iZZTBEXOi3n/4AH9w=', 'established', 'guest', '2023-08-13 10:29:08', '2023-08-13 10:29:08', '9145944210a7', '0969e3c95dfc'), - ('10d8f02ce5b4', NULL, 'n1MKxMj1RWkrcSmNfHdjUxKV3QY=', 'established', 'guest', '2023-09-29 23:25:49', '2023-09-29 23:25:49', '9145944210a7', '70ca971c992f'), - ('b32db30637c8', NULL, 'AC/b4erYO0iZZTBEXOi3n/4AH9w=', 'invited', 'manager', '2023-08-12 17:41:55', '2023-08-12 17:41:55', '9145944210a7', '9c2447aefe38'); +INSERT INTO public.camp_collaboration (id, inviteemail, invitekeyhash, status, color, abbreviation, role, createtime, updatetime, userid, campid) VALUES + ('237abf0bd057', 'e.mail2@test.com', 'myInviteKey2', 'invited', NULL, NULL, 'member', '2022-01-23 16:19:10', '2022-01-23 16:19:10', NULL, '6430aecc5422'), + ('0e524d43e799', NULL, NULL, 'established', NULL, NULL, 'manager', '2022-01-23 16:19:10', '2022-01-23 16:19:10', 'e7b00084dabf', '6430aecc5422'), + ('2c313fa367b3', NULL, NULL, 'established', NULL, NULL, 'manager', '2022-01-23 16:19:10', '2022-01-23 16:19:10', '3b41dca5c568', 'e5027d852487'), + ('2b3cf1ce6341', 'x@z.com', 'd12ntPWBK0qmPxfMGg/QRWh98XE=', 'invited', NULL, NULL, 'member', '2023-08-08 08:03:06', '2023-08-08 08:03:06', NULL, '3c79b99ab424'), + ('dba39edd9910', NULL, NULL, 'established', NULL, NULL, 'member', '2023-08-08 07:59:24', '2023-08-08 08:11:28', 'bee7cf5b3871', '3c79b99ab424'), + ('c88fd78c90ea', NULL, NULL, 'established', NULL, NULL, 'manager', '2023-08-08 07:58:53', '2023-08-08 08:12:12', 'caeba9f7e728', '3c79b99ab424'), + ('5111b2972441', 'inactive@test.com', 'Vazgl+0DsuUKrcTezKxA+KmBgOA=', 'inactive', NULL, NULL, 'member', '2023-08-08 07:59:53', '2023-08-08 09:18:22', NULL, '3c79b99ab424'), + ('68694999fa8d', NULL, NULL, 'established', NULL, NULL, 'manager', '2023-08-08 09:22:58', '2023-08-08 09:22:58', '9145944210a7', '6973c230d6b1'), + ('3229d273decd', NULL, '5eO+mQd6F+AQwCITx5mV8dX9/6U=', 'invited', NULL, NULL, 'manager', '2023-08-08 09:38:27', '2023-08-08 09:43:11', '130684395770', '6973c230d6b1'), + ('7cae64a7800c', 'e.mail@test.com', '7FUv2lrEcRjMbW/Nb1mTkBaAPOs=', 'invited', NULL, NULL, 'member', '2023-08-08 09:37:38', '2023-08-08 09:43:21', NULL, '6973c230d6b1'), + ('878426563205', NULL, NULL, 'established', NULL, NULL, 'guest', '2023-08-08 09:36:41', '2023-08-08 09:43:52', '48f00685a292', '6973c230d6b1'), + ('d27ca1d0e6e4', NULL, NULL, 'established', '#ff0080', '🐈‍⬛', 'member', '2023-08-08 09:36:01', '2023-08-08 09:44:19', 'bae69a1c9fcc', '6973c230d6b1'), + ('c463d2a19847', NULL, NULL, 'established', NULL, 'Ca', 'member', '2023-08-08 09:38:01', '2023-08-08 09:44:50', 'caeba9f7e728', '6973c230d6b1'), + ('763c0d181b63', NULL, NULL, 'established', NULL, NULL, 'manager', '2023-08-08 09:37:16', '2023-08-08 09:45:21', 'bee7cf5b3871', '6973c230d6b1'), + ('b7d93b2fa1be', NULL, 'mLdsTtaGGptPYSZLUDgX8sAFO54=', 'established', NULL, NULL, 'member', '2023-08-12 19:10:49', '2023-08-12 19:10:49', 'a2f4f3879c85', '9c2447aefe38'), + ('ac1cd0bcbd69', NULL, 'V30YTcBqBqs5xS7HrFM4ODRrzbw=', 'established', NULL, NULL, 'member', '2023-08-12 19:10:28', '2023-08-12 19:10:28', 'a3d9d86dc23b', '9c2447aefe38'), + ('8be6d2f6f7dc', NULL, 'ru6jsdD9fODk8+p8wmI909rJPkQ=', 'established', NULL, NULL, 'manager', '2023-08-12 19:11:03', '2023-08-12 19:11:03', '566aea2c2759', '9c2447aefe38'), + ('0e26982c9895', NULL, 'ao9OQRgXWBpCVaD4lGQaUFzvKwI=', 'established', NULL, NULL, 'member', '2023-08-12 19:10:14', '2023-08-12 19:10:14', 'dac7116e02ed', '9c2447aefe38'), + ('b2f127cb410f', NULL, NULL, 'established', NULL, NULL, 'manager', '2023-08-13 06:32:29', '2023-08-13 06:32:29', 'dac7116e02ed', '0969e3c95dfc'), + ('d806a59915f8', NULL, 'SuP47raE2s/2hGLsdBiO/icIU0E=', 'established', NULL, NULL, 'member', '2023-08-13 10:29:25', '2023-08-13 10:29:25', 'a3d9d86dc23b', '0969e3c95dfc'), + ('7db6a9ffc210', NULL, 'yyXTSd6kGh7kNENlcGDeYtq4ftQ=', 'established', NULL, NULL, 'guest', '2023-08-13 10:29:47', '2023-08-13 10:29:47', 'a2f4f3879c85', '0969e3c95dfc'), + ('60a470e1aff6', NULL, 'uGXywHjal9lK+rcdtRBcXh8y5qA=', 'established', NULL, NULL, 'manager', '2023-08-13 10:29:55', '2023-08-13 10:29:55', '566aea2c2759', '0969e3c95dfc'), + ('d1c0a4522283', NULL, NULL, 'established', NULL, NULL, 'manager', '2023-09-29 23:24:38', '2023-09-29 23:24:38', 'dac7116e02ed', '70ca971c992f'), + ('5b24ce470d9f', NULL, 'XisYzAgXUozfJA1M/y39ow8t5Vw=', 'established', NULL, NULL, 'member', '2023-09-29 23:27:54', '2023-09-29 23:27:54', 'a3d9d86dc23b', '70ca971c992f'), + ('46d14f7c072c', NULL, '4KCuIMWvkGVAjSBtAnG5QcesOrI=', 'established', NULL, NULL, 'manager', '2023-09-29 23:41:30', '2023-09-29 23:41:30', 'a2f4f3879c85', '70ca971c992f'), + ('b0bdb7202a9d', NULL, NULL, 'established', NULL, NULL, 'manager', '2023-08-08 07:53:12', '2023-08-08 07:53:12', '9145944210a7', '3c79b99ab424'), + ('b00054c3c03e', NULL, 'XC/b4erYO0iZZTBEXOi3n/4AH9w=', 'established', NULL, NULL, 'guest', '2023-08-13 10:29:08', '2023-08-13 10:29:08', '9145944210a7', '0969e3c95dfc'), + ('10d8f02ce5b4', NULL, 'n1MKxMj1RWkrcSmNfHdjUxKV3QY=', 'established', NULL, NULL, 'guest', '2023-09-29 23:25:49', '2023-09-29 23:25:49', '9145944210a7', '70ca971c992f'), + ('b32db30637c8', NULL, 'AC/b4erYO0iZZTBEXOi3n/4AH9w=', 'invited', NULL, NULL, 'manager', '2023-08-12 17:41:55', '2023-08-12 17:41:55', '9145944210a7', '9c2447aefe38'); diff --git a/api/migrations/schema/Version20240621060047.php b/api/migrations/schema/Version20240621060047.php new file mode 100644 index 0000000000..4f04aa3587 --- /dev/null +++ b/api/migrations/schema/Version20240621060047.php @@ -0,0 +1,27 @@ +addSql('ALTER TABLE profile ADD color VARCHAR(8) DEFAULT NULL'); + $this->addSql('ALTER TABLE profile ADD abbreviation TEXT DEFAULT NULL'); + } + + public function down(Schema $schema): void { + $this->addSql('ALTER TABLE profile DROP color'); + $this->addSql('ALTER TABLE profile DROP abbreviation'); + } +} diff --git a/api/src/Entity/Profile.php b/api/src/Entity/Profile.php index 536f67adba..92a6bb3d5b 100644 --- a/api/src/Entity/Profile.php +++ b/api/src/Entity/Profile.php @@ -172,6 +172,26 @@ class Profile extends BaseEntity { #[ORM\Column(type: 'string', length: 20, nullable: true)] public ?string $language = null; + /** + * The default color of the avatar as a hex color string. + */ + #[InputFilter\Trim] + #[Assert\Regex(pattern: '/^#[0-9a-zA-Z]{6}$/')] + #[ApiProperty(example: '#4DBB52')] + #[Groups(['read', 'write'])] + #[ORM\Column(type: 'string', length: 8, nullable: true)] + public ?string $color = null; + + /** + * The default abbreviation in the avatar. + */ + #[InputFilter\Trim] + #[Assert\Length(max: 2, countUnit: Assert\Length::COUNT_GRAPHEMES)] + #[ApiProperty(example: 'AB')] + #[Groups(['read', 'write'])] + #[ORM\Column(type: 'text', nullable: true)] + public ?string $abbreviation = null; + /** * The technical roles that this person has in the eCamp application. */ diff --git a/api/src/Entity/User.php b/api/src/Entity/User.php index cf3d68d9ba..40c960fb46 100644 --- a/api/src/Entity/User.php +++ b/api/src/Entity/User.php @@ -182,6 +182,24 @@ public function getDisplayName(): ?string { return $this->profile->getDisplayName(); } + /** + * A displayable name of the user. + */ + #[ApiProperty(example: '#ff0000')] + #[Groups(['read'])] + public function getColor(): ?string { + return $this->profile->color; + } + + /** + * A displayable name of the user. + */ + #[ApiProperty(example: 'AB')] + #[Groups(['read'])] + public function getAbbreviation(): ?string { + return $this->profile->abbreviation; + } + #[ApiProperty] #[SerializedName('profile')] #[Groups(['read'])] diff --git a/api/tests/Api/CampCollaborations/UpdateCampCollaborationTest.php b/api/tests/Api/CampCollaborations/UpdateCampCollaborationTest.php index c427641782..51b89f16a2 100644 --- a/api/tests/Api/CampCollaborations/UpdateCampCollaborationTest.php +++ b/api/tests/Api/CampCollaborations/UpdateCampCollaborationTest.php @@ -265,6 +265,53 @@ public function testPatchStatusFromInactiveToInvitedSendsInviteEmail() { $this->assertEmailCount(1); } + public function testPatchCampCollaborationValidatesInvalidColor() { + $campCollaboration = static::getFixture('campCollaboration3guest'); + static::createClientWithCredentials()->request('PATCH', '/camp_collaborations/'.$campCollaboration->getId(), ['json' => [ + 'color' => 'red', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + + $this->assertResponseStatusCodeSame(422); + $this->assertJsonContains([ + 'violations' => [ + [ + 'propertyPath' => 'color', + 'message' => 'This value is not valid.', + ], + ], + ]); + } + + #[DataProvider('invalidAbbreviations')] + public function testPatchCampCollaborationValidatesInvalidAbbreviation($abbreviation) { + $campCollaboration = static::getFixture('campCollaboration3guest'); + static::createClientWithCredentials()->request('PATCH', '/camp_collaborations/'.$campCollaboration->getId(), ['json' => [ + 'abbreviation' => $abbreviation, + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + + $this->assertResponseStatusCodeSame(422); + $this->assertJsonContains([ + 'violations' => [ + [ + 'propertyPath' => 'abbreviation', + 'message' => 'This value is too long. It should have 2 characters or less.', + ], + ], + ]); + } + + #[DataProvider('validAbbreviations')] + public function testPatchCampCollaborationValidatesValidAbbreviation($abbreviation) { + $campCollaboration = static::getFixture('campCollaboration5inactive'); + static::createClientWithCredentials()->request('PATCH', '/camp_collaborations/'.$campCollaboration->getId(), ['json' => [ + 'abbreviation' => $abbreviation, + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'abbreviation' => $abbreviation, + ]); + } + #[DataProvider('usersWhichCanEditCampCollaborations')] public function testRejectsPatchStatusFromEstablishedToInactiveIfNoManagerWouldBeInCamp(string $userFixtureName) { $campCollaboration = static::getFixture('campCollaboration1camp2manager'); @@ -355,4 +402,23 @@ public function testRejectsPatchRoleToGuestIfNoManagerWouldBeInCamp(string $user public static function usersWhichCanEditCampCollaborations(): array { return ['user1manager' => ['user1manager'], 'user2member' => ['user2member']]; } + + public static function invalidAbbreviations(): array { + return [ + ['ABC'], + ['D3C'], + ['🧑🏿‍🚀🙋🏼‍♀️😊'], + ]; + } + + public static function validAbbreviations(): array { + return [ + ['A'], + ['33'], + ['X4'], + ['✅😊'], + ['🧑🏿‍🚀🧑🏼‍🔧'], + ['⚜️'], + ]; + } } diff --git a/api/tests/Api/Profiles/ReadProfileTest.php b/api/tests/Api/Profiles/ReadProfileTest.php index 3c9528d388..187afa004b 100644 --- a/api/tests/Api/Profiles/ReadProfileTest.php +++ b/api/tests/Api/Profiles/ReadProfileTest.php @@ -34,6 +34,8 @@ public function testGetSingleProfileIsAllowedForSelf() { 'nickname' => $profile->nickname, 'language' => $profile->language, 'legalName' => $profile->getLegalName(), + 'abbreviation' => $profile->abbreviation, + 'color' => $profile->color, '_links' => [ 'self' => [ 'href' => '/profiles/'.$profile->getId(), @@ -58,6 +60,8 @@ public function testGetSingleProfileIsAllowedForRelatedUser() { 'nickname' => $profile->nickname, 'language' => $profile->language, 'legalName' => $profile->getLegalName(), + 'abbreviation' => $profile->abbreviation, + 'color' => $profile->color, '_links' => [ 'self' => [ 'href' => '/profiles/'.$profile->getId(), @@ -84,6 +88,8 @@ public function testGetSingleProfileIsAllowedForSelfIfSelfHasNoCampCollaboration 'nickname' => $profile->nickname, 'language' => $profile->language, 'legalName' => $profile->getLegalName(), + 'abbreviation' => $profile->abbreviation, + 'color' => $profile->color, '_links' => [ 'self' => [ 'href' => '/profiles/'.$profile->getId(), diff --git a/api/tests/Api/Profiles/UpdateProfileTest.php b/api/tests/Api/Profiles/UpdateProfileTest.php index 2270e3da14..6da0619cb3 100644 --- a/api/tests/Api/Profiles/UpdateProfileTest.php +++ b/api/tests/Api/Profiles/UpdateProfileTest.php @@ -204,6 +204,53 @@ public function testPatchProfileTrimsLanguage() { ]); } + public function testPatchProfileValidatesInvalidColor() { + $profile = static::getFixture('profile1manager'); + static::createClientWithCredentials()->request('PATCH', '/profiles/'.$profile->getId(), ['json' => [ + 'color' => 'red', + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + + $this->assertResponseStatusCodeSame(422); + $this->assertJsonContains([ + 'violations' => [ + [ + 'propertyPath' => 'color', + 'message' => 'This value is not valid.', + ], + ], + ]); + } + + #[DataProvider('invalidAbbreviations')] + public function testPatchCampCollaborationValidatesInvalidAbbreviation($abbreviation) { + $profile = static::getFixture('profile1manager'); + static::createClientWithCredentials()->request('PATCH', '/profiles/'.$profile->getId(), ['json' => [ + 'abbreviation' => $abbreviation, + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + + $this->assertResponseStatusCodeSame(422); + $this->assertJsonContains([ + 'violations' => [ + [ + 'propertyPath' => 'abbreviation', + 'message' => 'This value is too long. It should have 2 characters or less.', + ], + ], + ]); + } + + #[DataProvider('validAbbreviations')] + public function testPatchCampCollaborationValidatesValidAbbreviation($abbreviation) { + $profile = static::getFixture('profile1manager'); + static::createClientWithCredentials()->request('PATCH', '/profiles/'.$profile->getId(), ['json' => [ + 'abbreviation' => $abbreviation, + ], 'headers' => ['Content-Type' => 'application/merge-patch+json']]); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'abbreviation' => $abbreviation, + ]); + } + public function testPatchProfileValidatesInvalidLanguage() { $profile = static::getFixture('profile1manager'); static::createClientWithCredentials()->request('PATCH', '/profiles/'.$profile->getId(), ['json' => [ @@ -270,4 +317,22 @@ public static function notWriteableProfileProperties(): array { 'user' => ['user'], ]; } + + public static function invalidAbbreviations(): array { + return [ + ['ABC'], + ['D3C'], + ['🧑🏿‍🚀🙋🏼‍♀️😊'], + ]; + } + + public static function validAbbreviations(): array { + return [ + ['AB'], + ['33'], + ['X4'], + ['✅😊'], + ['🧑🏿‍🚀🧑🏼‍🔧'], + ]; + } } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set camp_collaborations__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set camp_collaborations__1.json index 44110ee299..e2ff2cbcd4 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set camp_collaborations__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set camp_collaborations__1.json @@ -126,6 +126,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } @@ -206,6 +208,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } @@ -286,6 +290,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } @@ -366,6 +372,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } @@ -446,6 +454,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } @@ -526,6 +536,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } @@ -606,6 +618,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } @@ -686,6 +700,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } @@ -766,6 +782,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } @@ -846,6 +864,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } @@ -926,6 +946,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set profiles__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set profiles__1.json index 38f9b870e0..5396b1b4c8 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set profiles__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set profiles__1.json @@ -10,6 +10,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "email": "escaped_value", "firstname": "escaped_value", "id": "escaped_value", @@ -27,6 +29,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "email": "escaped_value", "firstname": "escaped_value", "id": "escaped_value", @@ -44,6 +48,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "email": "escaped_value", "firstname": "escaped_value", "id": "escaped_value", @@ -61,6 +67,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "email": "escaped_value", "firstname": "escaped_value", "id": "escaped_value", @@ -78,6 +86,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "email": "escaped_value", "firstname": "escaped_value", "id": "escaped_value", diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camp_collaborations__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camp_collaborations__1.json index b3c5bec5b1..c84f1c605b 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camp_collaborations__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camp_collaborations__1.json @@ -56,6 +56,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camps__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camps__1.json index 6eb4f736b4..9afc1cba01 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camps__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camps__1.json @@ -32,6 +32,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } @@ -65,6 +67,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } @@ -98,6 +102,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } @@ -131,6 +137,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } @@ -164,6 +172,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "displayName": "escaped_value", "id": "escaped_value" } diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set profiles__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set profiles__1.json index 835aeeec69..01fb732b50 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set profiles__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set profiles__1.json @@ -7,6 +7,8 @@ "href": "escaped_value" } }, + "abbreviation": "escaped_value", + "color": "escaped_value", "email": "escaped_value", "firstname": "escaped_value", "id": "escaped_value", diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml index a68fa412dd..226c6c1573 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml @@ -15488,6 +15488,21 @@ components: The properties available to related eCamp users are here. Related means that they were or are collaborators in the same camp. properties: + abbreviation: + description: 'The default abbreviation in the avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string + color: + description: 'The default color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string email: description: 'Unique email of the user.' example: bi-pi@example.com @@ -15564,6 +15579,21 @@ components: deprecated: false description: '' properties: + abbreviation: + description: 'The default abbreviation in the avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string + color: + description: 'The default color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string email: description: 'Unique email of the user.' example: bi-pi@example.com @@ -15643,6 +15673,21 @@ components: The properties available to related eCamp users are here. Related means that they were or are collaborators in the same camp. properties: + abbreviation: + description: 'The default abbreviation in the avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string + color: + description: 'The default color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string email: description: 'Unique email of the user.' example: bi-pi@example.com @@ -15709,6 +15754,21 @@ components: The properties available to related eCamp users are here. Related means that they were or are collaborators in the same camp. properties: + abbreviation: + description: 'The default abbreviation in the avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string + color: + description: 'The default color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string firstname: description: "The user's (optional) first name." example: Robert @@ -15781,6 +15841,17 @@ components: maxLength: 16 readOnly: true type: string + abbreviation: + description: 'The default abbreviation in the avatar.' + example: AB + maxLength: 2 + type: ['null', string] + color: + description: 'The default color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: ['null', string] email: description: 'Unique email of the user.' example: bi-pi@example.com @@ -15871,6 +15942,21 @@ components: type: string type: object type: object + abbreviation: + description: 'The default abbreviation in the avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string + color: + description: 'The default color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string email: description: 'Unique email of the user.' example: bi-pi@example.com @@ -15956,6 +16042,21 @@ components: type: string type: object type: object + abbreviation: + description: 'The default abbreviation in the avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string + color: + description: 'The default color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string email: description: 'Unique email of the user.' example: bi-pi@example.com @@ -16044,6 +16145,21 @@ components: type: string type: object type: object + abbreviation: + description: 'The default abbreviation in the avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string + color: + description: 'The default color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string email: description: 'Unique email of the user.' example: bi-pi@example.com @@ -16133,6 +16249,21 @@ components: '@type': readOnly: true type: string + abbreviation: + description: 'The default abbreviation in the avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string + color: + description: 'The default color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string email: description: 'Unique email of the user.' example: bi-pi@example.com @@ -16232,6 +16363,21 @@ components: '@type': readOnly: true type: string + abbreviation: + description: 'The default abbreviation in the avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string + color: + description: 'The default color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string email: description: 'Unique email of the user.' example: bi-pi@example.com @@ -16311,6 +16457,21 @@ components: The properties available to related eCamp users are here. Related means that they were or are collaborators in the same camp. properties: + abbreviation: + description: 'The default abbreviation in the avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string + color: + description: 'The default color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string email: description: 'Unique email of the user.' example: bi-pi@example.com @@ -20218,6 +20379,20 @@ components: A person using eCamp. The properties available for all other eCamp users are here. properties: + abbreviation: + description: 'A displayable name of the user.' + example: AB + readOnly: true + type: + - 'null' + - string + color: + description: 'A displayable name of the user.' + example: '#ff0000' + readOnly: true + type: + - 'null' + - string displayName: description: 'A displayable name of the user.' example: 'Robert Baden-Powell' @@ -20245,6 +20420,20 @@ components: deprecated: false description: '' properties: + abbreviation: + description: 'A displayable name of the user.' + example: AB + readOnly: true + type: + - 'null' + - string + color: + description: 'A displayable name of the user.' + example: '#ff0000' + readOnly: true + type: + - 'null' + - string displayName: description: 'A displayable name of the user.' example: 'Robert Baden-Powell' @@ -20272,6 +20461,20 @@ components: deprecated: false description: '' properties: + abbreviation: + description: 'A displayable name of the user.' + example: AB + readOnly: true + type: + - 'null' + - string + color: + description: 'A displayable name of the user.' + example: '#ff0000' + readOnly: true + type: + - 'null' + - string displayName: description: 'A displayable name of the user.' example: 'Robert Baden-Powell' @@ -20301,6 +20504,20 @@ components: A person using eCamp. The properties available for all other eCamp users are here. properties: + abbreviation: + description: 'A displayable name of the user.' + example: AB + readOnly: true + type: + - 'null' + - string + color: + description: 'A displayable name of the user.' + example: '#ff0000' + readOnly: true + type: + - 'null' + - string displayName: description: 'A displayable name of the user.' example: 'Robert Baden-Powell' @@ -20391,10 +20608,20 @@ components: maxLength: 16 readOnly: true type: string + abbreviation: + description: 'A displayable name of the user.' + example: AB + readOnly: true + type: ['null', string] activationKey: description: 'User-Input for activation.' type: ['null', string] writeOnly: true + color: + description: 'A displayable name of the user.' + example: '#ff0000' + readOnly: true + type: ['null', string] displayName: description: 'A displayable name of the user.' example: 'Robert Baden-Powell' @@ -20454,6 +20681,20 @@ components: type: string type: object type: object + abbreviation: + description: 'A displayable name of the user.' + example: AB + readOnly: true + type: + - 'null' + - string + color: + description: 'A displayable name of the user.' + example: '#ff0000' + readOnly: true + type: + - 'null' + - string displayName: description: 'A displayable name of the user.' example: 'Robert Baden-Powell' @@ -20490,6 +20731,20 @@ components: type: string type: object type: object + abbreviation: + description: 'A displayable name of the user.' + example: AB + readOnly: true + type: + - 'null' + - string + color: + description: 'A displayable name of the user.' + example: '#ff0000' + readOnly: true + type: + - 'null' + - string displayName: description: 'A displayable name of the user.' example: 'Robert Baden-Powell' @@ -20526,6 +20781,20 @@ components: type: string type: object type: object + abbreviation: + description: 'A displayable name of the user.' + example: AB + readOnly: true + type: + - 'null' + - string + color: + description: 'A displayable name of the user.' + example: '#ff0000' + readOnly: true + type: + - 'null' + - string displayName: description: 'A displayable name of the user.' example: 'Robert Baden-Powell' @@ -20564,6 +20833,20 @@ components: type: string type: object type: object + abbreviation: + description: 'A displayable name of the user.' + example: AB + readOnly: true + type: + - 'null' + - string + color: + description: 'A displayable name of the user.' + example: '#ff0000' + readOnly: true + type: + - 'null' + - string displayName: description: 'A displayable name of the user.' example: 'Robert Baden-Powell' @@ -20657,6 +20940,20 @@ components: '@type': readOnly: true type: string + abbreviation: + description: 'A displayable name of the user.' + example: AB + readOnly: true + type: + - 'null' + - string + color: + description: 'A displayable name of the user.' + example: '#ff0000' + readOnly: true + type: + - 'null' + - string displayName: description: 'A displayable name of the user.' example: 'Robert Baden-Powell' @@ -20707,6 +21004,20 @@ components: '@type': readOnly: true type: string + abbreviation: + description: 'A displayable name of the user.' + example: AB + readOnly: true + type: + - 'null' + - string + color: + description: 'A displayable name of the user.' + example: '#ff0000' + readOnly: true + type: + - 'null' + - string displayName: description: 'A displayable name of the user.' example: 'Robert Baden-Powell' @@ -20757,6 +21068,20 @@ components: '@type': readOnly: true type: string + abbreviation: + description: 'A displayable name of the user.' + example: AB + readOnly: true + type: + - 'null' + - string + color: + description: 'A displayable name of the user.' + example: '#ff0000' + readOnly: true + type: + - 'null' + - string displayName: description: 'A displayable name of the user.' example: 'Robert Baden-Powell' @@ -20809,6 +21134,20 @@ components: '@type': readOnly: true type: string + abbreviation: + description: 'A displayable name of the user.' + example: AB + readOnly: true + type: + - 'null' + - string + color: + description: 'A displayable name of the user.' + example: '#ff0000' + readOnly: true + type: + - 'null' + - string displayName: description: 'A displayable name of the user.' example: 'Robert Baden-Powell' diff --git a/api/tests/Api/Users/ReadUserTest.php b/api/tests/Api/Users/ReadUserTest.php index 23a1f308dd..9cde757a64 100644 --- a/api/tests/Api/Users/ReadUserTest.php +++ b/api/tests/Api/Users/ReadUserTest.php @@ -57,6 +57,8 @@ public function testGetSingleUserIsAllowedForSelf() { $this->assertJsonEquals([ 'id' => $user->getId(), 'displayName' => $user->getDisplayName(), + 'abbreviation' => $user->getAbbreviation(), + 'color' => $user->getColor(), '_links' => [ 'self' => [ 'href' => $this->getIriFor('user1manager'), @@ -76,6 +78,8 @@ public function testGetSingleUserIsAllowedForRelatedUser() { $this->assertJsonEquals([ 'id' => $user->getId(), 'displayName' => $user->getDisplayName(), + 'abbreviation' => $user->getAbbreviation(), + 'color' => $user->getColor(), '_links' => [ 'self' => [ 'href' => $this->getIriFor('user2member'), diff --git a/common/helpers/__tests__/campCollaborationInitials.spec.js b/common/helpers/__tests__/campCollaborationInitials.spec.js index 379b88dccc..5693d6c486 100644 --- a/common/helpers/__tests__/campCollaborationInitials.spec.js +++ b/common/helpers/__tests__/campCollaborationInitials.spec.js @@ -17,6 +17,12 @@ describe('campCollaborationInitials', () => { [{ inviteEmail: 'ecamp@ecamp3.ch', status: 'inactive', user: null }, 'EC'], [{ abbreviation: 'B', inviteEmail: null, user: null }, 'B'], [{ abbreviation: 'AA', user: () => ({ displayName: 'Bi-Pi' }) }, 'AA'], + [ + { abbreviation: 'AA', user: () => ({ abbreviation: 'CC', displayName: 'Bi-Pi' }) }, + 'AA', + ], + [{ user: () => ({ abbreviation: 'QQ' }) }, 'QQ'], + [{ user: () => ({ abbreviation: 'QQ', displayName: 'Bi-Pi' }) }, 'QQ'], ])('maps %o to "%s"', (input, expected) => { expect(campCollaborationInitials(input)).toEqual(expected) }) diff --git a/common/helpers/__tests__/colors.spec.js b/common/helpers/__tests__/colors.spec.js index 8650b44257..0af33c5db4 100644 --- a/common/helpers/__tests__/colors.spec.js +++ b/common/helpers/__tests__/colors.spec.js @@ -24,6 +24,8 @@ describe('userColor', () => { [{ name: 'test' }, '#4d4d4d'], [{ _meta: {} }, '#4d4d4d'], [{ _meta: { loading: true } }, '#4d4d4d'], + [{ color: '#abcdef' }, '#abcdef'], + [{ color: '#abcdef', _meta: { loading: true } }, '#4d4d4d'], ])('maps %p to %p', (input, expected) => { expect(userColor(input)).toEqual(expected) }) @@ -40,6 +42,19 @@ describe('campCollaborationColor', () => { [{ _meta: { loading: true } }, '#4d4d4d'], [{ id: 'fffffff', user: () => ({ _meta: { loading: true } }) }, '#4d4d4d'], [{ color: '#ECA110' }, '#ECA110'], + [{ color: '#ECA110', _meta: { loading: true } }, '#4d4d4d'], + [ + { id: 'fffffff', user: () => ({ color: '#ECA110', _meta: { loading: true } }) }, + '#4d4d4d', + ], + [ + { + id: 'fffffff', + _meta: { loading: true }, + user: () => ({ color: '#ECA110', _meta: { loading: true } }), + }, + '#4d4d4d', + ], ])('maps %o to "%s"', (input, expected) => { expect(campCollaborationColor(input)).toEqual(expected) }) diff --git a/common/helpers/__tests__/userInitials.spec.js b/common/helpers/__tests__/userInitials.spec.js index 051ff74771..eff283e9f8 100644 --- a/common/helpers/__tests__/userInitials.spec.js +++ b/common/helpers/__tests__/userInitials.spec.js @@ -2,11 +2,15 @@ import userInitials from '../userInitials.js' describe('userInitials', () => { it.each([ + [{}, ''], + [null, ''], + [undefined, ''], [{ id: 'fffffff' }, ''], [{ displayName: 'test' }, 'TE'], [{ displayName: 'test', _meta: {} }, 'TE'], [{ _meta: { loading: true } }, ''], - ])('maps %p to %p', (input, expected) => { + [{ abbreviation: 'V3', displayName: 'test' }, 'V3'], + ])('maps %o to "%s"', (input, expected) => { expect(userInitials(input)).toEqual(expected) }) }) diff --git a/common/helpers/campCollaborationInitials.js b/common/helpers/campCollaborationInitials.js index 93438b3b75..3ca2823bf5 100644 --- a/common/helpers/campCollaborationInitials.js +++ b/common/helpers/campCollaborationInitials.js @@ -1,11 +1,24 @@ -import campCollaborationDisplayName from './campCollaborationDisplayName.js' +import userDisplayName from './userDisplayName.js' import initials from './initials.js' /** * Returns two characters to display for a camp collaboration based on its user */ export default function (campCollaboration) { - return campCollaboration?.abbreviation - ? campCollaboration.abbreviation - : initials(campCollaborationDisplayName(campCollaboration, null, false)) + if (!campCollaboration) { + return '' + } + + if (campCollaboration?.abbreviation) { + return campCollaboration.abbreviation + } + + if (typeof campCollaboration.user === 'function') { + if (campCollaboration.user().abbreviation) { + return campCollaboration.user().abbreviation + } + return initials(userDisplayName(campCollaboration.user())) + } + + return initials(campCollaboration.inviteEmail || '') } diff --git a/common/helpers/colors.js b/common/helpers/colors.js index 2b79dc1025..4e654108c2 100644 --- a/common/helpers/colors.js +++ b/common/helpers/colors.js @@ -41,8 +41,11 @@ function defaultColor() { /** * @returns {string} color for a user based on their id */ -function userColor(user) { - return idToColor(user.id, user._meta?.loading) +function userColor(user, inactive = user._meta?.loading) { + if (user.color && !inactive) { + return user.color + } + return idToColor(user.id, inactive) } /** @@ -53,21 +56,21 @@ function campCollaborationColor(campCollaboration) { return idToColor('', true) } - const loading = - campCollaboration._meta?.loading || - (typeof campCollaboration.user === 'function' && - campCollaboration.user()._meta?.loading) + const inactive = + campCollaboration._meta?.loading || campCollaboration.status === 'inactive' - if (campCollaboration?.color) { + if (campCollaboration?.color && !inactive) { return campCollaboration.color } - return idToColor( - typeof campCollaboration.user === 'function' - ? campCollaboration.user().id - : campCollaboration.id, - campCollaboration.status === 'inactive' || loading - ) + if (typeof campCollaboration.user === 'function') { + return userColor( + campCollaboration.user(), + inactive || campCollaboration.user()._meta?.loading + ) + } else { + return idToColor(campCollaboration.id, inactive) + } } export { contrastColor, defaultColor, userColor, campCollaborationColor, idToColor } diff --git a/common/helpers/userInitials.js b/common/helpers/userInitials.js index 4c6d5672c8..cdb6d9e225 100644 --- a/common/helpers/userInitials.js +++ b/common/helpers/userInitials.js @@ -5,5 +5,11 @@ import initials from './initials.js' * Returns two characters to display for a user */ export default function (user) { + if (!user) { + return '' + } + if (user.abbreviation) { + return user.abbreviation + } return initials(userDisplayName(user)) } diff --git a/common/locales/de.json b/common/locales/de.json index fdb5c54f2c..c8f3f1d83f 100644 --- a/common/locales/de.json +++ b/common/locales/de.json @@ -202,6 +202,19 @@ }, "name": "Lagerabschnitt | Lagerabschnitte" }, + "profile": { + "fields": { + "abbreviation": "Abkürzung", + "color": "Farbe", + "email": "E-Mail-Adresse", + "firstname": "Vorname", + "language": "Sprache", + "nickname": "Spitzname", + "password": "Passwort", + "surname": "Nachname" + }, + "name": "Profil" + }, "scheduleEntry": { "fields": { "duration": "Dauer", diff --git a/common/locales/en.json b/common/locales/en.json index d78a0fa02a..1093dd758c 100644 --- a/common/locales/en.json +++ b/common/locales/en.json @@ -209,6 +209,19 @@ }, "name": "Period | Periods" }, + "profile": { + "fields": { + "abbreviation": "Abbreviation", + "color": "Color", + "email": "Email address", + "firstname": "Firstname", + "language": "Language", + "nickname": "Nickname", + "password": "Password", + "surname": "Lastname" + }, + "name": "Profile" + }, "scheduleEntry": { "fields": { "duration": "Duration", diff --git a/common/locales/fr.json b/common/locales/fr.json index 5cf8b89303..db1a1a74a4 100644 --- a/common/locales/fr.json +++ b/common/locales/fr.json @@ -190,6 +190,19 @@ }, "name": "Période | Périodes" }, + "profile": { + "fields": { + "abbreviation": "Abréviation", + "color": "Couleur", + "email": "Adresse e-mail", + "firstname": "Prénom", + "language": "Langue", + "nickname": "Surnom", + "password": "Mot de passe", + "surname": "Nom de famille" + }, + "name": "Profil" + }, "scheduleEntry": { "fields": { "duration": "Durée", diff --git a/common/locales/it.json b/common/locales/it.json index c2ff83a458..f7ea5a4054 100644 --- a/common/locales/it.json +++ b/common/locales/it.json @@ -180,6 +180,19 @@ }, "name": "Periodo | Periodi" }, + "profile": { + "fields": { + "abbreviation": "Abbreviazione", + "color": "Colore", + "email": "Indirizzo email", + "firstname": "Nome", + "language": "Lingua", + "nickname": "Soprannome", + "password": "Parola d'ordine", + "surname": "Cognome" + }, + "name": "Profilo" + }, "scheduleEntry": { "fields": { "duration": "Durata", diff --git a/common/locales/rm.json b/common/locales/rm.json index 9e26dfeeb6..28973a4e31 100644 --- a/common/locales/rm.json +++ b/common/locales/rm.json @@ -1 +1,16 @@ -{} \ No newline at end of file +{ + "entity": { + "profile": { + "fields": { + "abbreviation": "Abbreviaziun", + "color": "Colur", + "email": "Adressa dad e-mail", + "firstname": "Prenum", + "language": "Lingua", + "nickname": "Surnum", + "password": "Pled-clav", + "surname": "Num da famiglia" + } + } + } +} \ No newline at end of file diff --git a/frontend/src/components/collaborator/CollaboratorCreate.vue b/frontend/src/components/collaborator/CollaboratorCreate.vue index f195eb0052..ba303341be 100644 --- a/frontend/src/components/collaborator/CollaboratorCreate.vue +++ b/frontend/src/components/collaborator/CollaboratorCreate.vue @@ -25,7 +25,7 @@ class="mb-2" /> - + diff --git a/frontend/src/components/collaborator/CollaboratorEdit.vue b/frontend/src/components/collaborator/CollaboratorEdit.vue index d1c23dc05d..47974ab47f 100644 --- a/frontend/src/components/collaborator/CollaboratorEdit.vue +++ b/frontend/src/components/collaborator/CollaboratorEdit.vue @@ -66,6 +66,7 @@ :collaboration="entityData" :status="collaborator.status" :readonly-role="isLastManager" + :initial-collaboration="collaborator" >