Browse Source

Merge branch 'birthdays' into 'develop'

Birthdays

See merge request pleroma/pleroma-fe!1432
HJ 1 year ago
parent
commit
22c3012e1c

+ 2 - 0
src/boot/after_store.js

@@ -60,6 +60,8 @@ const getInstanceConfig = async ({ store }) => {
 
       store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
       store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
+      store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma.metadata.birthday_required })
+      store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma.metadata.birthday_min_age || 0 })
 
       if (vapidPublicKey) {
         store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })

+ 29 - 1
src/components/registration/registration.js

@@ -3,6 +3,7 @@ import { required, requiredIf, sameAs } from '@vuelidate/validators'
 import { mapActions, mapState } from 'vuex'
 import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue'
 import localeService from '../../services/locale/locale.service.js'
+import { DAY } from 'src/services/date_utils/date_utils.js'
 
 const registration = {
   setup () { return { v$: useVuelidate() } },
@@ -13,6 +14,7 @@ const registration = {
       username: '',
       password: '',
       confirm: '',
+      birthday: '',
       reason: '',
       language: ''
     },
@@ -32,6 +34,12 @@ const registration = {
           required,
           sameAs: sameAs(this.user.password)
         },
+        birthday: {
+          required: requiredIf(() => this.birthdayRequired),
+          maxValue: value => {
+            return !this.birthdayRequired || new Date(value).getTime() <= this.birthdayMin.getTime()
+          }
+        },
         reason: { required: requiredIf(() => this.accountApprovalRequired) },
         language: {}
       }
