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 @@ -OSF Logo - + 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 @@ +
+
+ Addon card image +
+ +
+

{{ 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 @@
- + @if (icon()) { + + }

{{ title() }}

@if (showButton()) { diff --git a/src/app/shared/components/sub-header/sub-header.component.ts b/src/app/shared/components/sub-header/sub-header.component.ts index 96f7efcf3..edf768bd9 100644 --- a/src/app/shared/components/sub-header/sub-header.component.ts +++ b/src/app/shared/components/sub-header/sub-header.component.ts @@ -1,4 +1,10 @@ -import { Component, inject, input, output } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + input, + inject, + output, +} from '@angular/core'; import { Button } from 'primeng/button'; import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; import { toSignal } from '@angular/core/rxjs-interop'; @@ -8,7 +14,7 @@ import { toSignal } from '@angular/core/rxjs-interop'; imports: [Button], templateUrl: './sub-header.component.html', styleUrl: './sub-header.component.scss', - standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class SubHeaderComponent { showButton = input(false); diff --git a/src/app/shared/entities/addon-card.interface.ts b/src/app/shared/entities/addon-card.interface.ts new file mode 100644 index 000000000..0887fb260 --- /dev/null +++ b/src/app/shared/entities/addon-card.interface.ts @@ -0,0 +1,4 @@ +export interface AddonCard { + title: string; + img: string; +} diff --git a/src/app/shared/entities/addon-terms.interface.ts b/src/app/shared/entities/addon-terms.interface.ts new file mode 100644 index 000000000..1787afb95 --- /dev/null +++ b/src/app/shared/entities/addon-terms.interface.ts @@ -0,0 +1,5 @@ +export interface AddonTerm { + function: string; + status: string; + type: 'warning' | 'info'; +} diff --git a/src/app/shared/entities/google-drive-folder.interface.ts b/src/app/shared/entities/google-drive-folder.interface.ts new file mode 100644 index 000000000..21900cc26 --- /dev/null +++ b/src/app/shared/entities/google-drive-folder.interface.ts @@ -0,0 +1,4 @@ +export interface GoogleDriveFolder { + name: string; + selected: boolean; +} diff --git a/src/app/shared/entities/nav-item.interface.ts b/src/app/shared/entities/nav-item.interface.ts index ecc524998..668860766 100644 --- a/src/app/shared/entities/nav-item.interface.ts +++ b/src/app/shared/entities/nav-item.interface.ts @@ -2,4 +2,7 @@ export interface NavItem { path: string; label: string; icon?: string; + items?: NavItem[]; + isCollapsible?: boolean; + useExactMatch: boolean; } diff --git a/src/assets/images/temp/bitbucket.png b/src/assets/images/temp/bitbucket.png new file mode 100644 index 000000000..d41abaccf Binary files /dev/null and b/src/assets/images/temp/bitbucket.png differ diff --git a/src/assets/images/temp/dropbox.png b/src/assets/images/temp/dropbox.png new file mode 100644 index 000000000..ee29bbf65 Binary files /dev/null and b/src/assets/images/temp/dropbox.png differ diff --git a/src/assets/images/temp/figshare.png b/src/assets/images/temp/figshare.png new file mode 100644 index 000000000..34f865e93 Binary files /dev/null and b/src/assets/images/temp/figshare.png differ diff --git a/src/assets/images/temp/github.png b/src/assets/images/temp/github.png new file mode 100644 index 000000000..a4b97bbb9 Binary files /dev/null and b/src/assets/images/temp/github.png differ diff --git a/src/assets/images/temp/onedrive.png b/src/assets/images/temp/onedrive.png new file mode 100644 index 000000000..e02f65634 Binary files /dev/null and b/src/assets/images/temp/onedrive.png differ diff --git a/src/assets/images/temp/owncloud.png b/src/assets/images/temp/owncloud.png new file mode 100644 index 000000000..e05e37c50 Binary files /dev/null and b/src/assets/images/temp/owncloud.png differ diff --git a/src/assets/images/temp/s3.png b/src/assets/images/temp/s3.png new file mode 100644 index 000000000..26ccb84ca Binary files /dev/null and b/src/assets/images/temp/s3.png differ diff --git a/src/assets/styles/overrides/autocomplete.scss b/src/assets/styles/overrides/autocomplete.scss new file mode 100644 index 000000000..e3fbd660f --- /dev/null +++ b/src/assets/styles/overrides/autocomplete.scss @@ -0,0 +1,30 @@ +@use "assets/styles/variables" as var; + +.p-autocomplete { + height: 3.125rem; + border: 1px solid var(--grey-2); + border-radius: 0.57rem; + outline: none; + + .p-inputtext { + border: none; + outline: none; + height: 3rem; + width: 100%; + background-color: transparent; + box-shadow: none; + } + + .p-autocomplete-dropdown { + border: none; + background-color: transparent; + } + + .p-icon { + color: var.$grey-1; + } +} + +.p-autocomplete.p-inputwrapper { + width: 100%; +} diff --git a/src/assets/styles/overrides/button.scss b/src/assets/styles/overrides/button.scss index 28e5eca6b..6c48894af 100644 --- a/src/assets/styles/overrides/button.scss +++ b/src/assets/styles/overrides/button.scss @@ -153,6 +153,7 @@ @include mix.flex-center; gap: 0.7rem; color: var.$dark-blue-1; + height: 3.125rem; &:hover { color: var.$dark-blue-2; @@ -164,6 +165,12 @@ } } +.p-button-link:hover { + .p-button-label { + text-decoration: none; + } +} + .wide-button { button { margin-top: 1.7rem; diff --git a/src/assets/styles/overrides/card.scss b/src/assets/styles/overrides/card.scss index 84c630308..c2b7bbd90 100644 --- a/src/assets/styles/overrides/card.scss +++ b/src/assets/styles/overrides/card.scss @@ -10,4 +10,10 @@ .p-card-body { padding: 1.7rem; } + + .addon-card-small { + .p-card-body { + padding: 0.8rem; + } + } } diff --git a/src/assets/styles/overrides/drawer.scss b/src/assets/styles/overrides/drawer.scss index a739b8bd2..62af99d18 100644 --- a/src/assets/styles/overrides/drawer.scss +++ b/src/assets/styles/overrides/drawer.scss @@ -4,10 +4,11 @@ width: 100%; background-color: var.$dark-blue-1; padding-left: 1.8rem; + border: none; } .p-drawer-header { - height: 6.1rem; + height: 6.17rem; padding: 0; } diff --git a/src/assets/styles/overrides/dropdown.scss b/src/assets/styles/overrides/dropdown.scss new file mode 100644 index 000000000..bbd7a0baf --- /dev/null +++ b/src/assets/styles/overrides/dropdown.scss @@ -0,0 +1,22 @@ +@use "assets/styles/variables" as var; +@use "assets/styles/mixins" as mix; + +.p-dropdown { + height: 3.125rem; + border: 1px solid var(--grey-2); + border-radius: 0.57rem; + outline: none; + font-size: 16px; + color: var.$dark-blue-1; + + .p-select-label { + @include mix.flex-align-center; + } + + .p-select-option { + color: var.$dark-blue-1; + &.p-select-option-selected.p-focus { + background: var.$bg-blue-2; + } + } +} diff --git a/src/assets/styles/overrides/input.scss b/src/assets/styles/overrides/input.scss index a55ded8d2..670571845 100644 --- a/src/assets/styles/overrides/input.scss +++ b/src/assets/styles/overrides/input.scss @@ -2,15 +2,17 @@ .p-inputtext { color: var.$dark-blue-1; - width: 100%; background: white; border-color: var.$grey-2; - font-size: 16px; } -.p-password { +.p-password, +.p-inputtext { width: 100%; + height: 3.125rem; font-size: 16px; + border-radius: 0.57rem; + box-shadow: none; } label { diff --git a/src/assets/styles/overrides/panel-menu.scss b/src/assets/styles/overrides/panel-menu.scss new file mode 100644 index 000000000..7b72cdd48 --- /dev/null +++ b/src/assets/styles/overrides/panel-menu.scss @@ -0,0 +1,61 @@ +@use "../variables" as var; + +.p-panelmenu { + gap: 0.2rem; + + .nav-link { + display: flex; + justify-content: flex-start; + align-items: center; + padding-left: 0.8rem; + width: 100%; + max-height: 100%; + height: 3.4rem; + gap: 0.6rem; + color: var.$white; + text-decoration: none; + + .nav-icon { + font-size: 1.5rem; + } + } + + .p-panelmenu-panel { + padding: 0; + background-color: transparent; + border: none; + cursor: pointer; + + .p-panelmenu-header-link { + display: none; + } + } + + .p-panelmenu-header:not(.p-disabled) .p-panelmenu-header-content:hover, + .p-panelmenu-item:not(.p-disabled) > .p-panelmenu-item-content:hover { + background-color: transparent; + } + + .p-panelmenu-header:not(.p-disabled):focus-visible + .p-panelmenu-header-content, + .p-panelmenu-item.p-focus > .p-panelmenu-item-content { + background-color: var.$dark-blue-2; + border-radius: 0.5rem; + } + + .p-panelmenu-submenu { + padding: 0; + margin-left: 1.5rem; + margin-top: 0.5rem; + + .p-panelmenu-item { + border-left: 1px solid var.$grey-2; + height: 3rem; + padding: 0 0 1rem 0.5rem; + + .nav-link { + height: 2.5rem; + } + } + } +} diff --git a/src/assets/styles/overrides/radio.scss b/src/assets/styles/overrides/radio.scss new file mode 100644 index 000000000..6228cc469 --- /dev/null +++ b/src/assets/styles/overrides/radio.scss @@ -0,0 +1,14 @@ +@use "../variables" as var; + +.radio-label { + color: var.$dark-blue-1; +} + +.p-radiobutton-checked .p-radiobutton-box { + border-color: var.$pr-blue-1; + background: transparent; +} + +.p-radiobutton-checked .p-radiobutton-box .p-radiobutton-icon { + background: var.$pr-blue-1; +} diff --git a/src/assets/styles/overrides/stepper.scss b/src/assets/styles/overrides/stepper.scss new file mode 100644 index 000000000..f7535856f --- /dev/null +++ b/src/assets/styles/overrides/stepper.scss @@ -0,0 +1,12 @@ +@use "../variables" as var; +@use "../mixins" as mix; + +.p-stepper { + .p-steppanels { + padding: 0; + } + + .p-stepper-separator { + display: none; + } +} diff --git a/src/assets/styles/overrides/table.scss b/src/assets/styles/overrides/table.scss index 005f3f16a..8bdf63328 100644 --- a/src/assets/styles/overrides/table.scss +++ b/src/assets/styles/overrides/table.scss @@ -8,6 +8,7 @@ border-radius: 8px; padding: 24px; } + table { th, td { @@ -118,57 +119,103 @@ } } - @media (max-width: 1279.99px) { - p-table { - .p-datatable { - padding: 1.71rem 0.85rem 1.71rem 0.85rem; + .addon-table { + tr { + &.background-warning td { + background-color: var.$red-2; } - table { - th, - td { - max-width: calc((100vw - 6.85rem - 2px) / 3); - } + &.background-success td { + background-color: var.$green-2; } } } +} + +@media (max-width: 1279.99px) { + .home-table { + .p-datatable { + padding: 1.71rem 0.85rem 1.71rem 0.85rem; + } - @media (max-width: 599.99px) { - p-table { - .p-datatable { - padding: 0.4rem; + table { + th, + td { + max-width: calc((100vw - 6.85rem - 2px) / 3); } + } + } - table { - thead { - display: none; - } + .addon-table { + .p-datatable { + padding: 0.85rem; + overflow-x: auto; + } + } +} - tr { - max-width: calc(100vw - 3.14rem - 2px); - display: flex; - flex-direction: column; - padding: 0.85rem; - background: var.$bg-blue-3; +@media (max-width: 599.99px) { + .home-table { + .p-datatable { + padding: 0.4rem; + } - td { - max-width: calc(100vw - 3.14rem - 2px); - height: 2rem; - line-height: 2rem; - padding: 0; - border: 0; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - } + table { + thead { + display: none; + } - tbody { - display: flex; - flex-direction: column; - row-gap: 0.42rem; + tr { + max-width: calc(100vw - 3.14rem - 2px); + display: flex; + flex-direction: column; + padding: 0.85rem; + background: var.$bg-blue-3; + + td { + max-width: calc(100vw - 3.14rem - 2px); + height: 2rem; + line-height: 2rem; + padding: 0; + border: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } } + + tbody { + display: flex; + flex-direction: column; + row-gap: 0.42rem; + } + } + } + + .addon-table { + width: 100%; + overflow-x: auto; + display: block; + + .p-datatable { + padding: 0.85rem; + width: 100%; + + table { + width: 100%; + table-layout: auto; + } + + td { + white-space: wrap; + overflow: visible; + text-overflow: unset; + max-width: max-content; + } + + td:nth-child(2) { + white-space: nowrap; + } } } } diff --git a/src/assets/styles/overrides/tabs.scss b/src/assets/styles/overrides/tabs.scss new file mode 100644 index 000000000..e9cbad9a6 --- /dev/null +++ b/src/assets/styles/overrides/tabs.scss @@ -0,0 +1,22 @@ +@use "assets/styles/variables" as var; + +.p-tablist-tab-list { + background: transparent; + border: none; + + .p-tab { + color: var.$dark-blue-1; + border: none; + } + + .p-tab-active { + background: white; + border-top-left-radius: 0.7rem; + border-top-right-radius: 0.7rem; + border-color: transparent; + } + + .p-tablist-active-bar { + display: none; + } +} diff --git a/src/assets/styles/styles.scss b/src/assets/styles/styles.scss index 4366f52c6..f0cfc38d3 100644 --- a/src/assets/styles/styles.scss +++ b/src/assets/styles/styles.scss @@ -10,6 +10,12 @@ @use "./overrides/drawer"; @use "./overrides/card"; @use "./overrides/carousel"; +@use "./overrides/tabs"; +@use "./overrides/autocomplete"; +@use "./overrides/panel-menu"; +@use "./overrides/stepper"; +@use "./overrides/radio"; +@use "./overrides/dropdown"; @use "./overrides/confirmation-dialog"; @layer base, primeng, reset;