diff --git a/angular.json b/angular.json
index ceb7cd7ca..224dedd50 100644
--- a/angular.json
+++ b/angular.json
@@ -30,7 +30,8 @@
],
"styles": [
"src/assets/styles/styles.scss",
- "src/assets/icons/dist/icons.css"
+ "src/assets/icons/dist/icons.css",
+ "node_modules/primeflex/primeflex.css"
],
"stylePreprocessorOptions": {
"includePaths": ["src"]
diff --git a/bun.lock b/bun.lock
index 4c2a9f92c..d5d1ef93a 100644
--- a/bun.lock
+++ b/bun.lock
@@ -16,6 +16,7 @@
"@ngxs/logger-plugin": "^19.0.0",
"@ngxs/store": "^19.0.0",
"@primeng/themes": "^19.0.9",
+ "primeflex": "^4.0.0",
"primeng": "^19.0.9",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
@@ -1665,6 +1666,8 @@
"prettier": ["prettier@3.5.2", "", { "bin": "bin/prettier.cjs" }, "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg=="],
+ "primeflex": ["primeflex@4.0.0", "", {}, "sha512-UOEZCRjR36+sm5bUpDhS1xbA068l9VC6y1aTNVqQPtXuKIdPTqAWHRUxj3mKAoPrQ9W373ooJJMgNVXfiaw04g=="],
+
"primeng": ["primeng@19.0.9", "", { "dependencies": { "@primeuix/styled": "^0.3.2", "@primeuix/utils": "^0.3.2", "tslib": "^2.3.0" }, "peerDependencies": { "@angular/animations": "^19.0.0", "@angular/cdk": "^19.0.0", "@angular/common": "^19.0.0", "@angular/core": "^19.0.0", "@angular/forms": "^19.0.0", "@angular/platform-browser": "^19.0.0", "@angular/router": "^19.0.0", "rxjs": "^6.0.0 || ^7.8.1" } }, "sha512-Y994f4JDsdjwe1uK1J8Gvq94Mk2qQfV+M1zdW+XClXHK8EXzZp7kvBzfrlk607valJmUhLr/5DxSkVfVQGZhxA=="],
"proc-log": ["proc-log@5.0.0", "", {}, "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ=="],
diff --git a/package.json b/package.json
index bf372b752..6de527dff 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
"@ngxs/logger-plugin": "^19.0.0",
"@ngxs/store": "^19.0.0",
"@primeng/themes": "^19.0.9",
+ "primeflex": "^4.0.0",
"primeng": "^19.0.9",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
diff --git a/src/app/core/components/breadcrumb/breadcrumb.component.scss b/src/app/core/components/breadcrumb/breadcrumb.component.scss
index e69de29bb..c6e2905dd 100644
--- a/src/app/core/components/breadcrumb/breadcrumb.component.scss
+++ b/src/app/core/components/breadcrumb/breadcrumb.component.scss
@@ -0,0 +1,7 @@
+@use "assets/styles/variables" as var;
+
+.breadcrumbs {
+ text-transform: capitalize;
+ color: var.$dark-blue-1;
+ font-weight: 600;
+}
diff --git a/src/app/features/settings/account-settings/account-settings.component.html b/src/app/features/settings/account-settings/account-settings.component.html
index 17efe8421..0cc076f96 100644
--- a/src/app/features/settings/account-settings/account-settings.component.html
+++ b/src/app/features/settings/account-settings/account-settings.component.html
@@ -1 +1,226 @@
-
account-settings works!
+
+
+
+
+
+
+
+ To merge an existing account with this one or to log in with multiple
+ email addresses, add an alternate email address below. All projects and
+ components will be displayed under the email address listed as primary.
+
+
+
+
+
Primary Email:
+
+
email@example.com
+
+
+
+
Alternate Emails:
+
+
+
+ email@example.com
+
+
+
Make Primary
+
+
+ email@example.com
+
+
+
Make Primary
+
+
+
+
+
Unconfirmed emails:
+
+
+
+ email@example.com
+
+
+
Resend Confirmation
+
+
+
+
+
+
+
+
+
+
+
+ This location will be applied to new projects and components. It will not
+ affect existing projects and components.
+
+
+
+
+
+
+
+
+
+
+
+ Connected identities allow you to log in to the OSF via a third-party
+ service. You can revoke these identifies.
+
+
+
+
+ ORCID: 0000 - 0003 -0199 - 7112 (pending)
+
+
+
+
+
+
+
+
+ Affiliated Institutions connect your OSF account to your institution or
+ organization. Affiliated institutions can be removed from your profile.
+
+
+
+
Center for Open Science
+
+
+
+
+
+
+
+ By default, OSF users are indexed into SHARE, a free, open dataset of
+ research metadata. This allows SHARE to include your user profile and
+ research in its database, which is used by search engines and other
+ services to make research more discoverable. You can opt out of this
+ indexing by checking the box below. NOTE: Public projects, files,
+ registrations, and preprints will still be indexed in SHARE.
+
Learn more about SHARE
+
+
+
+
+
+
+
Out of SHARE Indexing
+
+
+
+
+
+
Opt In To SHARE Indexing
+
+
+
+
+
+
+
+
+
+
+
+
+ Two-factor authentication protects your OSF account using both your
+ password and email.
+
+
+
+
+
+
+
+
+
+ Warning: This action cannot be undone once approved.
+
+
+
+ Deactivating your account will remove you from all public projects to
+ which you are a contributor. Your account will no longer be associated
+ with OSF projects, and your work on the OSF will be inaccessible. If this
+ is a secondary account that you want to close, consider merging your
+ accounts.
+
+
+
+
+ Request deactivation
+
+
+
+
diff --git a/src/app/features/settings/account-settings/account-settings.component.scss b/src/app/features/settings/account-settings/account-settings.component.scss
index e69de29bb..9c670a463 100644
--- a/src/app/features/settings/account-settings/account-settings.component.scss
+++ b/src/app/features/settings/account-settings/account-settings.component.scss
@@ -0,0 +1,105 @@
+@use "assets/styles/mixins" as mix;
+@use "assets/styles/variables" as var;
+
+:host {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ gap: 1.7rem;
+ color: var(--dark-blue-1);
+
+ .header {
+ display: flex;
+ flex: 1;
+ padding: 7.14rem 1.71rem 3.43rem 1.71rem;
+ background: var(--gradient-1);
+
+ h1 {
+ margin-left: 0.85rem;
+ }
+
+ p-button {
+ margin-left: auto;
+ }
+
+ i {
+ color: var(--dark-blue-1);
+ font-size: 2.6rem;
+ }
+ }
+
+ .content {
+ display: flex;
+ flex-direction: column;
+ gap: 1.7rem;
+ padding: 1.7rem;
+ background: var.$white;
+ }
+
+ .account-setting {
+ border: 1px solid var.$grey-2;
+ padding: 1.7rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1.7rem;
+ border-radius: 0.5rem;
+
+ &-link {
+ font-weight: 600;
+ }
+
+ &-radio-group {
+ display: flex;
+ flex: 1;
+ gap: 4rem;
+ }
+
+ &-radio-item {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ }
+
+ &-content {
+ display: flex;
+ }
+
+ &-description {
+ line-height: 2rem;
+ }
+
+ &-action {
+ display: flex;
+ justify-content: flex-end;
+ }
+
+ &-emails {
+ display: flex;
+ flex-direction: column;
+ gap: 1.7rem;
+ }
+
+ &-select {
+ width: 50%;
+ }
+
+ &-email {
+ display: flex;
+ gap: 2rem;
+ align-items: center;
+
+ &--readonly {
+ display: flex;
+ align-items: center;
+ border: 1px solid var.$grey-2;
+ padding: 0.285rem 0.85rem;
+ border-radius: 0.285rem;
+ }
+
+ &__value {
+ display: flex;
+ gap: 0.428rem;
+ }
+ }
+ }
+}
diff --git a/src/app/features/settings/account-settings/account-settings.component.ts b/src/app/features/settings/account-settings/account-settings.component.ts
index 3d790183e..6c22e0822 100644
--- a/src/app/features/settings/account-settings/account-settings.component.ts
+++ b/src/app/features/settings/account-settings/account-settings.component.ts
@@ -1,9 +1,72 @@
-import { Component } from '@angular/core';
+import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
+import { Button } from 'primeng/button';
+import { Select } from 'primeng/select';
+import { MOCK_COUNTRIES } from '@osf/features/settings/account-settings/account-settings.const';
+import { RadioButton } from 'primeng/radiobutton';
+import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
+import {
+ AccountSettingsPasswordForm,
+ AccountSettingsPasswordFormControls,
+} from '@osf/features/settings/account-settings/account.settings.entities';
+import { InputText } from 'primeng/inputtext';
+import { Message } from 'primeng/message';
+import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog';
+import { AddEmailComponent } from '@osf/features/settings/account-settings/add-email/add-email.component';
+import { DeactivateAccountComponent } from '@osf/features/settings/account-settings/deactivate-account/deactivate-account/deactivate-account.component';
@Component({
selector: 'osf-account-settings',
- imports: [],
+ imports: [
+ Button,
+ Select,
+ RadioButton,
+ ReactiveFormsModule,
+ InputText,
+ Message,
+ ],
+ providers: [DialogService],
templateUrl: './account-settings.component.html',
styleUrl: './account-settings.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class AccountSettingsComponent {}
+export class AccountSettingsComponent {
+ readonly passwordForm: AccountSettingsPasswordForm = new FormGroup({
+ [AccountSettingsPasswordFormControls.OldPassword]: new FormControl('', {
+ nonNullable: true,
+ }),
+ [AccountSettingsPasswordFormControls.NewPassword]: new FormControl('', {
+ nonNullable: true,
+ }),
+ [AccountSettingsPasswordFormControls.ConfirmPassword]: new FormControl('', {
+ nonNullable: true,
+ }),
+ });
+ protected readonly optControl = new FormControl(false);
+ protected readonly MOCK_COUNTRIES = MOCK_COUNTRIES;
+ protected readonly AccountSettingsPasswordFormControls =
+ AccountSettingsPasswordFormControls;
+ private readonly dialogService = inject(DialogService);
+ private dialogRef: DynamicDialogRef | null = null;
+
+ addEmail() {
+ this.dialogRef = this.dialogService.open(AddEmailComponent, {
+ width: '448px',
+ focusOnShow: false,
+ header: 'Add alternative email',
+ closeOnEscape: true,
+ modal: true,
+ closable: true,
+ });
+ }
+
+ deactivateAccount() {
+ this.dialogRef = this.dialogService.open(DeactivateAccountComponent, {
+ width: '448px',
+ focusOnShow: false,
+ header: 'Deactivate account',
+ closeOnEscape: true,
+ modal: true,
+ closable: true,
+ });
+ }
+}
diff --git a/src/app/features/settings/account-settings/account-settings.const.ts b/src/app/features/settings/account-settings/account-settings.const.ts
new file mode 100644
index 000000000..658c0efd8
--- /dev/null
+++ b/src/app/features/settings/account-settings/account-settings.const.ts
@@ -0,0 +1,15 @@
+export interface Country {
+ name: string;
+ code: string;
+}
+
+export const MOCK_COUNTRIES: Country[] = [
+ {
+ name: 'United States',
+ code: 'US',
+ },
+ {
+ name: 'Canada',
+ code: 'CA',
+ },
+];
diff --git a/src/app/features/settings/account-settings/account.settings.entities.ts b/src/app/features/settings/account-settings/account.settings.entities.ts
new file mode 100644
index 000000000..6d1776a9a
--- /dev/null
+++ b/src/app/features/settings/account-settings/account.settings.entities.ts
@@ -0,0 +1,13 @@
+import { FormControl, FormGroup } from '@angular/forms';
+
+export enum AccountSettingsPasswordFormControls {
+ OldPassword = 'oldPassword',
+ NewPassword = 'newPassword',
+ ConfirmPassword = 'confirmPassword',
+}
+
+export type AccountSettingsPasswordForm = FormGroup<{
+ [AccountSettingsPasswordFormControls.OldPassword]: FormControl;
+ [AccountSettingsPasswordFormControls.NewPassword]: FormControl;
+ [AccountSettingsPasswordFormControls.ConfirmPassword]: FormControl;
+}>;
diff --git a/src/app/features/settings/account-settings/add-email/add-email.component.html b/src/app/features/settings/account-settings/add-email/add-email.component.html
new file mode 100644
index 000000000..ffa64fc01
--- /dev/null
+++ b/src/app/features/settings/account-settings/add-email/add-email.component.html
@@ -0,0 +1,32 @@
+
+
+ Email
+
+
+
+
+
+ Cancel
+
+
+
+ Add Email
+
+
+
diff --git a/src/app/features/settings/account-settings/add-email/add-email.component.scss b/src/app/features/settings/account-settings/add-email/add-email.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/app/features/settings/account-settings/add-email/add-email.component.spec.ts b/src/app/features/settings/account-settings/add-email/add-email.component.spec.ts
new file mode 100644
index 000000000..b5c9107f1
--- /dev/null
+++ b/src/app/features/settings/account-settings/add-email/add-email.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AddEmailComponent } from './add-email.component';
+
+describe('AddEmailComponent', () => {
+ let component: AddEmailComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [AddEmailComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(AddEmailComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/features/settings/account-settings/add-email/add-email.component.ts b/src/app/features/settings/account-settings/add-email/add-email.component.ts
new file mode 100644
index 000000000..1ad25252e
--- /dev/null
+++ b/src/app/features/settings/account-settings/add-email/add-email.component.ts
@@ -0,0 +1,21 @@
+import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
+import { DynamicDialogRef } from 'primeng/dynamicdialog';
+import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
+import { InputText } from 'primeng/inputtext';
+import { Button } from 'primeng/button';
+
+@Component({
+ selector: 'osf-add-email',
+ imports: [InputText, ReactiveFormsModule, Button],
+ templateUrl: './add-email.component.html',
+ styleUrl: './add-email.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class AddEmailComponent {
+ readonly dialogRef = inject(DynamicDialogRef);
+
+ protected readonly emailControl = new FormControl('', [
+ Validators.email,
+ Validators.required,
+ ]);
+}
diff --git a/src/app/features/settings/account-settings/deactivate-account/deactivate-account/deactivate-account.component.html b/src/app/features/settings/account-settings/deactivate-account/deactivate-account/deactivate-account.component.html
new file mode 100644
index 000000000..0a7fc0f86
--- /dev/null
+++ b/src/app/features/settings/account-settings/deactivate-account/deactivate-account/deactivate-account.component.html
@@ -0,0 +1,18 @@
+
+
Are you sure you want to to deactivate your account?
+
+
+
diff --git a/src/app/features/settings/account-settings/deactivate-account/deactivate-account/deactivate-account.component.scss b/src/app/features/settings/account-settings/deactivate-account/deactivate-account/deactivate-account.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/app/features/settings/account-settings/deactivate-account/deactivate-account/deactivate-account.component.spec.ts b/src/app/features/settings/account-settings/deactivate-account/deactivate-account/deactivate-account.component.spec.ts
new file mode 100644
index 000000000..db873e009
--- /dev/null
+++ b/src/app/features/settings/account-settings/deactivate-account/deactivate-account/deactivate-account.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DeactivateAccountComponent } from './deactivate-account.component';
+
+describe('DeactivateAccountComponent', () => {
+ let component: DeactivateAccountComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [DeactivateAccountComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(DeactivateAccountComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/features/settings/account-settings/deactivate-account/deactivate-account/deactivate-account.component.ts b/src/app/features/settings/account-settings/deactivate-account/deactivate-account/deactivate-account.component.ts
new file mode 100644
index 000000000..63461a312
--- /dev/null
+++ b/src/app/features/settings/account-settings/deactivate-account/deactivate-account/deactivate-account.component.ts
@@ -0,0 +1,11 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { Button } from 'primeng/button';
+
+@Component({
+ selector: 'osf-deactivate-account',
+ imports: [Button],
+ templateUrl: './deactivate-account.component.html',
+ styleUrl: './deactivate-account.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class DeactivateAccountComponent {}