diff --git a/admin/package.json b/admin/package.json index 5b4c7a24c..f74ff7729 100644 --- a/admin/package.json +++ b/admin/package.json @@ -55,7 +55,8 @@ "vue-router": "4.4.0", "vue3-datepicker": "^0.4.0", "vuex": "4.1.0", - "vuex-persistedstate": "4.1.0" + "vuex-persistedstate": "4.1.0", + "yup": "^1.6.1" }, "devDependencies": { "@apollo/client": "^3.10.8", diff --git a/admin/src/components/Federation/FederationVisualizeItem.vue b/admin/src/components/Federation/FederationVisualizeItem.vue index ea8a6196f..1aa4176e9 100644 --- a/admin/src/components/Federation/FederationVisualizeItem.vue +++ b/admin/src/components/Federation/FederationVisualizeItem.vue @@ -56,7 +56,7 @@ export default { ? formatDistanceToNow(new Date(dateString), { includeSecond: true, addSuffix: true, - locale: useDateLocale, + locale: useDateLocale(), }) : '' }, diff --git a/admin/src/components/NavBar.vue b/admin/src/components/NavBar.vue index b9dc12106..fa715da88 100644 --- a/admin/src/components/NavBar.vue +++ b/admin/src/components/NavBar.vue @@ -32,6 +32,9 @@ {{ $t('navbar.instances') }} + + {{ $t('navbar.projectBranding') }} + {{ $t('navbar.statistic') }} diff --git a/admin/src/components/ProjectBranding/ProjectBrandingForm.vue b/admin/src/components/ProjectBranding/ProjectBrandingForm.vue new file mode 100644 index 000000000..107da6b61 --- /dev/null +++ b/admin/src/components/ProjectBranding/ProjectBrandingForm.vue @@ -0,0 +1,129 @@ + + + + + + + + updateField(value, 'newUserToSpace')" + > + {{ $t('projectBranding.newUserToSpaceTooltip') }} + + + + + {{ errorMessage }} + + + {{ $t('save') }} + {{ $t('reset') }} + + + + + + diff --git a/admin/src/components/ProjectBranding/ProjectBrandingItem.vue b/admin/src/components/ProjectBranding/ProjectBrandingItem.vue new file mode 100644 index 000000000..d52d416ef --- /dev/null +++ b/admin/src/components/ProjectBranding/ProjectBrandingItem.vue @@ -0,0 +1,69 @@ + + + + {{ item.name }} + {{ item.alias }} + {{ item.newUserToSpace }} + + + + + + + + + + + + + + + + + + diff --git a/admin/src/components/input/LabeledInput.vue b/admin/src/components/input/LabeledInput.vue new file mode 100644 index 000000000..920fceacd --- /dev/null +++ b/admin/src/components/input/LabeledInput.vue @@ -0,0 +1,45 @@ + + + + + + + + + + + diff --git a/admin/src/components/input/ValidatedInput.vue b/admin/src/components/input/ValidatedInput.vue new file mode 100644 index 000000000..1d8e90ea9 --- /dev/null +++ b/admin/src/components/input/ValidatedInput.vue @@ -0,0 +1,90 @@ + + + + {{ errorMessage }} + + + + + diff --git a/admin/src/locales/de.json b/admin/src/locales/de.json index 22bb1d614..c48582d54 100644 --- a/admin/src/locales/de.json +++ b/admin/src/locales/de.json @@ -1,5 +1,7 @@ { "GDD": "GDD", + "actions": "Aktionen", + "alias": "Alias", "all_emails": "Alle Nutzer", "back": "zurück", "change_user_role": "Nutzerrolle ändern", @@ -52,7 +54,6 @@ "enter_text": "Text eintragen", "form": "Schöpfungsformular", "min_characters": "Mindestens 10 Zeichen eingeben", - "reset": "Zurücksetzen", "select_month": "Monat auswählen", "select_value": "Betrag auswählen", "submit_creation": "Schöpfung einreichen", @@ -68,6 +69,7 @@ "deleted": "gelöscht", "deleted_user": "Alle gelöschten Nutzer", "deny": "Ablehnen", + "description": "Beschreibung", "e_mail": "E-Mail", "edit": "bearbeiten", "enabled": "aktiviert", @@ -127,6 +129,7 @@ "lastname": "Nachname", "latitude": "Breitengrad:", "latitude-longitude-smart": "Breitengrad, Längengrad", + "logo": "Logo", "longitude": "Längengrad:", "math": { "equals": "=", @@ -155,6 +158,8 @@ "instances": "Instanzen", "logout": "Abmelden", "my-account": "Mein Konto", + "projectBranding": "Projekt Branding", + "projectBrandingTooltip": "Nutze ein eigenes Logo im Gradido Login und füge neue Benutzer einem Humhub-Space hinzu", "statistic": "Statistik", "user_search": "Nutzersuche" }, @@ -199,8 +204,15 @@ "yes": "Ja, Nutzer wiederherstellen" } }, + "projectBranding": { + "addTooltip": "Neuen Projekt Branding Eintrag hinzufügen", + "title": "Projekt Brandings", + "newUserToSpace": "Benutzer hinzufügen?", + "newUserToSpaceTooltip": "Neue Benutzer automatisch zum Space hinzufügen, falls Space vorhanden" + }, "redeemed": "eingelöst", "removeNotSelf": "Als Admin/Moderator kannst du dich nicht selber löschen.", + "reset": "Zurücksetzen", "save": "Speichern", "statistic": { "activeUsers": "Aktive Mitglieder", diff --git a/admin/src/locales/en.json b/admin/src/locales/en.json index c8c45a7a0..1fe06348d 100644 --- a/admin/src/locales/en.json +++ b/admin/src/locales/en.json @@ -1,5 +1,7 @@ { "GDD": "GDD", + "actions": "Actions", + "alias": "Alias", "all_emails": "All users", "back": "back", "change_user_role": "Change user role", @@ -68,6 +70,7 @@ "deleted": "deleted", "deleted_user": "All deleted user", "deny": "Reject", + "description": "Description", "e_mail": "E-mail", "edit": "edit", "enabled": "enabled", @@ -127,6 +130,7 @@ "lastname": "Lastname", "latitude": "Latitude:", "latitude-longitude-smart": "Latitude, Longitude", + "logo": "Logo", "longitude": "Longitude:", "math": { "equals": "=", @@ -155,6 +159,8 @@ "instances": "Instances", "logout": "Logout", "my-account": "My Account", + "projectBranding": "Project Branding", + "projectBrandingTooltip": "Use your own logo in the Gradido login and add new users to a Humhub space", "statistic": "Statistic", "user_search": "User search" }, @@ -199,9 +205,16 @@ "yes": "Yes,undelete user" } }, + "projectBranding": { + "addTooltip": "Add new project branding entry", + "title": "Project Branding", + "newUserToSpace": "Add user?", + "newUserToSpaceTooltip": "The hours should contain a maximum of two decimal places" + }, "redeemed": "redeemed", "removeNotSelf": "As an admin/moderator, you cannot delete yourself.", - "save": "Speichern", + "reset": "Reset", + "save": "Save", "statistic": { "activeUsers": "Active members", "count": "Count", diff --git a/admin/src/pages/ProjectBranding.vue b/admin/src/pages/ProjectBranding.vue new file mode 100644 index 000000000..03e137706 --- /dev/null +++ b/admin/src/pages/ProjectBranding.vue @@ -0,0 +1,119 @@ + + + + {{ $t('projectBranding.title') }} + + + + + + + + + + + + {{ $t('name') }} + {{ $t('alias') }} + + {{ $t('projectBranding.newUserToSpace') }} + + {{ $t('logo') }} + {{ $t('actions') }} + + + + + + + + + diff --git a/admin/src/router/routes.js b/admin/src/router/routes.js index 7342a0b6b..9a76b1e31 100644 --- a/admin/src/router/routes.js +++ b/admin/src/router/routes.js @@ -36,6 +36,11 @@ const routes = [ name: 'federation', component: () => import('@/pages/FederationVisualize.vue'), }, + { + path: '/projectBranding', + name: 'projectBranding', + component: () => import('@/pages/ProjectBranding.vue'), + }, { path: '/:catchAll(.*)', name: 'NotFound', diff --git a/admin/src/validationSchemas.js b/admin/src/validationSchemas.js new file mode 100644 index 000000000..b31e3043c --- /dev/null +++ b/admin/src/validationSchemas.js @@ -0,0 +1,14 @@ +// TODO: only needed for grace period, before all inputs updated for using veeValidate + yup +export const isLanguageKey = (str) => + str.match(/^(?!\.)[a-z][a-zA-Z0-9-]*([.][a-z][a-zA-Z0-9-]*)*(? { + const type = typeof error + if (type === 'object') { + return t(error.key, error.values) + } else if (type === 'string' && error.length > 0 && isLanguageKey(error)) { + return t(error) + } else { + return error + } +} diff --git a/admin/yarn.lock b/admin/yarn.lock index 3e3a8b8c4..ce8dccf2c 100644 --- a/admin/yarn.lock +++ b/admin/yarn.lock @@ -5657,6 +5657,11 @@ prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.13.1" +property-expr@^2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8" + integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA== + proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" @@ -6484,6 +6489,11 @@ throttle-debounce@^5.0.0: resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz#ec5549d84e053f043c9fd0f2a6dd892ff84456b1" integrity sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A== +tiny-case@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03" + integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q== + tinybench@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" @@ -6543,6 +6553,11 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== + tough-cookie@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.0.0.tgz#6b6518e2b5c070cf742d872ee0f4f92d69eac1af" @@ -6608,6 +6623,11 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-fest@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -7145,6 +7165,16 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +yup@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/yup/-/yup-1.6.1.tgz#8defcff9daaf9feac178029c0e13b616563ada4b" + integrity sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA== + dependencies: + property-expr "^2.0.5" + tiny-case "^1.0.3" + toposort "^2.0.2" + type-fest "^2.19.0" + zen-observable-ts@^0.8.21: version "0.8.21" resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz#85d0031fbbde1eba3cd07d3ba90da241215f421d" diff --git a/backend/src/graphql/resolver/ProjectBrandingResolver.ts b/backend/src/graphql/resolver/ProjectBrandingResolver.ts index b18536e1f..3f31e809a 100644 --- a/backend/src/graphql/resolver/ProjectBrandingResolver.ts +++ b/backend/src/graphql/resolver/ProjectBrandingResolver.ts @@ -1,5 +1,5 @@ import { ProjectBranding as DbProjectBranding } from '@entity/ProjectBranding' -import { Resolver, Query, Mutation, Arg, Int, Authorized } from 'type-graphql' +import { Resolver, Query, Mutation, Arg, Int, Authorized, ID } from 'type-graphql' import { ProjectBrandingInput } from '@input/ProjectBrandingInput' import { ProjectBranding } from '@model/ProjectBranding' @@ -44,13 +44,13 @@ export class ProjectBrandingResolver { @Mutation(() => ProjectBranding, { nullable: true }) @Authorized([RIGHTS.PROJECT_BRANDING_MUTATE]) async upsertProjectBranding( - @Arg('data') data: ProjectBrandingInput, + @Arg('input') input: ProjectBrandingInput, ): Promise { - const projectBranding = data.id - ? await DbProjectBranding.findOneOrFail({ where: { id: data.id } }) + const projectBranding = input.id + ? await DbProjectBranding.findOneOrFail({ where: { id: input.id } }) : new DbProjectBranding() - Object.assign(projectBranding, data) + Object.assign(projectBranding, input) await projectBranding.save() return new ProjectBranding(projectBranding) @@ -58,7 +58,7 @@ export class ProjectBrandingResolver { @Mutation(() => Boolean) @Authorized([RIGHTS.PROJECT_BRANDING_MUTATE]) - async deleteProjectBranding(@Arg('id', () => Int) id: number): Promise { + async deleteProjectBranding(@Arg('id', () => ID) id: number): Promise { try { await DbProjectBranding.delete({ id }) return true