Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/modules/profile/ui/AchievementElement.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<script setup lang="ts">
defineProps<{
name: string;
description: string;
picture: string;
}>();
</script>

<template>
<v-card
class="achievement-card bg-grey-lighten-4 rounded-lg pa-3 ma-2 d-flex flex-column align-center cursor-pointer"
elevation="0"
>
<v-avatar class="rounded-circle overflow-hidden mt-1" size="64">
<v-img :src="picture" :alt="name" cover aspect-ratio="1" />
</v-avatar>
<p class="text-body-1 mt-2 text-center">
{{ name }}
</p>
<v-tooltip activator="parent" location="bottom">
<p color="on-surface">{{ description }}</p>
</v-tooltip>
</v-card>
</template>

<style scoped>
.achievement-card {
width: 136px;
height: 144px;
transition: transform 0.2s ease;
}

.achievement-card:hover {
transform: translateY(-4px);
}
</style>
32 changes: 32 additions & 0 deletions src/modules/profile/ui/UserAchievements.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script setup lang="ts">
import AchievementElement from './AchievementElement.vue';
defineProps<{
achievements: Array<{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно вынести в отдельный тип (он наверное уже есть), и в единичный компонент прокинуть через Omit

id: number;
name: string;
description: string;
picture: string;
}>;
isLoading: boolean;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лоадеры класс

}>();
</script>

<template>
<v-slide-group v-if="achievements.length > 0" :show-arrows="false" class="me-n2 pt-9 pb-2">
<v-slide-group-item v-for="item in achievements" :key="item.id">
<AchievementElement
:name="item.name"
:description="item.description"
:picture="item.picture"
/>
</v-slide-group-item>
</v-slide-group>

<div v-else-if="isLoading" class="d-flex justify-center py-8">
<v-progress-circular indeterminate color="primary" />
</div>

