Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/profile')
-rw-r--r--app/assets/javascripts/profile/account/components/delete_account_modal.vue16
-rw-r--r--app/assets/javascripts/profile/account/components/update_username.vue2
-rw-r--r--app/assets/javascripts/profile/components/follow.vue33
-rw-r--r--app/assets/javascripts/profile/components/followers_tab.vue4
-rw-r--r--app/assets/javascripts/profile/components/following_tab.vue57
-rw-r--r--app/assets/javascripts/profile/components/profile_tabs.vue17
-rw-r--r--app/assets/javascripts/profile/components/snippets/snippets_tab.vue37
-rw-r--r--app/assets/javascripts/profile/components/user_achievements.vue2
-rw-r--r--app/assets/javascripts/profile/index.js4
-rw-r--r--app/assets/javascripts/profile/preferences/components/profile_preferences.vue49
10 files changed, 182 insertions, 39 deletions
diff --git a/app/assets/javascripts/profile/account/components/delete_account_modal.vue b/app/assets/javascripts/profile/account/components/delete_account_modal.vue
index c64fbc91d12..915f6578ac3 100644
--- a/app/assets/javascripts/profile/account/components/delete_account_modal.vue
+++ b/app/assets/javascripts/profile/account/components/delete_account_modal.vue
@@ -1,5 +1,6 @@
<script>
import { GlModal, GlSprintf } from '@gitlab/ui';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import csrf from '~/lib/utils/csrf';
import { __, s__ } from '~/locale';
@@ -8,6 +9,7 @@ export default {
GlModal,
GlSprintf,
},
+ mixins: [glFeatureFlagMixin()],
props: {
actionUrl: {
type: String,
@@ -67,6 +69,9 @@ export default {
},
},
i18n: {
+ textdelay: s__(`Profiles|
+You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account.
+Once you confirm %{deleteAccount}, it cannot be undone or recovered. You might have to wait seven days before creating a new account with the same username or email.`),
text: s__(`Profiles|
You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account.
Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
@@ -85,7 +90,16 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
@primary="onSubmit"
>
<p>
- <gl-sprintf :message="$options.i18n.text">
+ <gl-sprintf v-if="glFeatures.delayDeleteOwnUser" :message="$options.i18n.textdelay">
+ <template #yourAccount>
+ <strong>{{ s__('Profiles|your account') }}</strong>
+ </template>
+
+ <template #deleteAccount>
+ <strong>{{ s__('Profiles|Delete account') }}</strong>
+ </template>
+ </gl-sprintf>
+ <gl-sprintf v-else :message="$options.i18n.text">
<template #yourAccount>
<strong>{{ s__('Profiles|your account') }}</strong>
</template>
diff --git a/app/assets/javascripts/profile/account/components/update_username.vue b/app/assets/javascripts/profile/account/components/update_username.vue
index d96b5748abc..ae017d2a299 100644
--- a/app/assets/javascripts/profile/account/components/update_username.vue
+++ b/app/assets/javascripts/profile/account/components/update_username.vue
@@ -115,7 +115,7 @@ Please update your Git repository remotes as soon as possible.`),
v-model="newUsername"
data-testid="new-username-input"
:disabled="isRequestPending"
- class="form-control"
+ class="form-control gl-md-form-input-lg"
required="required"
/>
</div>
diff --git a/app/assets/javascripts/profile/components/follow.vue b/app/assets/javascripts/profile/components/follow.vue
index 7bab8a1c30d..2673ab6fbf4 100644
--- a/app/assets/javascripts/profile/components/follow.vue
+++ b/app/assets/javascripts/profile/components/follow.vue
@@ -1,7 +1,14 @@
<script>
-import { GlAvatarLabeled, GlAvatarLink, GlLoadingIcon, GlPagination } from '@gitlab/ui';
+import {
+ GlAvatarLabeled,
+ GlAvatarLink,
+ GlLoadingIcon,
+ GlPagination,
+ GlEmptyState,
+} from '@gitlab/ui';
import { DEFAULT_PER_PAGE } from '~/api';
import { NEXT, PREV } from '~/vue_shared/components/pagination/constants';
+import { isCurrentUser } from '~/lib/utils/common_utils';
export default {
i18n: {
@@ -13,7 +20,9 @@ export default {
GlAvatarLink,
GlLoadingIcon,
GlPagination,
+ GlEmptyState,
},
+ inject: ['followEmptyState', 'userId'],
props: {
/**
* Expected format:
@@ -48,12 +57,34 @@ export default {
required: false,
default: DEFAULT_PER_PAGE,
},
+ currentUserEmptyStateTitle: {
+ type: String,
+ required: true,
+ },
+ visitorEmptyStateTitle: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ emptyStateTitle() {
+ return isCurrentUser(this.userId)
+ ? this.currentUserEmptyStateTitle
+ : this.visitorEmptyStateTitle;
+ },
},
};
</script>
<template>
<gl-loading-icon v-if="loading" class="gl-mt-5" size="md" />
+ <gl-empty-state
+ v-else-if="!users.length"
+ class="gl-mt-5"
+ :svg-path="followEmptyState"
+ :svg-height="144"
+ :title="emptyStateTitle"
+ />
<div v-else>
<div class="gl-my-n3 gl-mx-n3 gl-display-flex gl-flex-wrap">
<div v-for="user in users" :key="user.id" class="gl-p-3 gl-w-full gl-md-w-half gl-lg-w-25p">
diff --git a/app/assets/javascripts/profile/components/followers_tab.vue b/app/assets/javascripts/profile/components/followers_tab.vue
index 1fa579bc611..927424d6c3f 100644
--- a/app/assets/javascripts/profile/components/followers_tab.vue
+++ b/app/assets/javascripts/profile/components/followers_tab.vue
@@ -12,6 +12,8 @@ export default {
errorMessage: s__(
'UserProfile|An error occurred loading the followers. Please refresh the page to try again.',
),
+ currentUserEmptyStateTitle: s__('UserProfile|You do not have any followers'),
+ visitorEmptyStateTitle: s__("UserProfile|This user doesn't have any followers"),
},
components: {
GlBadge,
@@ -68,6 +70,8 @@ export default {
:loading="loading"
:page="page"
:total-items="totalItems"
+ :current-user-empty-state-title="$options.i18n.currentUserEmptyStateTitle"
+ :visitor-empty-state-title="$options.i18n.visitorEmptyStateTitle"
@pagination-input="onPaginationInput"
/>
</gl-tab>
diff --git a/app/assets/javascripts/profile/components/following_tab.vue b/app/assets/javascripts/profile/components/following_tab.vue
index 8ee878e3dcc..66c7ee42a3f 100644
--- a/app/assets/javascripts/profile/components/following_tab.vue
+++ b/app/assets/javascripts/profile/components/following_tab.vue
@@ -1,16 +1,62 @@
<script>
import { GlBadge, GlTab } from '@gitlab/ui';
import { s__ } from '~/locale';
+import { getUserFollowing } from '~/rest_api';
+import { createAlert } from '~/alert';
+import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
+import Follow from './follow.vue';
export default {
i18n: {
title: s__('UserProfile|Following'),
+ errorMessage: s__(
+ 'UserProfile|An error occurred loading the following. Please refresh the page to try again.',
+ ),
+ currentUserEmptyStateTitle: s__('UserProfile|You are not following other users'),
+ visitorEmptyStateTitle: s__("UserProfile|This user isn't following other users"),
},
components: {
GlBadge,
GlTab,
+ Follow,
+ },
+ inject: ['followeesCount', 'userId'],
+ data() {
+ return {
+ following: [],
+ loading: true,
+ totalItems: 0,
+ page: 1,
+ };
+ },
+ watch: {
+ page: {
+ async handler() {
+ this.loading = true;
+
+ try {
+ const { data: following, headers } = await getUserFollowing(this.userId, {
+ page: this.page,
+ });
+
+ const { total } = parseIntPagination(normalizeHeaders(headers));
+
+ this.following = following;
+ this.totalItems = total;
+ } catch (error) {
+ createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true });
+ } finally {
+ this.loading = false;
+ }
+ },
+ immediate: true,
+ },
+ },
+ methods: {
+ onPaginationInput(page) {
+ this.page = page;
+ },
},
- inject: ['followeesCount'],
};
</script>
@@ -20,5 +66,14 @@ export default {
<span>{{ $options.i18n.title }}</span>
<gl-badge size="sm" class="gl-ml-2">{{ followeesCount }}</gl-badge>
</template>
+ <follow
+ :users="following"
+ :loading="loading"
+ :page="page"
+ :total-items="totalItems"
+ :current-user-empty-state-title="$options.i18n.currentUserEmptyStateTitle"
+ :visitor-empty-state-title="$options.i18n.visitorEmptyStateTitle"
+ @pagination-input="onPaginationInput"
+ />
</gl-tab>
</template>
diff --git a/app/assets/javascripts/profile/components/profile_tabs.vue b/app/assets/javascripts/profile/components/profile_tabs.vue
index 3a30c3bdc9b..e24167eb4fa 100644
--- a/app/assets/javascripts/profile/components/profile_tabs.vue
+++ b/app/assets/javascripts/profile/components/profile_tabs.vue
@@ -5,6 +5,7 @@ import { getUserProjects } from '~/rest_api';
import { s__ } from '~/locale';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { createAlert } from '~/alert';
+import { VISIBILITY_LEVEL_PUBLIC_STRING } from '~/visibility_level/constants';
import OverviewTab from './overview_tab.vue';
import ActivityTab from './activity_tab.vue';
import GroupsTab from './groups_tab.vue';
@@ -81,7 +82,21 @@ export default {
async mounted() {
try {
const response = await getUserProjects(this.userId, { per_page: 10 });
- this.personalProjects = convertObjectPropsToCamelCase(response.data, { deep: true });
+ this.personalProjects = convertObjectPropsToCamelCase(response.data, { deep: true }).map(
+ (project) => {
+ // This API does not return the `visibility` key if user is signed out.
+ // Because this API only returns public projects when signed out, in this case, we can assume
+ // the `visibility` attribute is `public` if it is missing.
+ if (!project.visibility) {
+ return {
+ ...project,
+ visibility: VISIBILITY_LEVEL_PUBLIC_STRING,
+ };
+ }
+
+ return project;
+ },
+ );
this.personalProjectsLoading = false;
} catch (error) {
createAlert({ message: this.$options.i18n.personalProjectsErrorMessage });
diff --git a/app/assets/javascripts/profile/components/snippets/snippets_tab.vue b/app/assets/javascripts/profile/components/snippets/snippets_tab.vue
index fce5e2f5e78..95649f9645b 100644
--- a/app/assets/javascripts/profile/components/snippets/snippets_tab.vue
+++ b/app/assets/javascripts/profile/components/snippets/snippets_tab.vue
@@ -1,9 +1,11 @@
<script>
import { GlTab, GlKeysetPagination, GlEmptyState } from '@gitlab/ui';
-import { s__ } from '~/locale';
+import { s__, __ } from '~/locale';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { TYPENAME_USER } from '~/graphql_shared/constants';
import { SNIPPET_MAX_LIST_COUNT } from '~/profile/constants';
+import { isCurrentUser } from '~/lib/utils/common_utils';
+import { helpPagePath } from '~/helpers/help_page_helper';
import getUserSnippets from '../graphql/get_user_snippets.query.graphql';
import SnippetRow from './snippet_row.vue';
@@ -11,7 +13,11 @@ export default {
name: 'SnippetsTab',
i18n: {
title: s__('UserProfile|Snippets'),
- noSnippets: s__('UserProfiles|No snippets found.'),
+ currentUserEmptyStateTitle: s__('UserProfile|Get started with snippets'),
+ visitorEmptyStateTitle: s__("UserProfile|This user doesn't have any snippets"),
+ emptyStateDescription: s__('UserProfile|Store, share, and embed bits of code and text.'),
+ newSnippet: __('New snippet'),
+ learnMore: __('Learn more'),
},
components: {
GlTab,
@@ -19,7 +25,7 @@ export default {
GlEmptyState,
SnippetRow,
},
- inject: ['userId', 'snippetsEmptyState'],
+ inject: ['userId', 'snippetsEmptyState', 'newSnippetPath'],
data() {
return {
userInfo: {},
@@ -57,6 +63,14 @@ export default {
hasSnippets() {
return this.userSnippets?.length;
},
+ emptyStateTitle() {
+ return isCurrentUser(this.userId)
+ ? this.$options.i18n.currentUserEmptyStateTitle
+ : this.$options.i18n.visitorEmptyStateTitle;
+ },
+ emptyStateDescription() {
+ return isCurrentUser(this.userId) ? this.$options.i18n.emptyStateDescription : null;
+ },
},
methods: {
isLastSnippet(index) {
@@ -76,6 +90,7 @@ export default {
beforeToken: this.pageInfo.startCursor,
};
},
+ helpPagePath,
},
};
</script>
@@ -100,11 +115,17 @@ export default {
</div>
</template>
<template v-if="!hasSnippets">
- <gl-empty-state class="gl-mt-5" :svg-height="75" :svg-path="snippetsEmptyState">
- <template #title>
- <p class="gl-font-weight-bold gl-mt-n5">{{ $options.i18n.noSnippets }}</p>
- </template>
- </gl-empty-state>
+ <gl-empty-state
+ class="gl-mt-5"
+ :svg-path="snippetsEmptyState"
+ :svg-height="144"
+ :title="emptyStateTitle"
+ :description="emptyStateDescription"
+ :primary-button-link="newSnippetPath"
+ :primary-button-text="$options.i18n.newSnippet"
+ :secondary-button-text="$options.i18n.learnMore"
+ :secondary-button-link="helpPagePath('user/snippets')"
+ />
</template>
</gl-tab>
</template>
diff --git a/app/assets/javascripts/profile/components/user_achievements.vue b/app/assets/javascripts/profile/components/user_achievements.vue
index 13a1b797a83..7ce6b61c4ac 100644
--- a/app/assets/javascripts/profile/components/user_achievements.vue
+++ b/app/assets/javascripts/profile/components/user_achievements.vue
@@ -85,7 +85,7 @@ export default {
:size="32"
tabindex="0"
shape="rect"
- class="gl-mx-2"
+ class="gl-mx-2 gl-p-1 gl-border-none"
/>
<br />
<gl-badge v-if="showCountBadge(userAchievement.count)" variant="info" size="sm">{{
diff --git a/app/assets/javascripts/profile/index.js b/app/assets/javascripts/profile/index.js
index 198ffdb434b..76430d7b34d 100644
--- a/app/assets/javascripts/profile/index.js
+++ b/app/assets/javascripts/profile/index.js
@@ -21,6 +21,8 @@ export const initProfileTabs = () => {
utcOffset,
userId,
snippetsEmptyState,
+ newSnippetPath,
+ followEmptyState,
} = el.dataset;
const apolloProvider = new VueApollo({
@@ -39,6 +41,8 @@ export const initProfileTabs = () => {
utcOffset,
userId,
snippetsEmptyState,
+ newSnippetPath,
+ followEmptyState,
},
render(createElement) {
return createElement(ProfileTabs);
diff --git a/app/assets/javascripts/profile/preferences/components/profile_preferences.vue b/app/assets/javascripts/profile/preferences/components/profile_preferences.vue
index 164ec46cdb9..aa30192b74b 100644
--- a/app/assets/javascripts/profile/preferences/components/profile_preferences.vue
+++ b/app/assets/javascripts/profile/preferences/components/profile_preferences.vue
@@ -110,34 +110,33 @@ export default {
</script>
<template>
- <div class="row gl-mt-3 js-preferences-form js-search-settings-section">
- <div v-if="integrationViews.length" class="col-sm-12">
- <hr data-testid="profile-preferences-integrations-rule" />
- </div>
- <div v-if="integrationViews.length" class="col-lg-4 profile-settings-sidebar">
- <h4 class="gl-mt-0" data-testid="profile-preferences-integrations-heading">
- {{ $options.i18n.integrations }}
- </h4>
- <p>
+ <div class="gl-display-contents js-preferences-form">
+ <div
+ v-if="integrationViews.length"
+ class="settings-section gl-border-t gl-pt-6! js-search-settings-section"
+ >
+ <div class="settings-sticky-header">
+ <div class="settings-sticky-header-inner">
+ <h4 class="gl-my-0" data-testid="profile-preferences-integrations-heading">
+ {{ $options.i18n.integrations }}
+ </h4>
+ </div>
+ </div>
+ <p class="gl-text-secondary">
{{ $options.i18n.integrationsDescription }}
</p>
+ <div>
+ <integration-view
+ v-for="view in integrationViews"
+ :key="view.name"
+ :help-link="view.help_link"
+ :message="view.message"
+ :message-url="view.message_url"
+ :config="$options.integrationViewConfigs[view.name]"
+ />
+ </div>
</div>
- <div v-if="integrationViews.length" class="col-lg-8">
- <integration-view
- v-for="view in integrationViews"
- :key="view.name"
- :help-link="view.help_link"
- :message="view.message"
- :message-url="view.message_url"
- :config="$options.integrationViewConfigs[view.name]"
- />
- </div>
-
- <div class="col-lg-4"></div>
- <div class="col-lg-8">
- <hr />
- </div>
- <div class="col-sm-12 js-hide-when-nothing-matches-search">
+ <div class="settings-sticky-footer js-hide-when-nothing-matches-search">
<gl-button
category="primary"
variant="confirm"