diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index f3537c4f8..f08ca7a23 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -107,18 +107,14 @@ export const routes: Routes = [ loadChildren: () => import('./features/registries/registries.routes').then((mod) => mod.registriesRoutes), }, { - path: 'my-profile', - loadComponent: () => - import('./features/profile/pages/my-profile/my-profile.component').then((mod) => mod.MyProfileComponent), + path: 'profile', + loadComponent: () => import('./features/profile/profile.component').then((mod) => mod.ProfileComponent), providers: [provideStates([ProfileState])], canActivate: [authGuard], }, { path: 'user/:id', - loadComponent: () => - import('./features/profile/pages/user-profile/user-profile.component').then( - (mod) => mod.UserProfileComponent - ), + loadComponent: () => import('./features/profile/profile.component').then((mod) => mod.ProfileComponent), providers: [provideStates([ProfileState])], }, { diff --git a/src/app/core/components/header/header.component.ts b/src/app/core/components/header/header.component.ts index a599fcefd..52f04a1fd 100644 --- a/src/app/core/components/header/header.component.ts +++ b/src/app/core/components/header/header.component.ts @@ -29,7 +29,7 @@ export class HeaderComponent { items = [ { label: 'navigation.myProfile', - command: () => this.router.navigate(['my-profile']), + command: () => this.router.navigate(['profile']), }, { label: 'navigation.settings', command: () => this.router.navigate(['settings']) }, { diff --git a/src/app/core/constants/nav-items.constant.ts b/src/app/core/constants/nav-items.constant.ts index 5537cda8a..b5b1d2fc4 100644 --- a/src/app/core/constants/nav-items.constant.ts +++ b/src/app/core/constants/nav-items.constant.ts @@ -352,7 +352,7 @@ export const MENU_ITEMS: MenuItem[] = [ }, { id: 'my-profile', - routerLink: '/my-profile', + routerLink: '/profile', label: 'navigation.profile', icon: 'fas fa-user', visible: false, diff --git a/src/app/core/store/user/user.actions.ts b/src/app/core/store/user/user.actions.ts index b76a8b834..ca346fc2c 100644 --- a/src/app/core/store/user/user.actions.ts +++ b/src/app/core/store/user/user.actions.ts @@ -1,4 +1,4 @@ -import { Education, Employment, Social, User, UserSettings } from '@osf/shared/models'; +import { Education, Employment, SocialModel, User, UserSettings } from '@osf/shared/models'; export class GetCurrentUser { static readonly type = '[User] Get Current User'; @@ -36,7 +36,7 @@ export class UpdateProfileSettingsEducation { export class UpdateProfileSettingsSocialLinks { static readonly type = '[Profile Settings] Update Social Links'; - constructor(public payload: Partial[]) {} + constructor(public payload: Partial[]) {} } export class UpdateProfileSettingsUser { diff --git a/src/app/core/store/user/user.selectors.ts b/src/app/core/store/user/user.selectors.ts index 5aad4c852..096406d80 100644 --- a/src/app/core/store/user/user.selectors.ts +++ b/src/app/core/store/user/user.selectors.ts @@ -1,6 +1,6 @@ import { Selector } from '@ngxs/store'; -import { Education, Employment, Social, User, UserSettings } from '@osf/shared/models'; +import { Education, Employment, SocialModel, User, UserSettings } from '@osf/shared/models'; import { UserStateModel } from './user.model'; import { UserState } from './user.state'; @@ -54,7 +54,7 @@ export class UserSelectors { } @Selector([UserState]) - static getSocialLinks(state: UserStateModel): Social | undefined { + static getSocialLinks(state: UserStateModel): SocialModel | undefined { return state.currentUser.data?.social; } diff --git a/src/app/core/store/user/user.state.ts b/src/app/core/store/user/user.state.ts index 8404141ec..93d135ef6 100644 --- a/src/app/core/store/user/user.state.ts +++ b/src/app/core/store/user/user.state.ts @@ -8,7 +8,7 @@ import { inject, Injectable } from '@angular/core'; import { ProfileSettingsKey } from '@osf/shared/enums'; import { removeNullable } from '@osf/shared/helpers'; import { UserMapper } from '@osf/shared/mappers'; -import { Social, User } from '@osf/shared/models'; +import { SocialModel, User } from '@osf/shared/models'; import { UserService } from '../../services'; @@ -210,7 +210,7 @@ export class UserState { return; } - let social = {} as Partial; + let social = {} as Partial; payload.forEach((item) => { social = { diff --git a/src/app/features/moderation/models/registry-json-api.model.ts b/src/app/features/moderation/models/registry-json-api.model.ts index 0b47f96df..ebd568026 100644 --- a/src/app/features/moderation/models/registry-json-api.model.ts +++ b/src/app/features/moderation/models/registry-json-api.model.ts @@ -1,24 +1,13 @@ -import { RegistrationReviewStates, RevisionReviewStates } from '@osf/shared/enums'; -import { ResponseJsonApi } from '@osf/shared/models'; +import { RegistrationNodeAttributesJsonApi, ResponseJsonApi } from '@osf/shared/models'; export type RegistryResponseJsonApi = ResponseJsonApi; export interface RegistryDataJsonApi { id: string; - attributes: RegistryAttributesJsonApi; + attributes: RegistrationNodeAttributesJsonApi; embeds: RegistryEmbedsJsonApi; } -export interface RegistryAttributesJsonApi { - id: string; - title: string; - revision_state: RevisionReviewStates; - reviews_state: RegistrationReviewStates; - public: boolean; - embargoed: boolean; - embargo_end_date: string; -} - export interface RegistryEmbedsJsonApi { schema_responses: { data: { id: string }[]; diff --git a/src/app/features/moderation/models/registry-moderation.model.ts b/src/app/features/moderation/models/registry-moderation.model.ts index 9e47b8d81..1a5054ec0 100644 --- a/src/app/features/moderation/models/registry-moderation.model.ts +++ b/src/app/features/moderation/models/registry-moderation.model.ts @@ -9,7 +9,7 @@ export interface RegistryModeration { reviewsState: RegistrationReviewStates; public: boolean; embargoed: boolean; - embargoEndDate?: string; + embargoEndDate: string | null; actions: ReviewAction[]; revisionId?: string | null; } diff --git a/src/app/features/profile/components/profile-information/profile-information.component.html b/src/app/features/profile/components/profile-information/profile-information.component.html index 37308942a..cf79c532d 100644 --- a/src/app/features/profile/components/profile-information/profile-information.component.html +++ b/src/app/features/profile/components/profile-information/profile-information.component.html @@ -21,7 +21,7 @@

{{ currentUser()?.fullName }}

-
+
@if (currentUser()?.social?.orcid) {
@@ -61,54 +61,9 @@

}
- @if (currentUser()?.social?.github) { - - github - - } - @if (currentUser()?.social?.scholar) { - - scholar - - } - @if (currentUser()?.social?.twitter) { - - x(twitter) - - } - @if (currentUser()?.social?.linkedIn) { - - linkedin - - } - @if (currentUser()?.social?.impactStory) { - - impactstory - - } - @if (currentUser()?.social?.baiduScholar) { - - baidu - - } - @if (currentUser()?.social?.researchGate) { - - researchGate - - } - @if (currentUser()?.social?.researcherId) { - - researchId - - } - @if (currentUser()?.social?.ssrn) { - - ssrn - - } - @if (currentUser()?.social?.academiaProfileID) { - - academia + @for (social of userSocials(); track social.alt) { + + }
diff --git a/src/app/features/profile/components/profile-information/profile-information.component.ts b/src/app/features/profile/components/profile-information/profile-information.component.ts index e1fbc0b7d..404d2d05e 100644 --- a/src/app/features/profile/components/profile-information/profile-information.component.ts +++ b/src/app/features/profile/components/profile-information/profile-information.component.ts @@ -7,9 +7,12 @@ import { ChangeDetectionStrategy, Component, computed, inject, input, output } f import { toSignal } from '@angular/core/rxjs-interop'; import { EducationHistoryComponent, EmploymentHistoryComponent } from '@osf/shared/components'; +import { SOCIAL_LINKS } from '@osf/shared/constants'; import { IS_MEDIUM } from '@osf/shared/helpers'; import { User } from '@osf/shared/models'; +import { mapUserSocials } from '../../helpers'; + @Component({ selector: 'osf-profile-information', imports: [Button, EmploymentHistoryComponent, EducationHistoryComponent, TranslatePipe, DatePipe, NgOptimizedImage], @@ -28,6 +31,8 @@ export class ProfileInformationComponent { () => this.currentUser()?.employment?.length || this.currentUser()?.education?.length ); + userSocials = computed(() => mapUserSocials(this.currentUser()?.social, SOCIAL_LINKS)); + toProfileSettings() { this.editProfile.emit(); } diff --git a/src/app/features/profile/helpers/index.ts b/src/app/features/profile/helpers/index.ts new file mode 100644 index 000000000..01a1e8a30 --- /dev/null +++ b/src/app/features/profile/helpers/index.ts @@ -0,0 +1 @@ +export * from './user-socials.helper'; diff --git a/src/app/features/profile/helpers/user-socials.helper.ts b/src/app/features/profile/helpers/user-socials.helper.ts new file mode 100644 index 000000000..e4fd979f1 --- /dev/null +++ b/src/app/features/profile/helpers/user-socials.helper.ts @@ -0,0 +1,39 @@ +import { SocialLinksModel, SocialLinkViewModel, SocialModel } from '@osf/shared/models'; + +export function mapUserSocials( + socialData: SocialModel | undefined, + socialLinks: SocialLinksModel[] +): SocialLinkViewModel[] { + if (!socialData) { + return []; + } + + return socialLinks.reduce((acc, social) => { + const socialValue = socialData[social.key]; + + if (!socialValue || (Array.isArray(socialValue) && socialValue.length === 0)) { + return acc; + } + + let url; + if (social.linkedField) { + const linkedValue = socialData[social.linkedField.key]; + if (linkedValue) { + url = `${social.address}${socialValue}${social.linkedField.address}${linkedValue}`; + } + } else { + const value = Array.isArray(socialValue) ? socialValue[0] : socialValue; + url = social.address + value; + } + + if (url && social.key !== 'profileWebsites') { + acc.push({ + url, + icon: `assets/icons/socials/${social.icon}`, + alt: social.label, + }); + } + + return acc; + }, []); +} diff --git a/src/app/features/profile/pages/my-profile/my-profile.component.html b/src/app/features/profile/pages/my-profile/my-profile.component.html deleted file mode 100644 index 013f43e57..000000000 --- a/src/app/features/profile/pages/my-profile/my-profile.component.html +++ /dev/null @@ -1,7 +0,0 @@ - - -@if (currentUser()) { -
- -
-} diff --git a/src/app/features/profile/pages/my-profile/my-profile.component.spec.ts b/src/app/features/profile/pages/my-profile/my-profile.component.spec.ts deleted file mode 100644 index 3e3efec9a..000000000 --- a/src/app/features/profile/pages/my-profile/my-profile.component.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { GlobalSearchComponent } from '@osf/shared/components'; - -import { ProfileInformationComponent } from '../../components'; - -import { MyProfileComponent } from './my-profile.component'; - -describe.skip('MyProfileComponent', () => { - let component: MyProfileComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [MyProfileComponent, [ProfileInformationComponent, GlobalSearchComponent]], - }).compileComponents(); - - fixture = TestBed.createComponent(MyProfileComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/features/profile/pages/my-profile/my-profile.component.ts b/src/app/features/profile/pages/my-profile/my-profile.component.ts deleted file mode 100644 index 358adfbe1..000000000 --- a/src/app/features/profile/pages/my-profile/my-profile.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { createDispatchMap, select } from '@ngxs/store'; - -import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; - -import { UserSelectors } from '@core/store/user'; -import { GlobalSearchComponent } from '@osf/shared/components'; -import { SEARCH_TAB_OPTIONS } from '@osf/shared/constants'; -import { ResourceType } from '@osf/shared/enums'; -import { SetDefaultFilterValue, UpdateFilterValue } from '@osf/shared/stores/global-search'; - -import { ProfileInformationComponent } from '../../components'; -import { SetUserProfile } from '../../store'; - -@Component({ - selector: 'osf-my-profile', - imports: [ProfileInformationComponent, GlobalSearchComponent], - templateUrl: './my-profile.component.html', - styleUrl: './my-profile.component.scss', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class MyProfileComponent implements OnInit { - private router = inject(Router); - private actions = createDispatchMap({ - setUserProfile: SetUserProfile, - updateFilterValue: UpdateFilterValue, - setDefaultFilterValue: SetDefaultFilterValue, - }); - - currentUser = select(UserSelectors.getCurrentUser); - - resourceTabOptions = SEARCH_TAB_OPTIONS.filter((x) => x.value !== ResourceType.Agent); - - ngOnInit(): void { - const user = this.currentUser(); - if (user) { - this.actions.setDefaultFilterValue('creator', user.iri!); - } - } - - toProfileSettings() { - this.router.navigate(['settings/profile']); - } -} diff --git a/src/app/features/profile/pages/user-profile/user-profile.component.html b/src/app/features/profile/pages/user-profile/user-profile.component.html deleted file mode 100644 index c62db7f8d..000000000 --- a/src/app/features/profile/pages/user-profile/user-profile.component.html +++ /dev/null @@ -1,11 +0,0 @@ -@if (isUserLoading()) { - -} @else { - @if (currentUser()) { - - -
- -
- } -} diff --git a/src/app/features/profile/pages/user-profile/user-profile.component.scss b/src/app/features/profile/pages/user-profile/user-profile.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/app/features/profile/pages/user-profile/user-profile.component.spec.ts b/src/app/features/profile/pages/user-profile/user-profile.component.spec.ts deleted file mode 100644 index b357490cf..000000000 --- a/src/app/features/profile/pages/user-profile/user-profile.component.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { MockComponents } from 'ng-mocks'; - -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { GlobalSearchComponent, LoadingSpinnerComponent } from '@osf/shared/components'; - -import { ProfileInformationComponent } from '../../components'; - -import { UserProfileComponent } from './user-profile.component'; - -describe.skip('UserProfileComponent', () => { - let component: UserProfileComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ - UserProfileComponent, - ...MockComponents(ProfileInformationComponent, GlobalSearchComponent, LoadingSpinnerComponent), - ], - }).compileComponents(); - - fixture = TestBed.createComponent(UserProfileComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/features/profile/pages/user-profile/user-profile.component.ts b/src/app/features/profile/pages/user-profile/user-profile.component.ts deleted file mode 100644 index e34b0baef..000000000 --- a/src/app/features/profile/pages/user-profile/user-profile.component.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { createDispatchMap, select } from '@ngxs/store'; - -import { ChangeDetectionStrategy, Component, HostBinding, inject, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; - -import { GlobalSearchComponent, LoadingSpinnerComponent } from '@osf/shared/components'; -import { SEARCH_TAB_OPTIONS } from '@osf/shared/constants'; -import { ResourceType } from '@osf/shared/enums'; -import { SetDefaultFilterValue } from '@osf/shared/stores/global-search'; - -import { ProfileInformationComponent } from '../../components'; -import { FetchUserProfile, ProfileSelectors } from '../../store'; - -@Component({ - selector: 'osf-user-profile', - imports: [ProfileInformationComponent, GlobalSearchComponent, LoadingSpinnerComponent], - templateUrl: './user-profile.component.html', - styleUrl: './user-profile.component.scss', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class UserProfileComponent implements OnInit { - @HostBinding('class') classes = 'flex-1'; - - private route = inject(ActivatedRoute); - private actions = createDispatchMap({ - fetchUserProfile: FetchUserProfile, - setDefaultFilterValue: SetDefaultFilterValue, - }); - - currentUser = select(ProfileSelectors.getUserProfile); - isUserLoading = select(ProfileSelectors.isUserProfileLoading); - - resourceTabOptions = SEARCH_TAB_OPTIONS.filter((x) => x.value !== ResourceType.Agent); - - ngOnInit(): void { - const userId = this.route.snapshot.params['id']; - - if (userId) { - this.actions.fetchUserProfile(userId).subscribe({ - next: () => { - this.actions.setDefaultFilterValue('creator', this.currentUser()!.iri!); - }, - }); - } - } -} diff --git a/src/app/features/profile/profile.component.html b/src/app/features/profile/profile.component.html new file mode 100644 index 000000000..1df872629 --- /dev/null +++ b/src/app/features/profile/profile.component.html @@ -0,0 +1,11 @@ +@if (isUserLoading() && !isMyProfile()) { + +} @else { + @if (user()) { + + +
+ +
+ } +} diff --git a/src/app/features/profile/pages/my-profile/my-profile.component.scss b/src/app/features/profile/profile.component.scss similarity index 100% rename from src/app/features/profile/pages/my-profile/my-profile.component.scss rename to src/app/features/profile/profile.component.scss diff --git a/src/app/features/profile/profile.component.spec.ts b/src/app/features/profile/profile.component.spec.ts new file mode 100644 index 000000000..f50fed4e1 --- /dev/null +++ b/src/app/features/profile/profile.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GlobalSearchComponent } from '@osf/shared/components'; + +import { ProfileInformationComponent } from './components'; +import { ProfileComponent } from './profile.component'; + +describe.skip('ProfileComponent', () => { + let component: ProfileComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProfileComponent, [ProfileInformationComponent, GlobalSearchComponent]], + }).compileComponents(); + + fixture = TestBed.createComponent(ProfileComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/profile/profile.component.ts b/src/app/features/profile/profile.component.ts new file mode 100644 index 000000000..3720c18c0 --- /dev/null +++ b/src/app/features/profile/profile.component.ts @@ -0,0 +1,78 @@ +import { createDispatchMap, select } from '@ngxs/store'; + +import { ChangeDetectionStrategy, Component, computed, DestroyRef, inject, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { ActivatedRoute, Router } from '@angular/router'; + +import { UserSelectors } from '@core/store/user'; +import { GlobalSearchComponent, LoadingSpinnerComponent } from '@osf/shared/components'; +import { SEARCH_TAB_OPTIONS } from '@osf/shared/constants'; +import { ResourceType } from '@osf/shared/enums'; +import { User } from '@osf/shared/models'; +import { SetDefaultFilterValue } from '@osf/shared/stores/global-search'; + +import { ProfileInformationComponent } from './components'; +import { FetchUserProfile, ProfileSelectors, SetUserProfile } from './store'; + +@Component({ + selector: 'osf-profile', + templateUrl: './profile.component.html', + styleUrl: './profile.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ProfileInformationComponent, GlobalSearchComponent, LoadingSpinnerComponent], +}) +export class ProfileComponent implements OnInit { + private router = inject(Router); + private route = inject(ActivatedRoute); + private destroyRef = inject(DestroyRef); + private actions = createDispatchMap({ + fetchUserProfile: FetchUserProfile, + setDefaultFilterValue: SetDefaultFilterValue, + setUserProfile: SetUserProfile, + }); + + private loggedInUser = select(UserSelectors.getCurrentUser); + private userProfile = select(ProfileSelectors.getUserProfile); + + isUserLoading = select(ProfileSelectors.isUserProfileLoading); + resourceTabOptions = SEARCH_TAB_OPTIONS.filter((x) => x.value !== ResourceType.Agent); + + isMyProfile = computed(() => !this.route.snapshot.params['id']); + user = computed(() => (this.isMyProfile() ? this.loggedInUser() : this.userProfile())); + + ngOnInit(): void { + const userId = this.route.snapshot.params['id']; + const currentUser = this.loggedInUser(); + + if (userId) { + this.loadUserProfile(userId); + } else if (currentUser) { + this.setupMyProfile(currentUser); + } + } + + toProfileSettings(): void { + this.router.navigate(['settings/profile']); + } + + private loadUserProfile(userId: string): void { + this.actions + .fetchUserProfile(userId) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => this.setSearchFilter()); + } + + private setupMyProfile(user: User): void { + this.actions.setUserProfile(user); + if (user?.iri) { + this.actions.setDefaultFilterValue('creator', user.iri); + } + } + + private setSearchFilter(): void { + const currentUser = this.user(); + if (currentUser?.iri) { + this.actions.setDefaultFilterValue('creator', currentUser.iri); + } + } +} diff --git a/src/app/features/settings/profile-settings/components/social-form/social-form.component.html b/src/app/features/settings/profile-settings/components/social-form/social-form.component.html index 878722f7f..b322b464c 100644 --- a/src/app/features/settings/profile-settings/components/social-form/social-form.component.html +++ b/src/app/features/settings/profile-settings/components/social-form/social-form.component.html @@ -22,7 +22,7 @@

@if (hasLinkedField()) { - .academia.edu/ + {{ linkedDomain() }} !!this.socialOutput()?.linkedField); readonly linkedLabel = computed(() => this.socialOutput()?.linkedField?.label); + readonly linkedDomain = computed(() => this.socialOutput()?.linkedField?.address); readonly linkedPlaceholder = computed(() => this.socialOutput()?.linkedField?.placeholder); } diff --git a/src/app/features/settings/profile-settings/components/social/social.component.ts b/src/app/features/settings/profile-settings/components/social/social.component.ts index b98815a4e..bb6c24ad7 100644 --- a/src/app/features/settings/profile-settings/components/social/social.component.ts +++ b/src/app/features/settings/profile-settings/components/social/social.component.ts @@ -17,12 +17,11 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormArray, FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { UpdateProfileSettingsSocialLinks, UserSelectors } from '@osf/core/store/user'; -import { Social } from '@osf/shared/models'; +import { SOCIAL_LINKS } from '@osf/shared/constants'; +import { SocialLinksForm, SocialModel } from '@osf/shared/models'; import { CustomConfirmationService, LoaderService, ToastService } from '@osf/shared/services'; -import { SOCIALS } from '../../constants/socials'; import { hasSocialLinkChanges, mapSocialLinkToPayload } from '../../helpers'; -import { SocialLinksForm } from '../../models'; import { SocialFormComponent } from '../social-form/social-form.component'; @Component({ @@ -42,7 +41,7 @@ export class SocialComponent { private readonly cd = inject(ChangeDetectorRef); private readonly fb = inject(FormBuilder); - private readonly socials = SOCIALS; + private readonly socials = SOCIAL_LINKS; readonly actions = createDispatchMap({ updateProfileSettingsSocialLinks: UpdateProfileSettingsSocialLinks }); readonly socialLinks = select(UserSelectors.getSocialLinks); @@ -80,7 +79,7 @@ export class SocialComponent { } const links = this.socialLinksForm.value.links as SocialLinksForm[]; - const mappedLinks = links.map((link) => mapSocialLinkToPayload(link)) satisfies Partial[]; + const mappedLinks = links.map((link) => mapSocialLinkToPayload(link)) satisfies Partial[]; this.loaderService.show(); this.actions diff --git a/src/app/features/settings/profile-settings/constants/index.ts b/src/app/features/settings/profile-settings/constants/index.ts index 90c8b6c16..1b98ec922 100644 --- a/src/app/features/settings/profile-settings/constants/index.ts +++ b/src/app/features/settings/profile-settings/constants/index.ts @@ -1,3 +1,2 @@ export * from './date-limits.const'; export * from './profile-settings-tab-options.const'; -export * from './socials'; diff --git a/src/app/features/settings/profile-settings/helpers/social-comparison.helper.ts b/src/app/features/settings/profile-settings/helpers/social-comparison.helper.ts index fc82bf595..174625d82 100644 --- a/src/app/features/settings/profile-settings/helpers/social-comparison.helper.ts +++ b/src/app/features/settings/profile-settings/helpers/social-comparison.helper.ts @@ -1,6 +1,4 @@ -import { Social } from '@osf/shared/models'; - -import { SOCIAL_KEYS, SocialLinksForm, SocialLinksKeys, SocialLinksModel } from '../models'; +import { SOCIAL_KEYS, SocialLinksForm, SocialLinksKeys, SocialLinksModel, SocialModel } from '@osf/shared/models'; export function normalizeValue(value: unknown, key: SocialLinksKeys): unknown { if (SOCIAL_KEYS.includes(key)) { @@ -9,7 +7,7 @@ export function normalizeValue(value: unknown, key: SocialLinksKeys): unknown { return value; } -export function mapSocialLinkToPayload(link: SocialLinksForm): Partial { +export function mapSocialLinkToPayload(link: SocialLinksForm): Partial { const key = link.socialOutput.key as SocialLinksKeys; const linkedKey = link.socialOutput.linkedField?.key as SocialLinksKeys; @@ -19,7 +17,7 @@ export function mapSocialLinkToPayload(link: SocialLinksForm): Partial { : [link.webAddress].filter(Boolean) : link.webAddress; - const result: Partial = { [key]: value }; + const result: Partial = { [key]: value }; if (linkedKey) { const typeSafeResult = result as Record; @@ -31,7 +29,7 @@ export function mapSocialLinkToPayload(link: SocialLinksForm): Partial { export function hasSocialLinkChanges( link: SocialLinksForm, - initialSocialLinks: Social, + initialSocialLinks: SocialModel, socialIndex: number, socials: SocialLinksModel[] ): boolean { @@ -41,7 +39,7 @@ export function hasSocialLinkChanges( const mappedLink = mapSocialLinkToPayload(link); const { key, linkedField } = social; - const hasChanged = (currentKey: keyof Social) => { + const hasChanged = (currentKey: keyof SocialModel) => { const current = mappedLink[currentKey]; const initial = normalizeValue(initialSocialLinks[currentKey], currentKey); diff --git a/src/app/features/settings/profile-settings/models/index.ts b/src/app/features/settings/profile-settings/models/index.ts index 3118024c6..67a239ca7 100644 --- a/src/app/features/settings/profile-settings/models/index.ts +++ b/src/app/features/settings/profile-settings/models/index.ts @@ -1,5 +1,4 @@ export * from './education-form.model'; export * from './employment-form.model'; export * from './name-form.model'; -export * from './social.model'; export * from './user-social-link.model'; diff --git a/src/app/shared/constants/index.ts b/src/app/shared/constants/index.ts index e8aadc9a5..2b610609d 100644 --- a/src/app/shared/constants/index.ts +++ b/src/app/shared/constants/index.ts @@ -18,5 +18,7 @@ export * from './scientists.const'; export * from './search-sort-options.const'; export * from './search-tab-options.const'; export * from './search-tutorial-steps.const'; +export * from './social-links.const'; +export * from './social-links.const'; export * from './social-share.config'; export * from './sort-options.const'; diff --git a/src/app/features/settings/profile-settings/constants/socials.ts b/src/app/shared/constants/social-links.const.ts similarity index 85% rename from src/app/features/settings/profile-settings/constants/socials.ts rename to src/app/shared/constants/social-links.const.ts index 385e0fbe5..d97563bc6 100644 --- a/src/app/features/settings/profile-settings/constants/socials.ts +++ b/src/app/shared/constants/social-links.const.ts @@ -1,12 +1,13 @@ import { SocialLinksModel } from '../models'; -export const SOCIALS: SocialLinksModel[] = [ +export const SOCIAL_LINKS: SocialLinksModel[] = [ { id: 0, label: 'settings.profileSettings.social.labels.researcherId', address: 'http://researchers.com/rid/', placeholder: 'x-xxxx-xxxx', key: 'researcherId', + icon: 'researcherID.png', }, { id: 1, @@ -14,6 +15,7 @@ export const SOCIALS: SocialLinksModel[] = [ address: 'http://orcid.org/', placeholder: 'xxxx-xxxx-xxxx', key: 'orcid', + icon: 'orcid.svg', }, { id: 2, @@ -21,13 +23,15 @@ export const SOCIALS: SocialLinksModel[] = [ address: 'https://linkedin.com/', placeholder: 'in/userID, profie/view?profileID, or pub/pubID', key: 'linkedIn', + icon: 'linkedin.svg', }, { id: 3, label: 'settings.profileSettings.social.labels.twitter', - address: '@', + address: 'https://x.com/', placeholder: 'twitterhandle', key: 'twitter', + icon: 'x.svg', }, { id: 4, @@ -35,6 +39,7 @@ export const SOCIALS: SocialLinksModel[] = [ address: 'https://github.com/', placeholder: 'username', key: 'github', + icon: 'github.svg', }, { id: 5, @@ -42,6 +47,7 @@ export const SOCIALS: SocialLinksModel[] = [ address: 'https://impactstory.org/u/', placeholder: 'profileID', key: 'impactStory', + icon: 'impactStory.svg', }, { id: 6, @@ -49,6 +55,7 @@ export const SOCIALS: SocialLinksModel[] = [ address: 'http://scholar.google.com/citations?user=', placeholder: 'profileID', key: 'scholar', + icon: 'scholar.svg', }, { id: 7, @@ -56,6 +63,7 @@ export const SOCIALS: SocialLinksModel[] = [ address: 'https://researchgate.net/profile/', placeholder: 'profileID', key: 'researchGate', + icon: 'researchGate.svg', }, { id: 8, @@ -63,6 +71,7 @@ export const SOCIALS: SocialLinksModel[] = [ address: 'http://xueshu.baidu.com/scholarID/', placeholder: 'profileID', key: 'baiduScholar', + icon: 'baiduScholar.png', }, { id: 9, @@ -70,6 +79,7 @@ export const SOCIALS: SocialLinksModel[] = [ address: 'http://papers.ssrn.com/sol3/cf_dev/AbsByAuth.cfm?per_id=', placeholder: 'profileID', key: 'ssrn', + icon: 'ssrn.svg', }, { id: 10, @@ -77,6 +87,7 @@ export const SOCIALS: SocialLinksModel[] = [ address: '', placeholder: 'https://yourwebsite.com', key: 'profileWebsites', + icon: '', }, { id: 11, @@ -84,10 +95,12 @@ export const SOCIALS: SocialLinksModel[] = [ address: 'https://', placeholder: 'institution', key: 'academiaInstitution', + icon: 'academia.svg', linkedField: { key: 'academiaProfileID', label: 'settings.profileSettings.social.labels.academiaProfileId', placeholder: 'profileId', + address: '.academia.edu/', }, }, ]; diff --git a/src/app/shared/models/profile-settings-update.model.ts b/src/app/shared/models/profile-settings-update.model.ts index a9a758472..b2deafe4e 100644 --- a/src/app/shared/models/profile-settings-update.model.ts +++ b/src/app/shared/models/profile-settings-update.model.ts @@ -1,3 +1,3 @@ -import { Education, Employment, Social, User } from './user'; +import { Education, Employment, SocialModel, User } from './user'; -export type ProfileSettingsUpdate = Partial[] | Partial[] | Partial | Partial; +export type ProfileSettingsUpdate = Partial[] | Partial[] | Partial | Partial; diff --git a/src/app/shared/models/registration/registration-node-json-api.model.ts b/src/app/shared/models/registration/registration-node-json-api.model.ts index 7ca3eb716..d4796801d 100644 --- a/src/app/shared/models/registration/registration-node-json-api.model.ts +++ b/src/app/shared/models/registration/registration-node-json-api.model.ts @@ -1,4 +1,4 @@ -import { RegistrationReviewStates } from '@osf/shared/enums'; +import { RegistrationReviewStates, RevisionReviewStates } from '@osf/shared/enums'; import { BaseNodeAttributesJsonApi } from '../nodes'; @@ -25,7 +25,7 @@ export interface RegistrationNodeAttributesJsonApi extends BaseNodeAttributesJso registration_responses: RegistrationResponses; registration_supplement: string; reviews_state: RegistrationReviewStates; - revision_state: string; + revision_state: RevisionReviewStates; withdrawal_justification: string | null; withdrawn: boolean; } diff --git a/src/app/shared/models/user/index.ts b/src/app/shared/models/user/index.ts index f80fe3921..f937689f8 100644 --- a/src/app/shared/models/user/index.ts +++ b/src/app/shared/models/user/index.ts @@ -1,4 +1,6 @@ export * from './education.model'; export * from './employment.model'; export * from './social.model'; +export * from './social-link-view.model'; +export * from './social-links.model'; export * from './user.models'; diff --git a/src/app/shared/models/user/social-link-view.model.ts b/src/app/shared/models/user/social-link-view.model.ts new file mode 100644 index 000000000..78f157145 --- /dev/null +++ b/src/app/shared/models/user/social-link-view.model.ts @@ -0,0 +1,5 @@ +export interface SocialLinkViewModel { + url: string; + icon: string; + alt: string; +} diff --git a/src/app/features/settings/profile-settings/models/social.model.ts b/src/app/shared/models/user/social-links.model.ts similarity index 76% rename from src/app/features/settings/profile-settings/models/social.model.ts rename to src/app/shared/models/user/social-links.model.ts index fd78d72c5..f8ffb3ba3 100644 --- a/src/app/features/settings/profile-settings/models/social.model.ts +++ b/src/app/shared/models/user/social-links.model.ts @@ -1,6 +1,6 @@ -import { Social } from '@osf/shared/models'; +import { SocialModel } from '@osf/shared/models'; -export type SocialLinksKeys = keyof Social; +export type SocialLinksKeys = keyof SocialModel; export const SOCIAL_KEYS: SocialLinksKeys[] = ['github', 'twitter', 'linkedIn', 'profileWebsites']; @@ -10,10 +10,12 @@ export interface SocialLinksModel { address: string; placeholder: string; key: SocialLinksKeys; + icon: string; linkedField?: { key: SocialLinksKeys; label: string; placeholder: string; + address: string; }; } diff --git a/src/app/shared/models/user/social.model.ts b/src/app/shared/models/user/social.model.ts index 7b61b9e1c..d4304fb3b 100644 --- a/src/app/shared/models/user/social.model.ts +++ b/src/app/shared/models/user/social.model.ts @@ -1,4 +1,4 @@ -export interface Social { +export interface SocialModel { ssrn: string; orcid: string; github: string[]; diff --git a/src/app/shared/models/user/user.models.ts b/src/app/shared/models/user/user.models.ts index 49bb58567..3667fa21f 100644 --- a/src/app/shared/models/user/user.models.ts +++ b/src/app/shared/models/user/user.models.ts @@ -2,7 +2,7 @@ import { JsonApiResponse } from '@shared/models'; import { Education } from './education.model'; import { Employment } from './employment.model'; -import { Social } from './social.model'; +import { SocialModel } from './social.model'; export type UserResponseJsonApi = JsonApiResponse; @@ -16,7 +16,7 @@ export interface User { suffix?: string; education: Education[]; employment: Employment[]; - social: Social; + social: SocialModel; dateRegistered: Date; link?: string; iri?: string; @@ -44,7 +44,7 @@ export interface UserDataJsonApi { education: Education[]; middle_names?: string; suffix?: string; - social: Social; + social: SocialModel; date_registered: string; allow_indexing?: boolean; can_view_reviews: boolean; diff --git a/src/assets/icons/socials/orcid.svg b/src/assets/icons/socials/orcid.svg new file mode 100644 index 000000000..6d2bd412a --- /dev/null +++ b/src/assets/icons/socials/orcid.svg @@ -0,0 +1,9 @@ + + + + + + + + +