@@ -52,6 +60,24 @@ const registration = {
     reasonPlaceholder () {
       return this.replaceNewlines(this.$t('registration.reason_placeholder'))
     },
+    birthdayMin () {
+      const minAge = this.birthdayMinAge
+      const today = new Date()
+      today.setUTCMilliseconds(0)
+      today.setUTCSeconds(0)
+      today.setUTCMinutes(0)
+      today.setUTCHours(0)
+      const minDate = new Date()
+      minDate.setTime(today.getTime() - minAge * DAY)
+      return minDate
+    },
+    birthdayMinAttr () {
+      return this.birthdayMin.toJSON().replace(/T.+$/, '')
+    },
+    birthdayMinFormatted () {
+      const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale)
+      return this.user.birthday && new Date(Date.parse(this.birthdayMin)).toLocaleDateString(browserLocale, { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' })
+    },
     ...mapState({
       registrationOpen: (state) => state.instance.registrationOpen,
       signedIn: (state) => !!state.users.currentUser,
@@ -59,7 +85,9 @@ const registration = {
       serverValidationErrors: (state) => state.users.signUpErrors,
       termsOfService: (state) => state.instance.tos,
       accountActivationRequired: (state) => state.instance.accountActivationRequired,
-      accountApprovalRequired: (state) => state.instance.accountApprovalRequired
+      accountApprovalRequired: (state) => state.instance.accountApprovalRequired,
+      birthdayRequired: (state) => state.instance.birthdayRequired,
+      birthdayMinAge: (state) => state.instance.birthdayMinAge
     })
   },
   methods: {

+ 34 - 0
src/components/registration/registration.vue

@@ -167,6 +167,40 @@
               </ul>
             </div>
 
+            <div
+              class="form-group"
+              :class="{ 'form-group--error': v$.user.birthday.$error }"
+            >
+              <label
+                class="form--label"
+                for="sign-up-birthday"
+              >
+                {{ birthdayRequired ? $t('registration.birthday') : $t('registration.birthday_optional') }}
+              </label>
+              <input
+                id="sign-up-birthday"
+                v-model="user.birthday"
+                :disabled="isPending"
+                class="form-control"
+                type="date"
+                :max="birthdayRequired ? birthdayMinAttr : undefined"
+                :aria-required="birthdayRequired"
+              >
+            </div>
+            <div
+              v-if="v$.user.birthday.$dirty"
+              class="form-error"
+            >
+              <ul>
+                <li v-if="v$.user.birthday.required.$invalid">
+                  <span>{{ $t('registration.validations.birthday_required') }}</span>
+                </li>
+                <li v-if="v$.user.birthday.maxValue.$invalid">
+                  <span>{{ $tc('registration.validations.birthday_min_age', { date: birthdayMinFormatted }) }}</span>
+                </li>
+              </ul>
+            </div>
+
             <div
               class="form-group"
               :class="{ 'form-group--error': v$.user.language.$error }"

+ 5 - 1
src/components/settings_modal/tabs/profile_tab.js

@@ -32,6 +32,8 @@ const ProfileTab = {
       newName: this.$store.state.users.currentUser.name_unescaped,
       newBio: unescape(this.$store.state.users.currentUser.description),
       newLocked: this.$store.state.users.currentUser.locked,
+      newBirthday: this.$store.state.users.currentUser.birthday,
+      showBirthday: this.$store.state.users.currentUser.show_birthday,
       newFields: this.$store.state.users.currentUser.fields.map(field => ({ name: field.name, value: field.value })),
       showRole: this.$store.state.users.currentUser.show_role,
       role: this.$store.state.users.currentUser.role,
@@ -125,7 +127,9 @@ const ProfileTab = {
         display_name: this.newName,
         fields_attributes: this.newFields.filter(el => el != null),
         bot: this.bot,
-        show_role: this.showRole
+        show_role: this.showRole,
+        birthday: this.newBirthday || '',
+        show_birthday: this.showBirthday
         /* eslint-enable camelcase */
       }
 

+ 5 - 0
src/components/settings_modal/tabs/profile_tab.scss

@@ -129,4 +129,9 @@
       padding: 0 0.5em;
     }
   }
+
+  .birthday-input {
+    display: block;
+    margin-bottom: 1em;
+  }
 }

+ 12 - 0
src/components/settings_modal/tabs/profile_tab.vue

@@ -35,6 +35,18 @@
           </template>
         </Checkbox>
       </p>
+      <div>
+        <p>{{ $t('settings.birthday.label') }}</p>
+        <input
+          id="birthday"
+          v-model="newBirthday"
+          type="date"
+          class="birthday-input"
+        >
+        <Checkbox v-model="showBirthday">
+          {{ $t('settings.birthday.show_birthday') }}
+        </Checkbox>
+      </div>
       <div v-if="maxFields > 0">
         <p>{{ $t('settings.profile_fields.label') }}</p>
         <div

+ 9 - 2
src/components/user_profile/user_profile.js

@@ -7,13 +7,16 @@ import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
 import RichContent from 'src/components/rich_content/rich_content.jsx'
 import List from '../list/list.vue'
 import withLoadMore from '../../hocs/with_load_more/with_load_more'
+import localeService from 'src/services/locale/locale.service.js'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
-  faCircleNotch
+  faCircleNotch,
+  faBirthdayCake
 } from '@fortawesome/free-solid-svg-icons'
 
 library.add(
-  faCircleNotch
+  faCircleNotch,
+  faBirthdayCake
 )
 
 const FollowerList = withLoadMore({
@@ -76,6 +79,10 @@ const UserProfile = {
     },
     followersTabVisible () {
       return this.isUs || !this.user.hide_followers
+    },
+    formattedBirthday () {
+      const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale)
+      return this.user.birthday && new Date(Date.parse(this.user.birthday)).toLocaleDateString(browserLocale, { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' })
     }
   },
   methods: {

+ 14 - 0
src/components/user_profile/user_profile.vue

@@ -12,6 +12,16 @@
         rounded="top"
         :has-note-editor="true"
       />
+      <span
+        v-if="!!user.birthday"
+        class="user-birthday"
+      >
+        <FAIcon
+          class="fa-old-padding"
+          icon="birthday-cake"
+        />
+        {{ $t('user_card.birthday', { birthday: formattedBirthday }) }}
+      </span>
       <div
         v-if="user.fields_html && user.fields_html.length > 0"
         class="user-profile-fields"
@@ -149,6 +159,10 @@
   // No sticky header on user profile
   --currentPanelStack: 1;
 
+  .user-birthday {
+    margin: 0 0.75em 0.5em;
+  }
+
   .user-profile-fields {
     margin: 0 0.5em;
 

+ 11 - 2
src/i18n/en.json

@@ -316,9 +316,13 @@
       "email_required": "cannot be left blank",
       "password_required": "cannot be left blank",
       "password_confirmation_required": "cannot be left blank",
-      "password_confirmation_match": "should be the same as password"
+      "password_confirmation_match": "should be the same as password",
+      "birthday_required": "cannot be left blank",
+      "birthday_min_age": "must be on or before {date}"
     },
-    "email_language": "In which language do you want to receive emails from the server?"
+    "email_language": "In which language do you want to receive emails from the server?",
+    "birthday": "Birthday:",
+    "birthday_optional": "Birthday (optional):"
   },
   "remote_user_resolver": {
     "remote_user_resolver": "Remote user resolver",
@@ -528,6 +532,10 @@
       "name": "Label",
       "value": "Content"
     },
+    "birthday": {
+      "label": "Birthday",
+      "show_birthday": "Show my birthday"
+    },
     "account_privacy": "Privacy",
     "use_contain_fit": "Don't crop the attachment in thumbnails",
     "name": "Name",
@@ -986,6 +994,7 @@
     "hide_repeats": "Hide repeats",
     "show_repeats": "Show repeats",
     "bot": "Bot",
+    "birthday": "Born {birthday}",
     "admin_menu": {
       "moderation": "Moderation",
       "grant_admin": "Grant Admin",

+ 2 - 0
src/modules/instance.js

@@ -116,6 +116,8 @@ const defaultState = {
   restrictedNicknames: [],
   safeDM: true,
   knownDomains: [],
+  birthdayRequired: false,
+  birthdayMinAge: 0,
 
   // Feature-set, apparently, not everything here is reported...
   shoutAvailable: false,

+ 3 - 0
src/services/entity_normalizer/entity_normalizer.service.js

@@ -125,6 +125,8 @@ export const parseUser = (data) => {
         output.role = 'member'
       }
 
+      output.birthday = data.pleroma.birthday
+
       if (data.pleroma.privileges) {
         output.privileges = data.pleroma.privileges
       } else if (data.pleroma.is_admin) {
@@ -162,6 +164,7 @@ export const parseUser = (data) => {
         output.no_rich_text = data.source.pleroma.no_rich_text
         output.show_role = data.source.pleroma.show_role
         output.discoverable = data.source.pleroma.discoverable
+        output.show_birthday = data.pleroma.show_birthday
       }
     }