<div v-else class="text-center text-grey-darken-2 py-8">
<p class="text-body-1 mb-0">У тебя еще нет достижений в приложении :(</p>
</div>
</template>
17 changes: 17 additions & 0 deletions src/modules/profile/ui/UserHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script setup lang="ts">
defineProps<{
userName: string;
photoUrl: string;
}>();
</script>

<template>
<div class="d-flex flex-row align-center ga-8">
<v-avatar size="120" class="flex-shrink-0">
<v-img :src="photoUrl" :alt="userName" cover />
</v-avatar>
<h2>
{{ userName }}
</h2>
</div>
</template>
21 changes: 21 additions & 0 deletions src/modules/profile/ui/UserInfo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script setup lang="ts">
import { UserdataArrayDataItem } from '@/models';
defineProps<{
name: string;
data: UserdataArrayDataItem[];
}>();
</script>

<template>
<div class="pa-5 mb-4 rounded-lg bg-grey-lighten-4">
<h3>{{ name }}</h3>
<div v-for="{ param, value } of data" :key="param" class="mt-4">
<div class="text-caption">
{{ param }}
</div>
<div class="text-body-1 text-break">
{{ value.name }}
</div>
</div>
</div>
</template>
4 changes: 4 additions & 0 deletions src/router/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ export const profileRoutes: RouteRecordRaw[] = [
path: '',
component: () => import('@/views/profile/ProfileView.vue'),
},
{
path: '/redesign',
component: () => import('@/views/profile/ProfilePageV2.vue'),
Comment thread
BatuevIO marked this conversation as resolved.
},
{
path: 'sessions',
component: () => import('@/views/profile/sessions/ProfileSessionsView.vue'),
Expand Down
174 changes: 174 additions & 0 deletions src/views/profile/ProfilePageV2.vue
Comment thread
BatuevIO marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<script setup lang="ts">
import { VuConfig } from '@profcomff/ui-kit';
import UserInfo from '@/modules/profile/ui/UserInfo.vue';
import UserHeader from '@/modules/profile/ui/UserHeader.vue';
import Placeholder from '@/assets/profile_image_placeholder.webp';
import { apiClient, AuthApi, UserdataApi } from '@/api';
import { useRouter, useRoute } from 'vue-router';
import { UserdataArray, UserdataCategoryName, UserdataParams, AchievementGet } from '@/models';
import UserAchievements from '@/modules/profile/ui/UserAchievements.vue';
import IrdomLayout from '@/components/IrdomLayout.vue';
import { useProfileStore } from '@/store/profile';
import { UserdataConverter } from '@/utils/UserdataConverter';
import { ToolbarActionItem } from '@/components/IrdomToolbar.vue';
import { useToolbar } from '@/store/toolbar';
import { onMounted, ref, computed } from 'vue';
import { getPictureUrl } from '@/utils/achievement';

const profileStore = useProfileStore();
const router = useRouter();
const route = useRoute();
const toolbar = useToolbar();

const isOwnProfile = !('id' in route.params) || route.params.id === undefined;

const buttons: ToolbarActionItem[] = [];

if (isOwnProfile) {
buttons.push({
icon: 'settings',
ariaLabel: 'Настройки',
onClick: () => router.push('/profile/settings'),
});
}

toolbar.setup({
title: 'Профиль',
actions: buttons,
});

enum UserdataLoadingState {
Loading = 1,
Ready = 2,
Error = 3,
}

const userdata = ref<UserdataArray>([]);
const userdataLoadingState = ref<UserdataLoadingState>(UserdataLoadingState.Loading);
const fullName = ref('');
const photoUrl = ref('');

const userId = ref<number>(-1);
const achievements = ref<AchievementGet[]>([]);
const achievementsIsLoading = ref(true);

const toolbarAction: ToolbarActionItem[] = [
{
icon: 'edit',
ariaLabel: 'Редактировать профиль',
onClick: () => router.push('/profile/edit'),
},
{
icon: 'settings',
ariaLabel: 'Настройки',
onClick: () => router.push('/profile/settings'),
},
];

const loadUserdata = async () => {
if (!profileStore.token) {
await apiClient.GET('/auth/me');
}

userdataLoadingState.value = UserdataLoadingState.Loading;

const { data: me } = await (isOwnProfile
? AuthApi.getMe(['auth_methods', 'groups', 'indirect_groups', 'session_scopes', 'user_scopes'])
: AuthApi.getById(Number(route.params.id), [
'auth_methods',
'groups',
'indirect_groups',
'scopes',
]));

if (me) {
userId.value = me.id;
getUserInfo();
loadAchievements();
}
};

const getUserInfo = async () => {
const { data } = await UserdataApi.getUser(userId.value);
if (data) {
fullName.value =
data.items.find(
item =>
item.category === UserdataCategoryName.PersonalInfo &&
item.param === UserdataParams.FullName
)?.value ?? 'Безымянный';
photoUrl.value =
data.items.find(
item =>
item.category === UserdataCategoryName.PersonalInfo && item.param === UserdataParams.Photo
)?.value ?? Placeholder;

userdata.value = UserdataConverter.flatToArray(data);
userdataLoadingState.value = UserdataLoadingState.Ready;
} else {
fullName.value = 'Безымянный';
photoUrl.value = Placeholder;
userdataLoadingState.value = UserdataLoadingState.Error;
}
};

const loadAchievements = async () => {
try {
const resp = await apiClient.GET('/achievement/user/{user_id}', {
params: { path: { user_id: userId.value } },
});
if (resp.data) {
achievements.value = resp.data.achievement;
}
} catch (error) {
console.error('Failed to load achievements:', error);
} finally {
achievementsIsLoading.value = false;
}
};

const preparedAchievements = computed(() => {
return achievements.value.map(achievement => ({
id: achievement.id,
name: achievement.name,
description: achievement.description,
picture: getPictureUrl(achievement.picture),
}));
});

onMounted(async () => {
loadUserdata();
});
</script>

<template>
<v-theme-provider :class="VuConfig">
<v-defaults-provider :defaults="VuConfig.defaults">
<IrdomLayout
:toolbar-actions="toolbarAction"
title="Профиль"
class-name="profile-toolbar"
centered-toolbar
>
<div class="pa-3">
<UserHeader :user-name="fullName" :photo-url="photoUrl" />

<UserAchievements
:achievements="preparedAchievements"
:is-loading="achievementsIsLoading"
/>

<div v-if="userdataLoadingState === UserdataLoadingState.Ready">
<h2 class="mb-3 mt-4">Основная информация</h2>
<div v-for="{ name, data } of userdata" :key="name" class="mb-4">
<UserInfo :name="name" :data="data" />
</div>
</div>

<FullscreenLoader v-else-if="userdataLoadingState === UserdataLoadingState.Loading" />
<h2 v-else>Не удалось загрузить данные профиля</h2>
</div>
</IrdomLayout>
</v-defaults-provider>
</v-theme-provider>
</template>
Loading