From f52e06cc90fe660145a037030b9fb1c416985bc2 Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 21 Apr 2026 16:31:06 +0300 Subject: [PATCH 1/4] fix: profile edit UX, news form validation, responsive breakpoints --- .../app/auth/register/register.component.html | 4 +- .../app/auth/register/register.component.scss | 5 ++ .../app/auth/register/register.component.ts | 8 +++ .../courses/detail/course-detail.component.ts | 2 +- .../courses/detail/info/info.component.html | 4 +- .../courses/lesson/lesson.component.html | 4 +- .../features/detail/detail.component.html | 40 +++++------ .../features/detail/detail.component.ts | 32 ++++++++- .../news-form/news-form.component.html | 12 +++- .../news-form/news-form.component.scss | 2 +- .../features/news-form/news-form.component.ts | 7 ++ .../src/app/office/office.component.html | 6 +- .../src/app/office/office.component.scss | 4 ++ .../src/app/office/office.component.ts | 11 ++- .../profile/detail/main/main.component.html | 8 ++- .../profile/detail/main/main.component.ts | 2 +- .../office/profile/edit/edit.component.html | 26 +++---- .../office/profile/edit/edit.component.scss | 14 ++++ .../app/office/profile/edit/edit.component.ts | 67 ++++++++++++++---- .../program/detail/list/list.component.html | 12 ++-- .../program/detail/main/main.component.html | 4 +- .../rating-card/rating-card.component.ts | 2 +- .../skills-group/skills-group.component.scss | 4 ++ .../specializations-group.component.scss | 4 ++ .../ui/components/input/input.component.html | 8 ++- .../ui/components/input/input.component.ts | 18 ++++- .../components/select/select.component.scss | 5 ++ .../textarea/textarea.component.html | 4 +- .../components/textarea/textarea.component.ts | 1 + .../auth/shared/privacy_policy_2022.docx | Bin .../src/styles/_responsive.scss | 17 ++++- 31 files changed, 252 insertions(+), 85 deletions(-) rename "projects/social_platform/src/assets/downloads/auth/shared/\320\237\320\276\320\273\320\270\321\202\320\270\320\272\320\260_\320\276\320\261\321\200\320\260\320\261\320\276\321\202\320\272\320\270_\320\277\320\265\321\200\321\201_\320\264\320\260\320\275\320\275\321\213\321\205_2022.docx" => projects/social_platform/src/assets/downloads/auth/shared/privacy_policy_2022.docx (100%) diff --git a/projects/social_platform/src/app/auth/register/register.component.html b/projects/social_platform/src/app/auth/register/register.component.html index f3ea50f77..323d89c8e 100644 --- a/projects/social_platform/src/app/auth/register/register.component.html +++ b/projects/social_platform/src/app/auth/register/register.component.html @@ -267,8 +267,8 @@ я прочитал соглашение и даю согласие на - обработку персональных данныхобработку персональных данных diff --git a/projects/social_platform/src/app/auth/register/register.component.scss b/projects/social_platform/src/app/auth/register/register.component.scss index 46fd5e3fb..7fd2e2a82 100644 --- a/projects/social_platform/src/app/auth/register/register.component.scss +++ b/projects/social_platform/src/app/auth/register/register.component.scss @@ -6,6 +6,11 @@ .register { height: 100vh; + &__policy-link { + color: var(--accent); + cursor: pointer; + } + &__agreement { display: flex; align-items: center; diff --git a/projects/social_platform/src/app/auth/register/register.component.ts b/projects/social_platform/src/app/auth/register/register.component.ts index 060f7c13c..f3637b743 100644 --- a/projects/social_platform/src/app/auth/register/register.component.ts +++ b/projects/social_platform/src/app/auth/register/register.component.ts @@ -107,6 +107,14 @@ export class RegisterComponent implements OnInit { } } + downloadPolicy(event: Event): void { + event.stopPropagation(); + const link = document.createElement("a"); + link.href = "/assets/downloads/auth/shared/privacy_policy_2022.docx"; + link.download = "Политика обработки персональных данных 2022.docx"; + link.click(); + } + onSendForm(): void { if (!this.validationService.getFormValidation(this.registerForm)) { return; diff --git a/projects/social_platform/src/app/office/courses/detail/course-detail.component.ts b/projects/social_platform/src/app/office/courses/detail/course-detail.component.ts index 3d3b5beee..2b27b2fbc 100644 --- a/projects/social_platform/src/app/office/courses/detail/course-detail.component.ts +++ b/projects/social_platform/src/app/office/courses/detail/course-detail.component.ts @@ -68,7 +68,7 @@ export class CourseDetailComponent implements OnInit { } get isMobile(): boolean { - return this.appWidth < 920; + return this.appWidth < 1000; } get showCover(): boolean { diff --git a/projects/social_platform/src/app/office/courses/detail/info/info.component.html b/projects/social_platform/src/app/office/courses/detail/info/info.component.html index 2b6776ff7..1788bf368 100644 --- a/projects/social_platform/src/app/office/courses/detail/info/info.component.html +++ b/projects/social_platform/src/app/office/courses/detail/info/info.component.html @@ -4,7 +4,7 @@
- @if (appWidth > 920) { + @if (appWidth > 1000) {
- @if (appWidth > 920) { + @if (appWidth > 1000) {
diff --git a/projects/social_platform/src/app/office/courses/lesson/lesson.component.html b/projects/social_platform/src/app/office/courses/lesson/lesson.component.html index fdc3ea4a9..3673f9d43 100644 --- a/projects/social_platform/src/app/office/courses/lesson/lesson.component.html +++ b/projects/social_platform/src/app/office/courses/lesson/lesson.component.html @@ -2,8 +2,8 @@ @if (lessonInfo()) {
-
- @if (appWidth >= 920) { +
+ @if (appWidth >= 1000) {

модуль {{ lessonInfo()?.moduleOrder }}

урок {{ lessonOrder() }}

diff --git a/projects/social_platform/src/app/office/features/detail/detail.component.html b/projects/social_platform/src/app/office/features/detail/detail.component.html index 0b903f04b..dce3027fe 100644 --- a/projects/social_platform/src/app/office/features/detail/detail.component.html +++ b/projects/social_platform/src/app/office/features/detail/detail.component.html @@ -5,7 +5,7 @@ @if (info()) {
- @if (appWidth > 920) { + @if (appWidth > 1000) { @@ -74,7 +74,7 @@ @if (userType() !== undefined) { @if ((isProjectsPage || isProjectsRatingPage) && appWidth - < 920) { + < 1000) { } } @if (isUserMember && !isUserManager && !isUserExpert) { оценка проектов @@ -160,10 +160,10 @@ - @if (appWidth > 920) { + @if (appWidth > 1000) { {{ info().name.includes("Технолидеры Будущего") ? "Каталог лучших стартапов 2022/23" @@ -171,7 +171,7 @@ ? "аналитика" : "положение" }} - } @if (isUserManager && appWidth > 920) { + } @if (isUserManager && appWidth > 1000) { } - @if (appWidth > 920) { + @if (appWidth > 1000) {
- } @if (appWidth < 920) { + } @if (appWidth < 1000) { - } @if (isUserManager || (isUserExpert && appWidth > 920)) { + } @if (isUserManager || (isUserExpert && appWidth > 1000)) { - } @if (!isUserManager && !isUserExpert && appWidth > 920) { + } @if (!isUserManager && !isUserExpert && appWidth > 1000) { - } @if (isUserManager || (isUserExpert && appWidth > 920)) { + } @if (isUserManager || (isUserExpert && appWidth > 1000)) { - } @if (appWidth < 920) { + } @if (appWidth < 1000) { - {{ appWidth > 920 ? "перейти в курс" : "курс" }} + {{ appWidth > 1000 ? "перейти в курс" : "курс" }} } diff --git a/projects/social_platform/src/app/office/features/detail/detail.component.ts b/projects/social_platform/src/app/office/features/detail/detail.component.ts index cb9aa73cb..59d19c8fb 100644 --- a/projects/social_platform/src/app/office/features/detail/detail.component.ts +++ b/projects/social_platform/src/app/office/features/detail/detail.component.ts @@ -539,8 +539,16 @@ export class DeatilComponent implements OnInit, OnDestroy { .subscribe({ next: user => { this.info.set(user); - this.isProfileFill = - user.progress! < 100 ? (this.isProfileFill = true) : (this.isProfileFill = false); + if ( + user.id !== undefined && + user.progress! < 100 && + !this.hasSeenProfileFillModal(user.id) + ) { + this.isProfileFill = true; + this.markSeenProfileFillModal(user.id); + } else { + this.isProfileFill = false; + } }, }); @@ -556,6 +564,26 @@ export class DeatilComponent implements OnInit, OnDestroy { } } + private getProfileFillSeenKey(userId: number): string { + return `profile_${userId}_fill_modal_seen`; + } + + private hasSeenProfileFillModal(userId: number): boolean { + try { + return !!localStorage.getItem(this.getProfileFillSeenKey(userId)); + } catch (e) { + return false; + } + } + + private markSeenProfileFillModal(userId: number): void { + try { + localStorage.setItem(this.getProfileFillSeenKey(userId), "1"); + } catch (e) { + // ignore storage errors + } + } + private isInProfileInfo(): void { const profileInfoSub$ = this.authService.profile.subscribe({ next: profile => { diff --git a/projects/social_platform/src/app/office/features/news-form/news-form.component.html b/projects/social_platform/src/app/office/features/news-form/news-form.component.html index 6199dbf03..aa1090759 100644 --- a/projects/social_platform/src/app/office/features/news-form/news-form.component.html +++ b/projects/social_platform/src/app/office/features/news-form/news-form.component.html @@ -10,9 +10,17 @@ placeholder="сегодня был хороший день" (paste)="onPaste($event)" (keydown.meta.enter)="onSubmit()" - [maxLength]="15940" + [maxLength]="maxTextLength" > - +
@for (i of imagesList; track i.id) { diff --git a/projects/social_platform/src/app/office/features/news-form/news-form.component.scss b/projects/social_platform/src/app/office/features/news-form/news-form.component.scss index 668815fb7..2c5205a79 100644 --- a/projects/social_platform/src/app/office/features/news-form/news-form.component.scss +++ b/projects/social_platform/src/app/office/features/news-form/news-form.component.scss @@ -6,7 +6,7 @@ &__row { display: flex; - align-items: center; + align-items: flex-start; ::ng-deep app-textarea { textarea { diff --git a/projects/social_platform/src/app/office/features/news-form/news-form.component.ts b/projects/social_platform/src/app/office/features/news-form/news-form.component.ts index 6cb0546d6..35be216ed 100644 --- a/projects/social_platform/src/app/office/features/news-form/news-form.component.ts +++ b/projects/social_platform/src/app/office/features/news-form/news-form.component.ts @@ -63,11 +63,18 @@ export class NewsFormComponent implements OnInit { messageForm: FormGroup; + readonly maxTextLength = 15940; + + get isTextOverflow(): boolean { + return (this.messageForm.get("text")?.value?.length ?? 0) > this.maxTextLength; + } + /** * Обработчик отправки формы * Валидирует форму и эмитит событие с данными новости */ onSubmit() { + if (this.isTextOverflow) return; if (!this.validationService.getFormValidation(this.messageForm)) { return; } diff --git a/projects/social_platform/src/app/office/office.component.html b/projects/social_platform/src/app/office/office.component.html index 5c6330611..f7c323e53 100644 --- a/projects/social_platform/src/app/office/office.component.html +++ b/projects/social_platform/src/app/office/office.component.html @@ -19,8 +19,10 @@ >

Платформа создана компанией ООО «Молодежный форсайт»

-

Политика обработки персональных данных

-

2022

+ + Политика обработки персональных данных + +

{{ currentYear() }}

diff --git a/projects/social_platform/src/app/office/office.component.scss b/projects/social_platform/src/app/office/office.component.scss index f1e93123b..db6fd13bf 100644 --- a/projects/social_platform/src/app/office/office.component.scss +++ b/projects/social_platform/src/app/office/office.component.scss @@ -128,6 +128,10 @@ } } + &__policy-link { + cursor: pointer; + } + &__header { display: none; background-color: var(--white); diff --git a/projects/social_platform/src/app/office/office.component.ts b/projects/social_platform/src/app/office/office.component.ts index e3dbb6063..4f3ae3128 100644 --- a/projects/social_platform/src/app/office/office.component.ts +++ b/projects/social_platform/src/app/office/office.component.ts @@ -14,7 +14,7 @@ import { ButtonComponent } from "@ui/components"; import { ModalComponent } from "@ui/components/modal/modal.component"; import { NavComponent } from "./features/nav/nav.component"; import { ProfileControlPanelComponent, SidebarComponent } from "@uilib"; -import { AsyncPipe } from "@angular/common"; +import { AsyncPipe, CommonModule } from "@angular/common"; import { InviteService } from "@services/invite.service"; import { toSignal } from "@angular/core/rxjs-interop"; import { ProgramSidebarCardComponent } from "./features/program-sidebar-card/program-sidebar-card.component"; @@ -39,6 +39,7 @@ import { Program } from "./program/models/program.model"; styleUrl: "./office.component.scss", standalone: true, imports: [ + CommonModule, SidebarComponent, NavComponent, RouterOutlet, @@ -71,6 +72,7 @@ export class OfficeComponent implements OnInit, OnDestroy { ); profile?: User; + protected currentYear = signal(new Date().getFullYear()); waitVerificationModal = false; waitVerificationAccepted = false; @@ -148,6 +150,13 @@ export class OfficeComponent implements OnInit, OnDestroy { this.subscriptions$.forEach($ => $.unsubscribe()); } + downloadPolicy(): void { + const link = document.createElement("a"); + link.href = "/assets/downloads/auth/shared/privacy_policy_2022.docx"; + link.download = "Политика обработки персональных данных 2022.docx"; + link.click(); + } + onAcceptWaitVerification() { this.waitVerificationAccepted = true; localStorage.setItem("waitVerificationAccepted", "true"); diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.html b/projects/social_platform/src/app/office/profile/detail/main/main.component.html index a01c14966..e0ebf080a 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.html +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.html @@ -243,7 +243,7 @@

образование

- {{ education.organizationName | truncate: 30 }} + {{ education.organizationName | truncate: 20 }}

работа

- {{ workExperience.organizationName | truncate: 30 }} + {{ workExperience.organizationName | truncate: 20 }}

- {{ workExperience.jobPosition }} + {{ + workExperience.jobPosition | truncate: 30 + }} подробнее !Array.isArray(item.about) || item.about.length > 0); } this.user = user as User; }, diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.html b/projects/social_platform/src/app/office/profile/edit/edit.component.html index 5ed010191..7b735dbfd 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.html +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.html @@ -196,8 +196,7 @@

редактирование профиля

size="big" id="birthday" [error]="birthday | controlError" - type="text" - mask="00.00.0000" + type="date" formControlName="birthday" > @if (birthday | controlError: "required") { @@ -322,22 +321,15 @@

редактирование профиля

@if (profileForm.get("phoneNumber"); as phoneNumber) {
- + - @if (phoneNumber | controlError: "required") { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
- }
}
@@ -495,7 +487,7 @@

редактирование профиля

track $index) {

- {{ educationItem.organizationName }} + {{ educationItem.organizationName | truncate: 50 }}

@@ -509,7 +501,7 @@

редактирование профиля

- {{ educationItem.description }} + {{ educationItem.description | truncate: 150 }}

@@ -671,7 +663,7 @@

редактирование профиля

track $index) {

- {{ workItem.organizationName }} + {{ workItem.organizationName | truncate: 50 }}

@@ -685,11 +677,11 @@

редактирование профиля

- {{ workItem.description }} + {{ workItem.description | truncate: 150 }}

- {{ workItem.jobPosition }} + {{ workItem.jobPosition | truncate: 50 }}

@@ -817,7 +809,7 @@

редактирование профиля

- {{ achievementItem.title }} + {{ achievementItem.title | truncate: 50 }}

@@ -825,7 +817,7 @@

редактирование профиля

- {{ achievementItem.status }} + {{ achievementItem.status | truncate: 150 }}

@if (achievementItem.files?.length) { @if (isStringFiles(achievementItem.files)) { diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.scss b/projects/social_platform/src/app/office/profile/edit/edit.component.scss index 86a68799c..5bbbe2e82 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.scss +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.scss @@ -352,6 +352,20 @@ } } +.profile__column { + fieldset { + position: relative; + padding-bottom: 20px; + + .error { + position: absolute; + bottom: 2px; + left: 0; + margin-top: 0; + } + } +} + .cancel { display: flex; flex-direction: column; diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.ts b/projects/social_platform/src/app/office/profile/edit/edit.component.ts index 782789f37..371137c37 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.ts +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.ts @@ -22,8 +22,6 @@ import { ButtonComponent, IconComponent, InputComponent, SelectComponent } from import { ControlErrorPipe, ValidationService } from "projects/core"; import { concatMap, first, map, noop, Observable, skip, Subscription } from "rxjs"; import { ActivatedRoute, Router, RouterModule } from "@angular/router"; -import * as dayjs from "dayjs"; -import * as cpf from "dayjs/plugin/customParseFormat"; import { NavService } from "@services/nav.service"; import { EditorSubmitButtonDirective } from "@ui/directives/editor-submit-button.directive"; import { TextareaComponent } from "@ui/components/textarea/textarea.component"; @@ -53,8 +51,7 @@ import { generateOptionsList } from "@utils/generate-options-list"; import { UploadFileComponent } from "@ui/components/upload-file/upload-file.component"; import { navProfileItems } from "projects/core/src/consts/navigation/nav-profile-items.const"; import { FileItemComponent } from "@ui/components/file-item/file-item.component"; - -dayjs.extend(cpf); +import { TruncatePipe } from "projects/core/src/lib/pipes/truncate.pipe"; /** * Компонент редактирования профиля пользователя @@ -103,6 +100,7 @@ dayjs.extend(cpf); RouterModule, UploadFileComponent, FileItemComponent, + TruncatePipe, ], }) export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { @@ -225,7 +223,7 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { lastName: profile.lastName ?? "", email: profile.email ?? "", userType: profile.userType ?? 1, - birthday: profile.birthday ? dayjs(profile.birthday).format("DD.MM.YYYY") : "", + birthday: profile.birthday ?? "", city: profile.city ?? "", coverImageAddress: profile.coverImageAddress ?? "", phoneNumber: profile.phoneNumber ?? "", @@ -662,6 +660,18 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { return; } + const educationOverflow = + (this.profileForm.get("organizationName")?.value?.length ?? 0) > 100 || + (this.profileForm.get("description")?.value?.length ?? 0) > 400; + + if (educationOverflow) { + this.isModalErrorSkillsChoose.set(true); + this.isModalErrorSkillChooseText.set( + "Превышено допустимое количество символов в одном из полей" + ); + return; + } + ["organizationName", "educationStatus"].forEach(name => this.profileForm.get(name)?.clearValidators() ); @@ -802,6 +812,18 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { return; } + const workOverflow = + (this.profileForm.get("organization")?.value?.length ?? 0) > 50 || + (this.profileForm.get("descriptionWork")?.value?.length ?? 0) > 400; + + if (workOverflow) { + this.isModalErrorSkillsChoose.set(true); + this.isModalErrorSkillChooseText.set( + "Превышено допустимое количество символов в одном из полей" + ); + return; + } + ["organization", "jobPosition"].forEach(name => this.profileForm.get(name)?.clearValidators()); ["organization", "jobPosition"].forEach(name => this.profileForm.get(name)?.setValidators([Validators.required]) @@ -1068,6 +1090,28 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { } }); + const lengthLimits: { field: string; limit: number }[] = [ + { field: "city", limit: 100 }, + { field: "aboutMe", limit: 300 }, + { field: "organizationName", limit: 100 }, + { field: "description", limit: 400 }, + { field: "organization", limit: 50 }, + { field: "descriptionWork", limit: 400 }, + ]; + + const hasLengthOverflow = lengthLimits.some(({ field, limit }) => { + const value = this.profileForm.get(field)?.value; + return typeof value === "string" && value.length > limit; + }); + + if (hasLengthOverflow) { + this.isModalErrorSkillsChoose.set(true); + this.isModalErrorSkillChooseText.set( + "Превышено допустимое количество символов в одном из полей" + ); + return; + } + const mainFieldsValid = ["firstName", "lastName", "birthday", "speciality", "city"].every( name => this.profileForm.get(name)?.valid ); @@ -1099,18 +1143,13 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { achievements, [this.userTypeMap[this.profileForm.value.userType]]: this.typeSpecific.value, typeSpecific: undefined, - birthday: this.profileForm.value.birthday - ? dayjs(this.profileForm.value.birthday, "DD.MM.YYYY").format("YYYY-MM-DD") - : undefined, + birthday: this.profileForm.value.birthday || undefined, skillsIds: this.profileForm.value.skills.map((s: Skill) => s.id), - phoneNumber: - typeof this.profileForm.value.phoneNumber === "string" - ? this.profileForm.value.phoneNumber.replace(/^([87])/, "+7") - : this.profileForm.value.phoneNumber, + phoneNumber: this.profileForm.value.phoneNumber + ? this.profileForm.value.phoneNumber.replace(/^\+?[87]/, "+7") + : null, }; - console.log(newProfile); - this.authService .saveProfile(newProfile) .pipe(concatMap(() => this.authService.getProfile())) diff --git a/projects/social_platform/src/app/office/program/detail/list/list.component.html b/projects/social_platform/src/app/office/program/detail/list/list.component.html index e7bb3199f..d351fed49 100644 --- a/projects/social_platform/src/app/office/program/detail/list/list.component.html +++ b/projects/social_platform/src/app/office/program/detail/list/list.component.html @@ -4,7 +4,7 @@ class="page" #listRoot [style.grid-template-columns]=" - appWidth < 920 ? '1fr' : listType === 'members' ? '10fr' : '8fr 2fr' + appWidth < 1000 ? '1fr' : listType === 'members' ? '10fr' : '8fr 2fr' " >
@@ -14,7 +14,7 @@
- @if (appWidth < 920 && listType !== 'members') { + @if (appWidth < 1000 && listType !== 'members') {
@@ -67,7 +67,7 @@
- } @else { @if (appWidth > 920) { + } @else { @if (appWidth > 1000) {
- @if (appWidth >= 920 && listType !== 'members') { + @if (appWidth >= 1000 && listType !== 'members') {
@if (listType === 'projects' || listType === 'rating') { @@ -145,7 +145,7 @@
- } @else { @if (appWidth > 920) { + } @else { @if (appWidth > 1000) {
- } @if (listType !== 'members' && listType !== 'projects' && appWidth >= 920) { + } @if (listType !== 'members' && listType !== 'projects' && appWidth >= 1000) {
diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.html b/projects/social_platform/src/app/office/program/detail/main/main.component.html index 6e3c93dcf..7f0a160d4 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.html +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.html @@ -10,7 +10,7 @@
} @else {
- @if (appWidth > 920) { + @if (appWidth > 1000) {
- @if (appWidth > 920) { + @if (appWidth > 1000) {
@if (program.isUserMember && program.links?.length) {