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) {
-
-
-
- }
- @if (currentUser()?.social?.scholar) {
-
-
-
- }
- @if (currentUser()?.social?.twitter) {
-
-
-
- }
- @if (currentUser()?.social?.linkedIn) {
-
-
-
- }
- @if (currentUser()?.social?.impactStory) {
-
-
-
- }
- @if (currentUser()?.social?.baiduScholar) {
-
-
-
- }
- @if (currentUser()?.social?.researchGate) {
-
-
-
- }
- @if (currentUser()?.social?.researcherId) {
-
-
-
- }
- @if (currentUser()?.social?.ssrn) {
-
-
-
- }
- @if (currentUser()?.social?.academiaProfileID) {
-
-
+ @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 @@
+