diff --git a/src/app/core/components/nav-menu/nav-menu.component.html b/src/app/core/components/nav-menu/nav-menu.component.html
index 1a795209e..629df1400 100644
--- a/src/app/core/components/nav-menu/nav-menu.component.html
+++ b/src/app/core/components/nav-menu/nav-menu.component.html
@@ -1,19 +1,24 @@
diff --git a/src/app/core/components/nav-menu/nav-menu.component.scss b/src/app/core/components/nav-menu/nav-menu.component.scss
index 470ab0834..69ad3b05b 100644
--- a/src/app/core/components/nav-menu/nav-menu.component.scss
+++ b/src/app/core/components/nav-menu/nav-menu.component.scss
@@ -1,27 +1,14 @@
@use "assets/styles/variables" as var;
+
:host {
.nav-menu {
width: 100%;
- max-width: 15rem;
-
- ul {
- .nav-link {
- display: flex;
- justify-content: flex-start;
- align-items: center;
- padding-left: 1rem;
- width: 100%;
- height: 3.4rem;
- gap: 0.5rem;
- color: var.$white;
- text-decoration: none;
- }
+ max-width: 15.5rem;
- .active {
- font-weight: 700;
- background-color: var.$dark-blue-2;
- border-radius: 0.5rem;
- }
+ .active {
+ font-weight: 700;
+ background-color: var.$dark-blue-2;
+ border-radius: 0.5rem;
}
}
}
diff --git a/src/app/core/components/nav-menu/nav-menu.component.ts b/src/app/core/components/nav-menu/nav-menu.component.ts
index b711ad7bf..c11d6efb9 100644
--- a/src/app/core/components/nav-menu/nav-menu.component.ts
+++ b/src/app/core/components/nav-menu/nav-menu.component.ts
@@ -1,13 +1,29 @@
import { Component } from '@angular/core';
import { RouterLink, RouterLinkActive } from '@angular/router';
import { NAV_ITEMS } from '@osf/core/helpers/nav-items.constant';
+import { PanelMenuModule } from 'primeng/panelmenu';
+import { MenuItem } from 'primeng/api';
@Component({
selector: 'osf-nav-menu',
- imports: [RouterLinkActive, RouterLink],
+ imports: [RouterLinkActive, RouterLink, PanelMenuModule],
templateUrl: './nav-menu.component.html',
styleUrl: './nav-menu.component.scss',
})
export class NavMenuComponent {
navItems = NAV_ITEMS;
+
+ private convertToMenuItem(item: (typeof NAV_ITEMS)[number]): MenuItem {
+ return {
+ label: item.label,
+ icon: item.icon ? `osf-icon-${item.icon}` : '',
+ expanded: false,
+ routerLink: item.isCollapsible ? undefined : item.path,
+ items: item.items?.map((subItem) => this.convertToMenuItem(subItem)),
+ };
+ }
+
+ protected menuItems: MenuItem[] = this.navItems.map((item) =>
+ this.convertToMenuItem(item),
+ );
}
diff --git a/src/app/core/components/sidenav/sidenav.component.html b/src/app/core/components/sidenav/sidenav.component.html
index 08714d186..de8c00f50 100644
--- a/src/app/core/components/sidenav/sidenav.component.html
+++ b/src/app/core/components/sidenav/sidenav.component.html
@@ -1,2 +1,4 @@
-
-
+
+
+
+
diff --git a/src/app/core/components/sidenav/sidenav.component.scss b/src/app/core/components/sidenav/sidenav.component.scss
index f39a1f973..fc2d00567 100644
--- a/src/app/core/components/sidenav/sidenav.component.scss
+++ b/src/app/core/components/sidenav/sidenav.component.scss
@@ -1,6 +1,6 @@
@use "assets/styles/mixins" as mix;
-:host {
+aside {
@include mix.flex-column;
gap: 1.8rem;
width: 19rem;
diff --git a/src/app/core/helpers/nav-items.constant.ts b/src/app/core/helpers/nav-items.constant.ts
index f02730bbb..fab14df27 100644
--- a/src/app/core/helpers/nav-items.constant.ts
+++ b/src/app/core/helpers/nav-items.constant.ts
@@ -1,16 +1,57 @@
import { NavItem } from '@osf/shared/entities/nav-item.interface';
export const NAV_ITEMS: NavItem[] = [
- { path: '/home', label: 'Home', icon: 'home' },
- { path: '', label: 'Search OSF', icon: 'search' },
+ { path: '/home', label: 'Home', icon: 'home', useExactMatch: true },
+ { path: '/search', label: 'Search OSF', icon: 'search', useExactMatch: true },
{
path: '/support',
label: 'Support',
icon: 'support',
+ useExactMatch: true,
+ },
+ {
+ path: '/settings',
+ label: 'Settings',
+ icon: 'settings',
+ isCollapsible: true,
+ useExactMatch: true,
+ items: [
+ {
+ path: '/settings/profile-settings',
+ label: 'Profile Settings',
+ useExactMatch: true,
+ },
+ {
+ path: '/settings/account-settings',
+ label: 'Account Settings',
+ useExactMatch: true,
+ },
+ {
+ path: '/settings/addons',
+ label: 'Addon Accounts',
+ useExactMatch: false,
+ },
+ {
+ path: '/settings/notifications',
+ label: 'Notifications',
+ useExactMatch: true,
+ },
+ {
+ path: '/settings/developer-apps',
+ label: 'Developer Apps',
+ useExactMatch: true,
+ },
+ {
+ path: '/settings/personal-access-tokens',
+ label: 'Personal Access Tokens',
+ useExactMatch: true,
+ },
+ ],
},
{
path: '/donate',
label: 'Donate',
icon: 'donate',
+ useExactMatch: true,
},
];
diff --git a/src/app/features/auth/sign-up/sign-up.component.scss b/src/app/features/auth/sign-up/sign-up.component.scss
index 9fb027757..ba443f861 100644
--- a/src/app/features/auth/sign-up/sign-up.component.scss
+++ b/src/app/features/auth/sign-up/sign-up.component.scss
@@ -12,7 +12,7 @@
@include mix.flex-column;
color: var.$dark-blue-1;
width: 32rem;
- margin: 4.5rem 0 2rem 0;
+ margin: 4.5rem 0 1.5rem 0;
padding: 2rem;
background: white;
border-radius: 0.6rem;
@@ -89,7 +89,7 @@
}
.message-container {
- margin-top: auto;
+ margin: auto;
}
.mobile {
diff --git a/src/app/features/home/home.component.html b/src/app/features/home/home.component.html
index fc2219772..c3cff8964 100644
--- a/src/app/features/home/home.component.html
+++ b/src/app/features/home/home.component.html
@@ -27,17 +27,14 @@
search OSF
-
-
-
-
+
+ @if (cards().length) {
+ @for (card of cards(); track card.title) {
+
+
+
+ }
+ }
+
diff --git a/src/app/features/settings/addons/addon-card-list/addon-card-list.component.scss b/src/app/features/settings/addons/addon-card-list/addon-card-list.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/app/features/settings/addons/addon-card-list/addon-card-list.component.spec.ts b/src/app/features/settings/addons/addon-card-list/addon-card-list.component.spec.ts
new file mode 100644
index 000000000..6ce44934b
--- /dev/null
+++ b/src/app/features/settings/addons/addon-card-list/addon-card-list.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AddonCardListComponent } from './addon-card-list.component';
+
+describe('AddonCardListComponent', () => {
+ let component: AddonCardListComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [AddonCardListComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(AddonCardListComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/features/settings/addons/addon-card-list/addon-card-list.component.ts b/src/app/features/settings/addons/addon-card-list/addon-card-list.component.ts
new file mode 100644
index 000000000..fe43facbb
--- /dev/null
+++ b/src/app/features/settings/addons/addon-card-list/addon-card-list.component.ts
@@ -0,0 +1,14 @@
+import { Component, input } from '@angular/core';
+import { AddonCardComponent } from '@osf/features/settings/addons/addon-card/addon-card.component';
+import { AddonCard } from '@shared/entities/addon-card.interface';
+
+@Component({
+ selector: 'osf-addon-card-list',
+ imports: [AddonCardComponent],
+ templateUrl: './addon-card-list.component.html',
+ styleUrl: './addon-card-list.component.scss',
+})
+export class AddonCardListComponent {
+ cards = input([]);
+ cardButtonLabel = input('');
+}
diff --git a/src/app/features/settings/addons/addon-card/addon-card.component.html b/src/app/features/settings/addons/addon-card/addon-card.component.html
new file mode 100644
index 000000000..a5779d782
--- /dev/null
+++ b/src/app/features/settings/addons/addon-card/addon-card.component.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
{{ card()?.title }}
+
+
+
diff --git a/src/app/features/settings/addons/addon-card/addon-card.component.scss b/src/app/features/settings/addons/addon-card/addon-card.component.scss
new file mode 100644
index 000000000..9deebed59
--- /dev/null
+++ b/src/app/features/settings/addons/addon-card/addon-card.component.scss
@@ -0,0 +1,30 @@
+@use "assets/styles/variables" as var;
+
+:host {
+ display: block;
+
+ img {
+ max-height: 8.6rem;
+ max-width: 100%;
+ }
+
+ .addon-card {
+ height: 18.6rem;
+ text-align: center;
+ border: 1px solid var.$grey-2;
+ border-radius: 1rem;
+ padding: 2rem;
+ }
+
+ .mobile {
+ img {
+ max-height: 4.5rem;
+ }
+ }
+
+ .addon-card.mobile {
+ height: 12.5rem;
+ gap: 0.5rem;
+ padding: 1rem;
+ }
+}
diff --git a/src/app/features/settings/addons/addon-card/addon-card.component.spec.ts b/src/app/features/settings/addons/addon-card/addon-card.component.spec.ts
new file mode 100644
index 000000000..2ca38d8f6
--- /dev/null
+++ b/src/app/features/settings/addons/addon-card/addon-card.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AddonCardComponent } from './addon-card.component';
+
+describe('AddonCardComponent', () => {
+ let component: AddonCardComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [AddonCardComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(AddonCardComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/features/settings/addons/addon-card/addon-card.component.ts b/src/app/features/settings/addons/addon-card/addon-card.component.ts
new file mode 100644
index 000000000..ae9229105
--- /dev/null
+++ b/src/app/features/settings/addons/addon-card/addon-card.component.ts
@@ -0,0 +1,24 @@
+import { Component, inject, input } from '@angular/core';
+import { Button } from 'primeng/button';
+import { AddonCard } from '@shared/entities/addon-card.interface';
+import { Router } from '@angular/router';
+import { toSignal } from '@angular/core/rxjs-interop';
+import { IS_XSMALL } from '@shared/utils/breakpoints.tokens';
+
+@Component({
+ selector: 'osf-addon-card',
+ imports: [Button],
+ templateUrl: './addon-card.component.html',
+ styleUrl: './addon-card.component.scss',
+})
+export class AddonCardComponent {
+ card = input();
+ cardButtonLabel = input('');
+ isMobile = toSignal(inject(IS_XSMALL));
+
+ constructor(private router: Router) {}
+
+ onConnect(): void {
+ this.router.navigate(['/settings/addons/connect-addon']);
+ }
+}
diff --git a/src/app/features/settings/addons/addons.component.html b/src/app/features/settings/addons/addons.component.html
new file mode 100644
index 000000000..9665e5762
--- /dev/null
+++ b/src/app/features/settings/addons/addons.component.html
@@ -0,0 +1,61 @@
+
+
+
+ @if (!isMobile()) {
+
+ All Add-ons
+ Connected Add-ons
+
+ }
+
+
+ @if (isMobile()) {
+
+ }
+
+
+ Sync your projects with external services to help stay connected and
+ organized. Select a category and browse the options.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/features/settings/addons/addons.component.scss b/src/app/features/settings/addons/addons.component.scss
new file mode 100644
index 000000000..ef94601f7
--- /dev/null
+++ b/src/app/features/settings/addons/addons.component.scss
@@ -0,0 +1,11 @@
+@use "assets/styles/mixins" as mix;
+@use "assets/styles/variables" as var;
+
+:host {
+ @include mix.flex-column;
+ flex: 1;
+
+ .tabs-container {
+ @include mix.flex-column;
+ }
+}
diff --git a/src/app/features/settings/addons/addons.component.spec.ts b/src/app/features/settings/addons/addons.component.spec.ts
new file mode 100644
index 000000000..4d1a6de4c
--- /dev/null
+++ b/src/app/features/settings/addons/addons.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AddonsComponent } from './addons.component';
+
+describe('AddonsComponent', () => {
+ let component: AddonsComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [AddonsComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(AddonsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/features/settings/addons/addons.component.ts b/src/app/features/settings/addons/addons.component.ts
new file mode 100644
index 000000000..90d7dc18f
--- /dev/null
+++ b/src/app/features/settings/addons/addons.component.ts
@@ -0,0 +1,98 @@
+import {
+ ChangeDetectionStrategy,
+ Component,
+ computed,
+ signal,
+ OnInit,
+ untracked,
+ inject,
+} from '@angular/core';
+import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.component';
+import { Tab, TabList, TabPanel, TabPanels, Tabs } from 'primeng/tabs';
+import { SearchInputComponent } from '@shared/components/search-input/search-input.component';
+import { AutoCompleteModule } from 'primeng/autocomplete';
+import { AddonCardListComponent } from '@osf/features/settings/addons/addon-card-list/addon-card-list.component';
+import { AddonCard } from '@shared/entities/addon-card.interface';
+import { toSignal } from '@angular/core/rxjs-interop';
+import { IS_XSMALL } from '@shared/utils/breakpoints.tokens';
+import { DropdownModule } from 'primeng/dropdown';
+import { FormsModule } from '@angular/forms';
+
+interface TabOption {
+ label: string;
+ value: number;
+}
+
+@Component({
+ selector: 'osf-addons',
+ standalone: true,
+ imports: [
+ SubHeaderComponent,
+ TabList,
+ Tabs,
+ Tab,
+ TabPanel,
+ TabPanels,
+ SearchInputComponent,
+ AutoCompleteModule,
+ AddonCardListComponent,
+ DropdownModule,
+ FormsModule,
+ ],
+ templateUrl: './addons.component.html',
+ styleUrl: './addons.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class AddonsComponent implements OnInit {
+ protected readonly defaultTabValue = 0;
+ protected readonly isMobile = toSignal(inject(IS_XSMALL));
+ protected readonly searchValue = signal('');
+ protected readonly cards = signal([]);
+ protected readonly selectedTab = this.defaultTabValue;
+ protected readonly tabOptions: TabOption[] = [
+ { label: 'All Add-ons', value: 0 },
+ { label: 'Connected Add-ons', value: 1 },
+ ];
+ protected readonly filteredCards = computed((): AddonCard[] => {
+ const searchValue = this.searchValue();
+
+ return untracked(() =>
+ this.cards().filter((card) =>
+ card.title.toLowerCase().includes(searchValue.toLowerCase()),
+ ),
+ );
+ });
+
+ ngOnInit(): void {
+ this.cards.set([
+ {
+ title: 'Bitbucket',
+ img: 'bitbucket',
+ },
+ {
+ title: 'Github',
+ img: 'github',
+ },
+ {
+ title: 'Dropbox',
+ img: 'dropbox',
+ },
+ {
+ title: 'Figshare',
+ img: 'figshare',
+ },
+ {
+ title: 'OneDrive',
+ img: 'onedrive',
+ },
+ {
+ title: 'S3',
+ img: 's3',
+ },
+ {
+ title: 'OwnCloud',
+ img: 'owncloud',
+ },
+ ]);
+ }
+}
diff --git a/src/app/features/settings/addons/connect-addon/connect-addon.component.html b/src/app/features/settings/addons/connect-addon/connect-addon.component.html
new file mode 100644
index 000000000..ae967b61d
--- /dev/null
+++ b/src/app/features/settings/addons/connect-addon/connect-addon.component.html
@@ -0,0 +1,191 @@
+
+
+
+
+
+
+
+ Figshare Terms
+
+
+
+
+ Function
+ Status
+
+
+
+
+ {{ term.function }}
+ {{ term.status }}
+
+
+
+
+
+
+ • This add-on connects your OSF project to an external service.
+ Use of this service is bound by its terms and conditions. The
+ OSF is not responsible for the service or for your use thereof.
+
+
+ • This add-on allows you to store files using an external
+ service. Files added to this add-on are not stored within the
+ OSF.
+
+
+
+
+
+
+
+
+
+
+
+ Setup new account
+
+ Account Name
+
+ This will distinguish your account from other using the same
+ addon.
+
+ Google Drive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/features/settings/addons/connect-addon/connect-addon.component.scss b/src/app/features/settings/addons/connect-addon/connect-addon.component.scss
new file mode 100644
index 000000000..bbed0f544
--- /dev/null
+++ b/src/app/features/settings/addons/connect-addon/connect-addon.component.scss
@@ -0,0 +1,21 @@
+@use "assets/styles/mixins" as mix;
+@use "assets/styles/variables" as var;
+
+:host {
+ flex: 1;
+ @include mix.flex-column;
+
+ .stepper-container {
+ background-color: white;
+ flex: 1;
+
+ button {
+ width: 100%;
+ }
+ }
+
+ .folders-list {
+ border: 1px solid var.$grey-2;
+ border-radius: 0.57rem;
+ }
+}
diff --git a/src/app/features/settings/addons/connect-addon/connect-addon.component.spec.ts b/src/app/features/settings/addons/connect-addon/connect-addon.component.spec.ts
new file mode 100644
index 000000000..90ae04365
--- /dev/null
+++ b/src/app/features/settings/addons/connect-addon/connect-addon.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ConnectAddonComponent } from './connect-addon.component';
+
+describe('ConnectAddonComponent', () => {
+ let component: ConnectAddonComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ConnectAddonComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ConnectAddonComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/features/settings/addons/connect-addon/connect-addon.component.ts b/src/app/features/settings/addons/connect-addon/connect-addon.component.ts
new file mode 100644
index 000000000..85fc54c05
--- /dev/null
+++ b/src/app/features/settings/addons/connect-addon/connect-addon.component.ts
@@ -0,0 +1,97 @@
+import { Component, signal } from '@angular/core';
+import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.component';
+import { StepPanel, StepPanels, Stepper } from 'primeng/stepper';
+import { Button } from 'primeng/button';
+import { TableModule } from 'primeng/table';
+import { RouterLink } from '@angular/router';
+import { NgClass } from '@angular/common';
+import { Card } from 'primeng/card';
+import { RadioButton } from 'primeng/radiobutton';
+import { FormsModule } from '@angular/forms';
+import { Checkbox } from 'primeng/checkbox';
+import { GoogleDriveFolder } from '@shared/entities/google-drive-folder.interface';
+import { AddonTerm } from '@shared/entities/addon-terms.interface';
+import { Divider } from 'primeng/divider';
+
+@Component({
+ selector: 'osf-connect-addon',
+ imports: [
+ SubHeaderComponent,
+ StepPanel,
+ StepPanels,
+ Stepper,
+ Button,
+ TableModule,
+ RouterLink,
+ NgClass,
+ Card,
+ FormsModule,
+ RadioButton,
+ Checkbox,
+ Divider,
+ ],
+ templateUrl: './connect-addon.component.html',
+ styleUrl: './connect-addon.component.scss',
+ standalone: true,
+})
+export class ConnectAddonComponent {
+ protected radioConfig = '';
+ protected readonly selectedFolders = signal([]);
+ protected readonly folders: GoogleDriveFolder[] = [
+ { name: 'folder name example', selected: false },
+ { name: 'folder name example', selected: false },
+ { name: 'folder name example', selected: false },
+ { name: 'folder name example', selected: false },
+ { name: 'folder name example', selected: false },
+ { name: 'folder name example', selected: false },
+ ];
+ protected readonly terms: AddonTerm[] = [
+ {
+ function: 'Add / Update Files',
+ status: 'You cannot add or update files for figshare within OSF.',
+ type: 'warning',
+ },
+ {
+ function: 'Delete files',
+ status: 'You cannot delete files for figshare within OSF.',
+ type: 'warning',
+ },
+ {
+ function: 'Forking',
+ status:
+ 'Only the user who first authorized the figshare add-on within source project can transfer its authorization to a forked project or component.',
+ type: 'info',
+ },
+ {
+ function: 'Logs',
+ status:
+ 'OSF tracks changes you make to your figshare content within OSF, but not changes made directly within figshare.',
+ type: 'info',
+ },
+ {
+ function: 'Permissions',
+ status:
+ 'The OSF does not change permissions for linked figshare files. Privacy changes made to an OSF project or component will not affect those set in figshare.',
+ type: 'info',
+ },
+ {
+ function: 'Registering',
+ status:
+ 'figshare content will be registered, but version history will not be copied to the registration.',
+ type: 'info',
+ },
+ {
+ function: 'View/Download File Versions',
+ status:
+ 'figshare files can be viewed/downloaded in OSF, but version history is not supported.',
+ type: 'warning',
+ },
+ ];
+
+ toggleFolderSelection(folder: GoogleDriveFolder): void {
+ folder.selected = !folder.selected;
+ this.selectedFolders.set(
+ this.folders.filter((f) => f.selected).map((f) => f.name),
+ );
+ }
+}
diff --git a/src/app/features/settings/profile-settings/profile-settings.component.ts b/src/app/features/settings/profile-settings/profile-settings.component.ts
index ed4fdece4..3b11dfa1e 100644
--- a/src/app/features/settings/profile-settings/profile-settings.component.ts
+++ b/src/app/features/settings/profile-settings/profile-settings.component.ts
@@ -1,4 +1,4 @@
-import { Component } from '@angular/core';
+import { ChangeDetectionStrategy, Component } from '@angular/core';
import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.component';
@Component({
@@ -6,5 +6,6 @@ import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.com
imports: [SubHeaderComponent],
templateUrl: './profile-settings.component.html',
styleUrl: './profile-settings.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProfileSettingsComponent {}
diff --git a/src/app/features/settings/settings-container.component.html b/src/app/features/settings/settings-container.component.html
index 67e7bd4cd..f58acf390 100644
--- a/src/app/features/settings/settings-container.component.html
+++ b/src/app/features/settings/settings-container.component.html
@@ -1 +1,3 @@
-
+
+
+
diff --git a/src/app/features/settings/settings-container.component.scss b/src/app/features/settings/settings-container.component.scss
index 28d91b04b..4bafbf07a 100644
--- a/src/app/features/settings/settings-container.component.scss
+++ b/src/app/features/settings/settings-container.component.scss
@@ -1,3 +1,12 @@
+@use "assets/styles/mixins" as mix;
+
:host {
- margin-top: 4.5rem;
+ @include mix.flex-column;
+ flex: 1;
+
+ .desktop {
+ @include mix.flex-column;
+ margin-top: 4.5rem;
+ flex: 1;
+ }
}
diff --git a/src/app/features/settings/settings-container.component.ts b/src/app/features/settings/settings-container.component.ts
index ee731c455..e5245d081 100644
--- a/src/app/features/settings/settings-container.component.ts
+++ b/src/app/features/settings/settings-container.component.ts
@@ -1,5 +1,7 @@
-import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { RouterOutlet } from '@angular/router';
+import { toSignal } from '@angular/core/rxjs-interop';
+import { IS_WEB } from '@shared/utils/breakpoints.tokens';
@Component({
selector: 'osf-settings-container',
@@ -8,4 +10,6 @@ import { RouterOutlet } from '@angular/router';
styleUrl: './settings-container.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class SettingsContainerComponent {}
+export class SettingsContainerComponent {
+ isDesktop = toSignal(inject(IS_WEB));
+}
diff --git a/src/app/features/settings/settings.routes.ts b/src/app/features/settings/settings.routes.ts
index 6922327a2..713b9a278 100644
--- a/src/app/features/settings/settings.routes.ts
+++ b/src/app/features/settings/settings.routes.ts
@@ -27,6 +27,25 @@ export const settingsRoutes: Routes = [
(mod) => mod.DeveloperAppsComponent,
),
},
+ {
+ path: 'addons',
+ children: [
+ {
+ path: '',
+ loadComponent: () =>
+ import('./addons/addons.component').then(
+ (mod) => mod.AddonsComponent,
+ ),
+ },
+ {
+ path: 'connect-addon',
+ loadComponent: () =>
+ import('./addons/connect-addon/connect-addon.component').then(
+ (mod) => mod.ConnectAddonComponent,
+ ),
+ },
+ ],
+ },
],
},
];
diff --git a/src/app/shared/components/password-input-hint/password-input-hint.component.ts b/src/app/shared/components/password-input-hint/password-input-hint.component.ts
index 3d4310988..f35ea5d16 100644
--- a/src/app/shared/components/password-input-hint/password-input-hint.component.ts
+++ b/src/app/shared/components/password-input-hint/password-input-hint.component.ts
@@ -1,4 +1,4 @@
-import { Component, input } from '@angular/core';
+import { ChangeDetectionStrategy, Component, input } from '@angular/core';
import { BooleanOrNullOrUndefined } from '@core/helpers/types.helper';
@Component({
@@ -6,6 +6,7 @@ import { BooleanOrNullOrUndefined } from '@core/helpers/types.helper';
imports: [],
templateUrl: './password-input-hint.component.html',
styleUrl: './password-input-hint.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PasswordInputHintComponent {
isError = input(false);
diff --git a/src/app/shared/components/search-input/search-input.component.html b/src/app/shared/components/search-input/search-input.component.html
new file mode 100644
index 000000000..d1c3a99a5
--- /dev/null
+++ b/src/app/shared/components/search-input/search-input.component.html
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/src/app/shared/components/search-input/search-input.component.scss b/src/app/shared/components/search-input/search-input.component.scss
new file mode 100644
index 000000000..126fc9970
--- /dev/null
+++ b/src/app/shared/components/search-input/search-input.component.scss
@@ -0,0 +1,18 @@
+@use "assets/styles/variables" as var;
+
+.search-container {
+ position: relative;
+
+ i {
+ position: absolute;
+ left: 0.61rem;
+ color: var.$dark-blue-1;
+ font-size: 1.1rem;
+ }
+
+ .search-input {
+ width: 100%;
+ padding-left: 2.61rem;
+ color: var.$dark-blue-1;
+ }
+}
diff --git a/src/app/shared/components/search-input/search-input.component.spec.ts b/src/app/shared/components/search-input/search-input.component.spec.ts
new file mode 100644
index 000000000..b8645dc62
--- /dev/null
+++ b/src/app/shared/components/search-input/search-input.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SearchInputComponent } from './search-input.component';
+
+describe('SearchInputComponent', () => {
+ let component: SearchInputComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [SearchInputComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(SearchInputComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/shared/components/search-input/search-input.component.ts b/src/app/shared/components/search-input/search-input.component.ts
new file mode 100644
index 000000000..78d5a7c5a
--- /dev/null
+++ b/src/app/shared/components/search-input/search-input.component.ts
@@ -0,0 +1,25 @@
+import {
+ ChangeDetectionStrategy,
+ Component,
+ input,
+ model,
+} from '@angular/core';
+import { InputText } from 'primeng/inputtext';
+import { FormsModule } from '@angular/forms';
+
+@Component({
+ selector: 'osf-search-input',
+ standalone: true,
+ imports: [InputText, FormsModule],
+ templateUrl: './search-input.component.html',
+ styleUrl: './search-input.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class SearchInputComponent {
+ protected readonly placeholder = input('');
+ protected readonly searchValue = model.required();
+
+ onSearchChange(value: string): void {
+ this.searchValue.set(value);
+ }
+}
diff --git a/src/app/shared/components/sub-header/sub-header.component.html b/src/app/shared/components/sub-header/sub-header.component.html
index 6efbdd61c..17cd23819 100644
--- a/src/app/shared/components/sub-header/sub-header.component.html
+++ b/src/app/shared/components/sub-header/sub-header.component.html
@@ -1,6 +1,8 @@