From db733d12179e19f97d19667984ac85691bffbc88 Mon Sep 17 00:00:00 2001 From: Ajay Gandecha Date: Mon, 22 Jul 2024 22:40:48 -0400 Subject: [PATCH 01/10] Initial Profile Page Redesign --- .../profile-page/profile-page.component.css | 126 ++++++++++++++++- .../profile-page/profile-page.component.html | 127 ++++++++++-------- .../profile-page/profile-page.component.ts | 4 +- frontend/src/app/profile/profile.module.ts | 4 +- .../profile-about-card.widget.css | 85 ------------ .../profile-about-card.widget.html | 48 ------- .../profile-about-card.widget.ts | 22 --- frontend/src/app/shared/shared.module.ts | 3 - frontend/src/assets/github-mark-white.svg | 1 + frontend/src/assets/github-mark.svg | 1 + 10 files changed, 206 insertions(+), 215 deletions(-) delete mode 100644 frontend/src/app/shared/profile-about-card/profile-about-card.widget.css delete mode 100644 frontend/src/app/shared/profile-about-card/profile-about-card.widget.html delete mode 100644 frontend/src/app/shared/profile-about-card/profile-about-card.widget.ts create mode 100644 frontend/src/assets/github-mark-white.svg create mode 100644 frontend/src/assets/github-mark.svg diff --git a/frontend/src/app/profile/profile-page/profile-page.component.css b/frontend/src/app/profile/profile-page/profile-page.component.css index 8ed5440ae..3935effd0 100644 --- a/frontend/src/app/profile/profile-page/profile-page.component.css +++ b/frontend/src/app/profile/profile-page/profile-page.component.css @@ -8,7 +8,129 @@ * */ -.profile-management-actions { +p { + margin-bottom: 0; +} + +.header-row { + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + margin-left: 8px; +} + +.actions-list { + display: flex; + flex-direction: column; +} + +.actions-row { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; +} + +.bottom-gap { + margin-bottom: 16px; +} + +.trailing-row-button { + margin-left: auto; +} + +.mat-mdc-list-item-icon { + margin-right: 16px; +} + +.column { + display: flex; + flex-direction: column; +} + +.user-avatar { + min-width: 40px; + max-width: 40px; + min-height: 40px; + max-height: 40px; + background-color: #4786c6; + line-height: 40px; + border-radius: 100%; + text-align: center; +} + +.no-hover-chipset { + --mdc-chip-focus-state-layer-color: transparent !important; + --mdc-chip-hover-state-layer-color: transparent !important; +} + +.mat-mdc-chip { + height: 26px; +} + +.links-row { + display: flex; + flex-direction: row; + margin-top: 4px; + align-items: center; + margin-bottom: 12px; + margin-left: 48px; + gap: 4px; +} + +#github-icon { + margin-left: 6px; +} + +#github-link { + margin-right: 4px; +} + +#link-github-button { + height: 20px; + padding-left: 2px; + padding-right: 2px; + font-weight: 500; +} + +#unlink-github-button { + height: 20px; + padding-left: 2px; + padding-right: 2px; + font-weight: 400; +} + +#bio-divider { + margin-top: 16px; + margin-bottom: 16px; +} + +mat-card-subtitle { + margin-top: 4px; + margin-bottom: 4px; +} + +.mat-mdc-list-item-icon { + margin-left: 0; +} + +mat-list-item { + display: flex; + flex-direction: row; +} + +.mdc-list-item__content { + width: 200px !important; +} + +.mdc-list-item__primary-text { + width: 200px; +} + +/* OLD */ + +/* .profile-management-actions { display: flex; flex-direction: column; align-items: flex-start; @@ -41,6 +163,8 @@ margin-bottom: 0; } +*/ + .profile-edit-fab { position: absolute; bottom: 32px; diff --git a/frontend/src/app/profile/profile-page/profile-page.component.html b/frontend/src/app/profile/profile-page/profile-page.component.html index 8f45b4dfc..55e92cf63 100644 --- a/frontend/src/app/profile/profile-page/profile-page.component.html +++ b/frontend/src/app/profile/profile-page/profile-page.component.html @@ -1,78 +1,97 @@ - - + - Profile Actions + @if (profile.github_avatar) { + + } @else if (profile.first_name && profile.last_name) { +

+ {{ profile.first_name.charAt(0) }}{{ profile.last_name.charAt(0) }} +

+ } + +
+ + {{ profile.first_name }} {{ profile.last_name }} + + + + {{ profile.pronouns }} + + +
- -
-

Github

-
-
- -
- - - -
+ + -
-

Community Agreement

-
+ + Profile Bio +

+ Lorem ipsum odor amet, consectetuer adipiscing elit. Inceptos neque vel + curabitur orci est. Sem non bibendum lectus, vestibulum nec elementum + metus ad. Porta magna tempus, augue massa adipiscing netus. Congue + consequat hac erat pretium tempor. Conubia natoque orci pulvinar taciti + metus ad suscipit senectus nulla. Gravida vel dapibus taciti efficitur, + tempor phasellus varius. Finibus tristique enim sodales nascetur bibendum + bibendum nunc fermentum. +

+ + + + + + Profile Actions + + + +
+ handshake +
Community Agreement
-
-
-

Auth Bearer Token

-
+
+ key +
Bearer Token
-
-

- {{ token }} -

-
- + + - -
- diff --git a/frontend/src/app/profile/profile-page/profile-page.component.ts b/frontend/src/app/profile/profile-page/profile-page.component.ts index 625f9529a..83f528a57 100644 --- a/frontend/src/app/profile/profile-page/profile-page.component.ts +++ b/frontend/src/app/profile/profile-page/profile-page.component.ts @@ -15,6 +15,7 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { MatDialog } from '@angular/material/dialog'; import { CommunityAgreement } from 'src/app/shared/community-agreement/community-agreement.widget'; import { AuthenticationService } from 'src/app/authentication.service'; +import { SocialMediaIconWidgetService } from 'src/app/shared/social-media-icon/social-media-icon.widget.service'; @Component({ selector: 'app-profile-page', @@ -44,7 +45,8 @@ export class ProfilePageComponent { public auth: AuthenticationService, protected profileService: ProfileService, protected snackBar: MatSnackBar, - protected dialog: MatDialog + protected dialog: MatDialog, + private icons: SocialMediaIconWidgetService ) { /** Get currently-logged-in user. */ const data = this.route.snapshot.data as { diff --git a/frontend/src/app/profile/profile.module.ts b/frontend/src/app/profile/profile.module.ts index ac22a10d2..f0d15b477 100644 --- a/frontend/src/app/profile/profile.module.ts +++ b/frontend/src/app/profile/profile.module.ts @@ -33,6 +33,7 @@ import { SharedModule } from '../shared/shared.module'; import { ProfilePageComponent } from './profile-page/profile-page.component'; import { ProfileEditorComponent } from './profile-editor/profile-editor.component'; import { ProfileRoutingModule } from './profile-routing.module'; +import { MatChipsModule } from '@angular/material/chips'; @NgModule({ declarations: [ProfilePageComponent, ProfileEditorComponent], @@ -55,7 +56,8 @@ import { ProfileRoutingModule } from './profile-routing.module'; MatTooltipModule, ProfileRoutingModule, RouterModule, - SharedModule + SharedModule, + MatChipsModule ] }) export class ProfileModule {} diff --git a/frontend/src/app/shared/profile-about-card/profile-about-card.widget.css b/frontend/src/app/shared/profile-about-card/profile-about-card.widget.css deleted file mode 100644 index fdde8c189..000000000 --- a/frontend/src/app/shared/profile-about-card/profile-about-card.widget.css +++ /dev/null @@ -1,85 +0,0 @@ -.profile-header { - display: flex; - align-items: center; - justify-content: space-between; -} - -.mdc-list-item--with-two-lines .mdc-list-item__primary-text, -.mdc-list-item--with-three-lines .mdc-list-item__primary-text { - margin-bottom: 0; -} - -.left-header-info { - display: flex; - flex-direction: row; - width: 100%; - align-items: center; - justify-content: flex-start; -} - -.user-avatar { - min-width: 40px; - max-width: 40px; - min-height: 40px; - max-height: 40px; - margin-right: 1rem; - margin-top: 0; - margin-bottom: 0; - background-color: #4786c6; - vertical-align: middle; - justify-content: center; - text-align: center; - line-height: 40px; - border-radius: 100%; -} - -.user-name { - overflow: hidden; - text-overflow: ellipsis; - display: -webkit-box; - -webkit-line-clamp: 1; - /* number of lines to show */ - line-clamp: 1; - -webkit-box-orient: vertical; -} - -.user-info-label { - font-weight: 500; - margin-bottom: 0; -} - -.user-info-detail { - margin-top: 4px; - margin-bottom: 10px; -} - -.contact-info { - display: grid; - grid-template-columns: 1fr 1fr; -} - -#github_link { - color: #4786c6; -} - -.mat-mdc-card-avatar { - margin-bottom: 0 !important; -} - -.mdc-list-group__subheader { - margin: 0; -} - -.mdc-list-item { - padding: 0 !important; - display: flex; - align-items: baseline; -} - -.mdc-list-group__subheader { - margin: 0; -} - -.mat-mdc-list-base { - padding: 0; -} diff --git a/frontend/src/app/shared/profile-about-card/profile-about-card.widget.html b/frontend/src/app/shared/profile-about-card/profile-about-card.widget.html deleted file mode 100644 index 46bc94461..000000000 --- a/frontend/src/app/shared/profile-about-card/profile-about-card.widget.html +++ /dev/null @@ -1,48 +0,0 @@ - - - -
- - - -

- {{ profile.first_name.charAt(0) }}{{ profile.last_name.charAt(0) }} -

-
- - - {{ profile.first_name }} {{ profile.last_name }} -
-
- - - -
diff --git a/frontend/src/app/shared/profile-about-card/profile-about-card.widget.ts b/frontend/src/app/shared/profile-about-card/profile-about-card.widget.ts deleted file mode 100644 index 04b821c30..000000000 --- a/frontend/src/app/shared/profile-about-card/profile-about-card.widget.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * The Profile About Card displays profile specific information - * about a user, such as name, pfp, pronouns, and email. - * - * @author Jade Keegan - * @copyright 2024 - * @license MIT - */ - -import { Component, Input } from '@angular/core'; -import { Profile } from '../../models.module'; - -@Component({ - selector: 'profile-about-card', - templateUrl: './profile-about-card.widget.html', - styleUrls: ['./profile-about-card.widget.css'] -}) -export class ProfileAboutCard { - @Input() profile!: Profile; - - constructor() {} -} diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 488ed5e06..5a519258d 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -29,7 +29,6 @@ import { UserLookup } from './user-lookup/user-lookup.widget'; import { CommunityAgreement } from './community-agreement/community-agreement.widget'; import { UserChipList } from './user-chip-list/user-chip-list.widget'; -import { ProfileAboutCard } from './profile-about-card/profile-about-card.widget'; import { MatPaneComponent } from './mat/mat-pane/mat-pane.component'; import { BannerCardComponent } from './banner-card/banner-card.component'; import { GroupEventsPipe } from '../event/pipes/group-events.pipe'; @@ -46,7 +45,6 @@ import { MarkdownDirective } from './directives/markdown.directive'; UserLookup, UserChipList, CommunityAgreement, - ProfileAboutCard, MatPaneComponent, BannerCardComponent, AdminFabComponent, @@ -81,7 +79,6 @@ import { MarkdownDirective } from './directives/markdown.directive'; SearchBar, UserLookup, UserChipList, - ProfileAboutCard, MatPaneComponent, BannerCardComponent, AdminFabComponent, diff --git a/frontend/src/assets/github-mark-white.svg b/frontend/src/assets/github-mark-white.svg new file mode 100644 index 000000000..d5e649185 --- /dev/null +++ b/frontend/src/assets/github-mark-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/github-mark.svg b/frontend/src/assets/github-mark.svg new file mode 100644 index 000000000..37fa923df --- /dev/null +++ b/frontend/src/assets/github-mark.svg @@ -0,0 +1 @@ + \ No newline at end of file From 4e62540878d26484a04f312722ab38923bdcb7ab Mon Sep 17 00:00:00 2001 From: Ajay Gandecha Date: Tue, 23 Jul 2024 10:20:48 -0400 Subject: [PATCH 02/10] Add Markdown to Profile Bio Field --- .../profile-page/profile-page.component.html | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/profile/profile-page/profile-page.component.html b/frontend/src/app/profile/profile-page/profile-page.component.html index 55e92cf63..32469fd9d 100644 --- a/frontend/src/app/profile/profile-page/profile-page.component.html +++ b/frontend/src/app/profile/profile-page/profile-page.component.html @@ -40,14 +40,14 @@
Profile Bio -

- Lorem ipsum odor amet, consectetuer adipiscing elit. Inceptos neque vel - curabitur orci est. Sem non bibendum lectus, vestibulum nec elementum +

+ Lorem ipsum **odor amet**, consectetuer adipiscing elit. Inceptos neque + vel curabitur orci est. Sem non bibendum lectus, vestibulum nec elementum metus ad. Porta magna tempus, augue massa adipiscing netus. Congue consequat hac erat pretium tempor. Conubia natoque orci pulvinar taciti metus ad suscipit senectus nulla. Gravida vel dapibus taciti efficitur, tempor phasellus varius. Finibus tristique enim sodales nascetur bibendum - bibendum nunc fermentum. + bibendum nunc fermentum. [Test](https://apple.com)

@@ -71,10 +71,7 @@
key
Bearer Token
-
@@ -84,7 +81,11 @@ - From ad766b3d2b5014899710d47814605ee8b3f8cef9 Mon Sep 17 00:00:00 2001 From: Ajay Gandecha Date: Tue, 23 Jul 2024 10:39:02 -0400 Subject: [PATCH 03/10] Add Bio and Get by Onyen to User Backend --- backend/api/profile.py | 2 + backend/api/user.py | 10 +++++ backend/entities/user_entity.py | 6 +++ backend/models/public_user.py | 1 + backend/models/user.py | 2 + backend/services/user.py | 23 +++++++++++- .../section-editor.component.ts | 3 +- frontend/src/app/models.module.ts | 2 + .../profile-editor.component.html | 8 ++++ .../profile-editor.component.ts | 6 ++- .../profile-page/profile-page.component.css | 37 ------------------- .../profile-page/profile-page.component.html | 14 +++---- frontend/src/app/profile/profile.service.ts | 2 + .../shared/user-lookup/user-lookup.widget.ts | 3 +- 14 files changed, 67 insertions(+), 52 deletions(-) diff --git a/backend/api/profile.py b/backend/api/profile.py index 3306dd04a..4c58de57a 100644 --- a/backend/api/profile.py +++ b/backend/api/profile.py @@ -66,6 +66,7 @@ def update_profile( email=profile.email, pronouns=profile.pronouns, accepted_community_agreement=profile.accepted_community_agreement, + bio=profile.bio, ) user = user_svc.create(user, user) else: @@ -75,6 +76,7 @@ def update_profile( user.pronouns = profile.pronouns user.onyen = onyen user.accepted_community_agreement = profile.accepted_community_agreement + user.bio = profile.bio user = user_svc.update(user, user) user_details = user_svc.get(user.pid) diff --git a/backend/api/user.py b/backend/api/user.py index f2892cfb1..24f617409 100644 --- a/backend/api/user.py +++ b/backend/api/user.py @@ -18,3 +18,13 @@ def search( ): """Search for users based on a query string which matches against name, onyen, and email address.""" return user_svc.search(subject, q) + + +@api.get("/{onyen}", tags=["Users"]) +def get_by_onyen( + onyen: str, + subject: User = Depends(registered_user), + user_svc: UserService = Depends(), +): + """Search for one user by their onyen""" + return user_svc.get_by_onyen(subject, onyen) diff --git a/backend/entities/user_entity.py b/backend/entities/user_entity.py index 7a134f0fd..787a1a55d 100644 --- a/backend/entities/user_entity.py +++ b/backend/entities/user_entity.py @@ -47,6 +47,8 @@ class UserEntity(EntityBase): accepted_community_agreement: Mapped[bool] = mapped_column( Boolean, nullable=False, default=False ) + # Bio of the user + bio: Mapped[str | None] = mapped_column(String(), nullable=True) # All of the roles for the given user. # NOTE: This field establishes a many-to-many relationship between the users and roles table. @@ -92,6 +94,7 @@ def from_model(cls, model: User) -> Self: github_id=model.github_id, github_avatar=model.github_avatar, accepted_community_agreement=model.accepted_community_agreement, + bio=model.bio, ) def to_model(self) -> User: @@ -113,6 +116,7 @@ def to_model(self) -> User: github_avatar=self.github_avatar, pronouns=self.pronouns, accepted_community_agreement=self.accepted_community_agreement, + bio=self.bio, ) def update(self, model: User) -> None: @@ -133,6 +137,7 @@ def update(self, model: User) -> None: self.github_id = model.github_id or None self.github_avatar = model.github_avatar or "" self.accepted_community_agreement = model.accepted_community_agreement + self.bio = model.bio def to_public_model(self) -> PublicUser: return PublicUser( @@ -142,4 +147,5 @@ def to_public_model(self) -> PublicUser: pronouns=self.pronouns, email=self.email, github_avatar=self.github_avatar, + bio=self.bio, ) diff --git a/backend/models/public_user.py b/backend/models/public_user.py index 22ad25850..a65c09970 100644 --- a/backend/models/public_user.py +++ b/backend/models/public_user.py @@ -22,3 +22,4 @@ class PublicUser(BaseModel): pronouns: str email: str github_avatar: str | None = None + bio: str | None = None diff --git a/backend/models/user.py b/backend/models/user.py index 544cf49f7..abea5e6e4 100644 --- a/backend/models/user.py +++ b/backend/models/user.py @@ -36,6 +36,7 @@ class User(UserIdentity, BaseModel): github_id: int | None = None github_avatar: str | None = None accepted_community_agreement: bool = False + bio: str | None = None class NewUser(User, BaseModel): @@ -63,3 +64,4 @@ class ProfileForm(BaseModel): pronouns: str email: str accepted_community_agreement: bool = False + bio: str | None = None diff --git a/backend/services/user.py b/backend/services/user.py index 2c7caad99..142ec34f5 100644 --- a/backend/services/user.py +++ b/backend/services/user.py @@ -7,7 +7,7 @@ from sqlalchemy import select, or_, func, cast, String from sqlalchemy.orm import Session from ..database import db_session -from ..models import User, UserDetails, Paginated, PaginationParams +from ..models import User, UserDetails, Paginated, PaginationParams, PublicUser from ..entities import UserEntity from .exceptions import ResourceNotFoundException from .permission import PermissionService @@ -68,6 +68,25 @@ def get_by_id(self, id: int) -> User: return user_entity.to_model() + def get_by_onyen(self, subject: User, onyen: str) -> PublicUser: + """Get a User by their onyen. + + Args: + onyen: The onyen of the user. + + Returns: + PublicUser + + Raises: + ResourceNotFoundException if the User ID is not found + """ + user_query = select(UserEntity).where(UserEntity.onyen == onyen) + user_entity = self._session.scalars(user_query).one_or_none() + if user_entity is None: + raise ResourceNotFoundException(f"User with {id} not found") + + return user_entity.to_public_model() + def search(self, _subject: User, query: str) -> list[User]: """Search for users by their name, onyen, email. @@ -84,7 +103,7 @@ def search(self, _subject: User, query: str) -> list[User]: UserEntity.last_name.ilike(f"%{query}%"), UserEntity.onyen.ilike(f"%{query}%"), UserEntity.email.ilike(f"%{query}%"), - cast(UserEntity.pid, String).ilike(f"%{query}%") + cast(UserEntity.pid, String).ilike(f"%{query}%"), ) statement = statement.where(criteria).limit(10) entities = self._session.execute(statement).scalars() diff --git a/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts b/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts index 36102552d..b3fcf875a 100644 --- a/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts +++ b/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts @@ -207,7 +207,8 @@ export class SectionEditorComponent { last_name: staff.last_name, pronouns: '', email: '', - github_avatar: '' + github_avatar: '', + bio: '' }; }) ?? []; } diff --git a/frontend/src/app/models.module.ts b/frontend/src/app/models.module.ts index 53b9b0ea8..09d5527db 100644 --- a/frontend/src/app/models.module.ts +++ b/frontend/src/app/models.module.ts @@ -21,6 +21,7 @@ export interface Profile { github_id: number | null; github_avatar: string | null; accepted_community_agreement: boolean; + bio: string | null; } /** Interface for UserSummary Type (used on frontend for user requests) */ @@ -33,6 +34,7 @@ export interface UserSummary { email: string | null; pronouns: string | null; permissions: Permission[]; + bio: string | null; } /** Interface for the Role Type */ diff --git a/frontend/src/app/profile/profile-editor/profile-editor.component.html b/frontend/src/app/profile/profile-editor/profile-editor.component.html index 23abb7bf8..58a9c2528 100644 --- a/frontend/src/app/profile/profile-editor/profile-editor.component.html +++ b/frontend/src/app/profile/profile-editor/profile-editor.component.html @@ -57,6 +57,14 @@ name="pronouns" required /> + + Bio (Markdown Supported) + + diff --git a/frontend/src/app/profile/profile-editor/profile-editor.component.ts b/frontend/src/app/profile/profile-editor/profile-editor.component.ts index b3974c9ac..1f3a17b50 100644 --- a/frontend/src/app/profile/profile-editor/profile-editor.component.ts +++ b/frontend/src/app/profile/profile-editor/profile-editor.component.ts @@ -30,7 +30,8 @@ export class ProfileEditorComponent implements OnInit { first_name: '', last_name: '', email: '', - pronouns: '' + pronouns: '', + bio: '' }); constructor( @@ -66,7 +67,8 @@ export class ProfileEditorComponent implements OnInit { first_name: profile.first_name, last_name: profile.last_name, email: profile.email, - pronouns: profile.pronouns + pronouns: profile.pronouns, + bio: profile.bio }); } diff --git a/frontend/src/app/profile/profile-page/profile-page.component.css b/frontend/src/app/profile/profile-page/profile-page.component.css index 3935effd0..8f35f07ac 100644 --- a/frontend/src/app/profile/profile-page/profile-page.component.css +++ b/frontend/src/app/profile/profile-page/profile-page.component.css @@ -128,43 +128,6 @@ mat-list-item { width: 200px; } -/* OLD */ - -/* .profile-management-actions { - display: flex; - flex-direction: column; - align-items: flex-start; - padding: 0 16px 16px 16px; -} - -.action-section { - width: 100% !important; -} - -.section-title { - font-size: large; - font-weight: 500; - margin: 16px 0px 5px 0px; -} - -.action-buttons { - display: flex; - flex: 1; -} - -.action-button { - margin-right: 10px; - white-space: normal; - word-wrap: normal; -} - -.token-text { - word-wrap: break-word; - margin-bottom: 0; -} - -*/ - .profile-edit-fab { position: absolute; bottom: 32px; diff --git a/frontend/src/app/profile/profile-page/profile-page.component.html b/frontend/src/app/profile/profile-page/profile-page.component.html index 32469fd9d..ad615a086 100644 --- a/frontend/src/app/profile/profile-page/profile-page.component.html +++ b/frontend/src/app/profile/profile-page/profile-page.component.html @@ -40,15 +40,11 @@
Profile Bio -

- Lorem ipsum **odor amet**, consectetuer adipiscing elit. Inceptos neque - vel curabitur orci est. Sem non bibendum lectus, vestibulum nec elementum - metus ad. Porta magna tempus, augue massa adipiscing netus. Congue - consequat hac erat pretium tempor. Conubia natoque orci pulvinar taciti - metus ad suscipit senectus nulla. Gravida vel dapibus taciti efficitur, - tempor phasellus varius. Finibus tristique enim sodales nascetur bibendum - bibendum nunc fermentum. [Test](https://apple.com) -

+ @if (profile.bio) { +

{{ profile.bio! }}

+ } @else { +

User does not have a bio.

+ } diff --git a/frontend/src/app/profile/profile.service.ts b/frontend/src/app/profile/profile.service.ts index 980fddf15..b297ce8c7 100644 --- a/frontend/src/app/profile/profile.service.ts +++ b/frontend/src/app/profile/profile.service.ts @@ -24,6 +24,7 @@ export interface Profile { role: number; permissions: Permission[]; accepted_community_agreement: boolean; + bio: string | null; } export interface PublicProfile { @@ -33,6 +34,7 @@ export interface PublicProfile { pronouns: string; email: string; github_avatar: string | null; + bio: string | null; } @Injectable({ diff --git a/frontend/src/app/shared/user-lookup/user-lookup.widget.ts b/frontend/src/app/shared/user-lookup/user-lookup.widget.ts index b18c934fb..7355bf3a2 100644 --- a/frontend/src/app/shared/user-lookup/user-lookup.widget.ts +++ b/frontend/src/app/shared/user-lookup/user-lookup.widget.ts @@ -75,7 +75,8 @@ export class UserLookup implements OnInit { last_name: user.last_name!, pronouns: user.pronouns!, email: user.email!, - github_avatar: user.github_avatar + github_avatar: user.github_avatar, + bio: user.bio }; this.users.push(organizer); } From edec850565e78c4da83a86de3292fe2bff9105a8 Mon Sep 17 00:00:00 2001 From: Ajay Gandecha Date: Tue, 23 Jul 2024 10:53:25 -0400 Subject: [PATCH 04/10] Create Public Profile --- backend/entities/user_entity.py | 1 + backend/models/public_user.py | 1 + .../section-editor.component.ts | 1 + .../src/app/profile/profile-routing.module.ts | 4 +- frontend/src/app/profile/profile.module.ts | 7 +- frontend/src/app/profile/profile.resolver.ts | 9 +- frontend/src/app/profile/profile.service.ts | 5 + .../public-profile-page.component.css | 145 ++++++++++++++++++ .../public-profile-page.component.html | 41 +++++ .../public-profile-page.component.ts | 32 ++++ .../shared/user-lookup/user-lookup.widget.ts | 1 + 11 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 frontend/src/app/profile/public-profile-page/public-profile-page.component.css create mode 100644 frontend/src/app/profile/public-profile-page/public-profile-page.component.html create mode 100644 frontend/src/app/profile/public-profile-page/public-profile-page.component.ts diff --git a/backend/entities/user_entity.py b/backend/entities/user_entity.py index 787a1a55d..359f722ac 100644 --- a/backend/entities/user_entity.py +++ b/backend/entities/user_entity.py @@ -147,5 +147,6 @@ def to_public_model(self) -> PublicUser: pronouns=self.pronouns, email=self.email, github_avatar=self.github_avatar, + github=self.github, bio=self.bio, ) diff --git a/backend/models/public_user.py b/backend/models/public_user.py index a65c09970..4af284be8 100644 --- a/backend/models/public_user.py +++ b/backend/models/public_user.py @@ -22,4 +22,5 @@ class PublicUser(BaseModel): pronouns: str email: str github_avatar: str | None = None + github: str | None = None bio: str | None = None diff --git a/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts b/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts index b3fcf875a..6a97a7b82 100644 --- a/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts +++ b/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts @@ -208,6 +208,7 @@ export class SectionEditorComponent { pronouns: '', email: '', github_avatar: '', + github: '', bio: '' }; }) ?? []; diff --git a/frontend/src/app/profile/profile-routing.module.ts b/frontend/src/app/profile/profile-routing.module.ts index a3cf5e68a..298587249 100644 --- a/frontend/src/app/profile/profile-routing.module.ts +++ b/frontend/src/app/profile/profile-routing.module.ts @@ -11,10 +11,12 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { ProfileEditorComponent } from './profile-editor/profile-editor.component'; import { ProfilePageComponent } from './profile-page/profile-page.component'; +import { PublicProfilePageComponent } from './public-profile-page/public-profile-page.component'; const routes: Routes = [ ProfileEditorComponent.Route, - ProfilePageComponent.Route + ProfilePageComponent.Route, + PublicProfilePageComponent.Route ]; @NgModule({ diff --git a/frontend/src/app/profile/profile.module.ts b/frontend/src/app/profile/profile.module.ts index f0d15b477..3219abd66 100644 --- a/frontend/src/app/profile/profile.module.ts +++ b/frontend/src/app/profile/profile.module.ts @@ -34,9 +34,14 @@ import { ProfilePageComponent } from './profile-page/profile-page.component'; import { ProfileEditorComponent } from './profile-editor/profile-editor.component'; import { ProfileRoutingModule } from './profile-routing.module'; import { MatChipsModule } from '@angular/material/chips'; +import { PublicProfilePageComponent } from './public-profile-page/public-profile-page.component'; @NgModule({ - declarations: [ProfilePageComponent, ProfileEditorComponent], + declarations: [ + ProfilePageComponent, + ProfileEditorComponent, + PublicProfilePageComponent + ], imports: [ CommonModule, MatTabsModule, diff --git a/frontend/src/app/profile/profile.resolver.ts b/frontend/src/app/profile/profile.resolver.ts index 4f602a6cb..368545077 100644 --- a/frontend/src/app/profile/profile.resolver.ts +++ b/frontend/src/app/profile/profile.resolver.ts @@ -1,6 +1,6 @@ import { inject } from '@angular/core'; import { ResolveFn } from '@angular/router'; -import { Profile, ProfileService } from './profile.service'; +import { Profile, ProfileService, PublicProfile } from './profile.service'; export const profileResolver: ResolveFn = ( route, @@ -8,3 +8,10 @@ export const profileResolver: ResolveFn = ( ) => { return inject(ProfileService).profile$; }; + +export const publicProfileResolver: ResolveFn = ( + route, + state +) => { + return inject(ProfileService).getByOnyen(route.params['onyen']); +}; diff --git a/frontend/src/app/profile/profile.service.ts b/frontend/src/app/profile/profile.service.ts index b297ce8c7..f817dbec7 100644 --- a/frontend/src/app/profile/profile.service.ts +++ b/frontend/src/app/profile/profile.service.ts @@ -34,6 +34,7 @@ export interface PublicProfile { pronouns: string; email: string; github_avatar: string | null; + github: string | null; bio: string | null; } @@ -98,4 +99,8 @@ export class ProfileService { unlinkGitHub() { return this.http.delete('/oauth/github'); } + + getByOnyen(onyen: string): Observable { + return this.http.get(`/api/user/${onyen}`); + } } diff --git a/frontend/src/app/profile/public-profile-page/public-profile-page.component.css b/frontend/src/app/profile/public-profile-page/public-profile-page.component.css new file mode 100644 index 000000000..a7a3e9b14 --- /dev/null +++ b/frontend/src/app/profile/public-profile-page/public-profile-page.component.css @@ -0,0 +1,145 @@ +/** +* profile-page.component.css +* +* The profile page should include basic information about the user, the +* user's organizations, and the user's past/upcoming events in a simple, +* easily navigable manner. Major details about events should be +* expandable so the screen is not overcrowded. +* +*/ + +p { + margin-bottom: 0; + } + + .header-row { + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + margin-left: 8px; + } + + .actions-list { + display: flex; + flex-direction: column; + } + + .actions-row { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + } + + .bottom-gap { + margin-bottom: 16px; + } + + .trailing-row-button { + margin-left: auto; + } + + .mat-mdc-list-item-icon { + margin-right: 16px; + } + + .column { + display: flex; + flex-direction: column; + } + + .user-avatar { + min-width: 40px; + max-width: 40px; + min-height: 40px; + max-height: 40px; + background-color: #4786c6; + line-height: 40px; + border-radius: 100%; + text-align: center; + } + + .no-hover-chipset { + --mdc-chip-focus-state-layer-color: transparent !important; + --mdc-chip-hover-state-layer-color: transparent !important; + } + + .mat-mdc-chip { + height: 26px; + } + + .links-row { + display: flex; + flex-direction: row; + margin-top: 4px; + align-items: center; + margin-bottom: 12px; + margin-left: 48px; + gap: 4px; + } + + #github-icon { + margin-left: 6px; + } + + #github-link { + margin-right: 4px; + } + + #link-github-button { + height: 20px; + padding-left: 2px; + padding-right: 2px; + font-weight: 500; + } + + #unlink-github-button { + height: 20px; + padding-left: 2px; + padding-right: 2px; + font-weight: 400; + } + + #bio-divider { + margin-top: 16px; + margin-bottom: 16px; + } + + mat-card-subtitle { + margin-top: 4px; + margin-bottom: 4px; + } + + .mat-mdc-list-item-icon { + margin-left: 0; + } + + mat-list-item { + display: flex; + flex-direction: row; + } + + .mdc-list-item__content { + width: 200px !important; + } + + .mdc-list-item__primary-text { + width: 200px; + } + + .profile-edit-fab { + position: absolute; + bottom: 32px; + right: 32px; + display: flex; + flex-direction: column; + gap: 24px; + align-items: flex-end; + } + + @media (prefers-color-scheme: light) { + #github_link { + color: black; + } + } \ No newline at end of file diff --git a/frontend/src/app/profile/public-profile-page/public-profile-page.component.html b/frontend/src/app/profile/public-profile-page/public-profile-page.component.html new file mode 100644 index 000000000..b623a9768 --- /dev/null +++ b/frontend/src/app/profile/public-profile-page/public-profile-page.component.html @@ -0,0 +1,41 @@ + + + @if (profile.github_avatar) { + + } @else if (profile.first_name && profile.last_name) { +

+ {{ profile.first_name.charAt(0) }}{{ profile.last_name.charAt(0) }} +

+ } + +
+ + {{ profile.first_name }} {{ profile.last_name }} + + + + {{ profile.pronouns }} + + +
+
+ + + + Profile Bio + @if (profile.bio) { +

{{ profile.bio! }}

+ } @else { +

User does not have a bio.

+ } +
+
diff --git a/frontend/src/app/profile/public-profile-page/public-profile-page.component.ts b/frontend/src/app/profile/public-profile-page/public-profile-page.component.ts new file mode 100644 index 000000000..952a42dc5 --- /dev/null +++ b/frontend/src/app/profile/public-profile-page/public-profile-page.component.ts @@ -0,0 +1,32 @@ +import { Component } from '@angular/core'; +import { ActivatedRoute, Route } from '@angular/router'; +import { isAuthenticated } from 'src/app/gate/gate.guard'; +import { publicProfileResolver } from '../profile.resolver'; +import { PublicProfile } from '../profile.service'; + +@Component({ + selector: 'app-public-profile-page', + templateUrl: './public-profile-page.component.html', + styleUrl: './public-profile-page.component.css' +}) +export class PublicProfilePageComponent { + public static Route: Route = { + path: ':onyen', + component: PublicProfilePageComponent, + title: 'Profile Page', + canActivate: [isAuthenticated], + resolve: { + profile: publicProfileResolver + } + }; + + profile: PublicProfile; + + constructor(private route: ActivatedRoute) { + /** Get currently-logged-in user. */ + const data = this.route.snapshot.data as { + profile: PublicProfile; + }; + this.profile = data.profile; + } +} diff --git a/frontend/src/app/shared/user-lookup/user-lookup.widget.ts b/frontend/src/app/shared/user-lookup/user-lookup.widget.ts index 7355bf3a2..5a55ffd6b 100644 --- a/frontend/src/app/shared/user-lookup/user-lookup.widget.ts +++ b/frontend/src/app/shared/user-lookup/user-lookup.widget.ts @@ -76,6 +76,7 @@ export class UserLookup implements OnInit { pronouns: user.pronouns!, email: user.email!, github_avatar: user.github_avatar, + github: user.github, bio: user.bio }; this.users.push(organizer); From 24db42c476c6ab86b150d47f1aec017285a62b52 Mon Sep 17 00:00:00 2001 From: Ajay Gandecha Date: Tue, 23 Jul 2024 10:59:25 -0400 Subject: [PATCH 05/10] Add Public Profile Page, Link to Public Profile from Events Page --- backend/entities/event_registration_entity.py | 3 +++ backend/entities/user_entity.py | 1 + backend/models/public_user.py | 1 + backend/test/services/event/event_test_data.py | 7 +++++++ .../section/section-editor/section-editor.component.ts | 1 + .../app/event/widgets/event-card/event-card.widget.html | 8 ++++---- .../featured-event-card/featured-event-card.widget.html | 8 ++++---- frontend/src/app/profile/profile.service.ts | 1 + frontend/src/app/shared/user-lookup/user-lookup.widget.ts | 1 + 9 files changed, 23 insertions(+), 8 deletions(-) diff --git a/backend/entities/event_registration_entity.py b/backend/entities/event_registration_entity.py index 47af8978b..a183257c6 100644 --- a/backend/entities/event_registration_entity.py +++ b/backend/entities/event_registration_entity.py @@ -107,9 +107,12 @@ def to_flat_model(self) -> PublicUser: """ return PublicUser( id=self.user_id, + onyen=self.user.onyen, first_name=self.user.first_name, last_name=self.user.last_name, pronouns=self.user.pronouns, email=self.user.email, github_avatar=self.user.github_avatar, + github=self.user.github, + bio=self.user.bio, ) diff --git a/backend/entities/user_entity.py b/backend/entities/user_entity.py index 359f722ac..0f3bc3bac 100644 --- a/backend/entities/user_entity.py +++ b/backend/entities/user_entity.py @@ -142,6 +142,7 @@ def update(self, model: User) -> None: def to_public_model(self) -> PublicUser: return PublicUser( id=self.id, + onyen=self.onyen, first_name=self.first_name, last_name=self.last_name, pronouns=self.pronouns, diff --git a/backend/models/public_user.py b/backend/models/public_user.py index 4af284be8..457f9e243 100644 --- a/backend/models/public_user.py +++ b/backend/models/public_user.py @@ -17,6 +17,7 @@ class PublicUser(BaseModel): """ id: int | None + onyen: str first_name: str last_name: str pronouns: str diff --git a/backend/test/services/event/event_test_data.py b/backend/test/services/event/event_test_data.py index 043d46b43..0b4e9b283 100644 --- a/backend/test/services/event/event_test_data.py +++ b/backend/test/services/event/event_test_data.py @@ -67,6 +67,7 @@ organizers=[ PublicUser( id=root.id, + onyen=root.onyen, first_name=root.first_name, last_name=root.last_name, pronouns=root.pronouns, @@ -118,6 +119,7 @@ organizers=[ PublicUser( id=user.id, + onyen=user.onyen, first_name=user.first_name, last_name=user.last_name, pronouns=user.pronouns, @@ -125,6 +127,7 @@ ), PublicUser( id=ambassador.id, + onyen=ambassador.onyen, first_name=ambassador.first_name, last_name=ambassador.last_name, pronouns=ambassador.pronouns, @@ -156,6 +159,7 @@ organizers=[ PublicUser( id=user.id, + onyen=user.onyen, first_name=user.first_name, last_name=user.last_name, pronouns=user.pronouns, @@ -163,6 +167,7 @@ ), PublicUser( id=ambassador.id, + onyen=user.onyen, first_name=ambassador.first_name, last_name=ambassador.last_name, pronouns=ambassador.pronouns, @@ -170,6 +175,7 @@ ), PublicUser( id=root.id, + onyen=user.onyen, first_name=root.first_name, last_name=root.last_name, pronouns=root.pronouns, @@ -190,6 +196,7 @@ organizers=[ PublicUser( id=user.id, + onyen=user.onyen, first_name=user.first_name, last_name=user.last_name, pronouns=user.pronouns, diff --git a/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts b/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts index 6a97a7b82..bb8f1971f 100644 --- a/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts +++ b/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts @@ -203,6 +203,7 @@ export class SectionEditorComponent { .map((staff) => { return { id: staff.user_id!, + onyen: '', first_name: staff.first_name, last_name: staff.last_name, pronouns: '', diff --git a/frontend/src/app/event/widgets/event-card/event-card.widget.html b/frontend/src/app/event/widgets/event-card/event-card.widget.html index 2bdcdad74..b12107e9c 100644 --- a/frontend/src/app/event/widgets/event-card/event-card.widget.html +++ b/frontend/src/app/event/widgets/event-card/event-card.widget.html @@ -31,7 +31,7 @@ - {{ event.organization_name }} - + @if (event.organizers.length > 0) { @@ -47,7 +47,7 @@ @for (organizer of event.organizers; track organizer.id) { - + @if (organizer.github_avatar && organizer.github_avatar !== '') { + }

diff --git a/frontend/src/app/event/widgets/featured-event-card/featured-event-card.widget.html b/frontend/src/app/event/widgets/featured-event-card/featured-event-card.widget.html index d278c0cb4..cacb15024 100644 --- a/frontend/src/app/event/widgets/featured-event-card/featured-event-card.widget.html +++ b/frontend/src/app/event/widgets/featured-event-card/featured-event-card.widget.html @@ -19,7 +19,7 @@ - {{ event.organization_name }} - + @if (event.organizers.length > 0) { @@ -35,7 +35,7 @@ @for (organizer of event.organizers; track organizer.id) { - + @if (organizer.github_avatar && organizer.github_avatar !== '') { + } diff --git a/frontend/src/app/profile/profile.service.ts b/frontend/src/app/profile/profile.service.ts index f817dbec7..a4c988876 100644 --- a/frontend/src/app/profile/profile.service.ts +++ b/frontend/src/app/profile/profile.service.ts @@ -29,6 +29,7 @@ export interface Profile { export interface PublicProfile { id: number; + onyen: string; first_name: string; last_name: string; pronouns: string; diff --git a/frontend/src/app/shared/user-lookup/user-lookup.widget.ts b/frontend/src/app/shared/user-lookup/user-lookup.widget.ts index 5a55ffd6b..96c071842 100644 --- a/frontend/src/app/shared/user-lookup/user-lookup.widget.ts +++ b/frontend/src/app/shared/user-lookup/user-lookup.widget.ts @@ -71,6 +71,7 @@ export class UserLookup implements OnInit { if (this.users.filter((e) => e.id === user.id).length == 0) { let organizer: PublicProfile = { id: user.id!, + onyen: user.onyen, first_name: user.first_name!, last_name: user.last_name!, pronouns: user.pronouns!, From 9c414dda33f73e7ab31a39345f0423f92b58d055 Mon Sep 17 00:00:00 2001 From: Ajay Gandecha Date: Tue, 23 Jul 2024 13:25:41 -0400 Subject: [PATCH 06/10] Fix FAB Color --- .../src/app/profile/profile-page/profile-page.component.html | 2 +- frontend/src/styles/styles.scss | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/profile/profile-page/profile-page.component.html b/frontend/src/app/profile/profile-page/profile-page.component.html index ad615a086..59422beaa 100644 --- a/frontend/src/app/profile/profile-page/profile-page.component.html +++ b/frontend/src/app/profile/profile-page/profile-page.component.html @@ -88,7 +88,7 @@

- diff --git a/frontend/src/styles/styles.scss b/frontend/src/styles/styles.scss index 37fb16dfb..a08163bdc 100644 --- a/frontend/src/styles/styles.scss +++ b/frontend/src/styles/styles.scss @@ -88,8 +88,11 @@ html, body { @mixin mat-button-styles($theme) { .mat-mdc-fab { + background-color: mat.get-theme-color($theme, secondary); + color: mat.get-theme-color($theme, on-secondary); + .mat-icon { - color: mat.get-theme-color($theme, on-primary); + color: mat.get-theme-color($theme, on-secondary); } } From d59abc64a1e4c02d9a8585bb804713230d9e5691 Mon Sep 17 00:00:00 2001 From: Ajay Gandecha Date: Tue, 23 Jul 2024 13:30:02 -0400 Subject: [PATCH 07/10] Add Redirect from Public Profile to Regular Profile if User is Same --- frontend/src/app/profile/profile.resolver.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/profile/profile.resolver.ts b/frontend/src/app/profile/profile.resolver.ts index 368545077..2b2c6291e 100644 --- a/frontend/src/app/profile/profile.resolver.ts +++ b/frontend/src/app/profile/profile.resolver.ts @@ -1,6 +1,7 @@ import { inject } from '@angular/core'; -import { ResolveFn } from '@angular/router'; +import { ResolveFn, Router } from '@angular/router'; import { Profile, ProfileService, PublicProfile } from './profile.service'; +import { tap } from 'rxjs'; export const profileResolver: ResolveFn = ( route, @@ -13,5 +14,13 @@ export const publicProfileResolver: ResolveFn = ( route, state ) => { - return inject(ProfileService).getByOnyen(route.params['onyen']); + let profileService = inject(ProfileService); + let router = inject(Router); + return profileService.getByOnyen(route.params['onyen']).pipe( + tap((profile) => { + if (profile.onyen == profileService.profile()?.onyen) { + router.navigateByUrl('/profile'); + } + }) + ); }; From e8bdb10c0fa242193a3f2d9c0f372d2fea4c4137 Mon Sep 17 00:00:00 2001 From: Ajay Gandecha Date: Tue, 23 Jul 2024 16:05:06 -0400 Subject: [PATCH 08/10] Add Website and LinkedIn Fields --- backend/api/profile.py | 4 + backend/entities/event_registration_entity.py | 2 + backend/entities/user_entity.py | 12 + backend/models/public_user.py | 2 + backend/models/user.py | 4 + .../test/services/event/event_test_data.py | 17 ++ .../section-editor.component.ts | 4 +- frontend/src/app/models.module.ts | 2 + .../profile-editor.component.html | 16 ++ .../profile-editor.component.ts | 6 +- .../profile-page/profile-page.component.css | 8 +- .../profile-page/profile-page.component.html | 40 ++- frontend/src/app/profile/profile.service.ts | 4 + .../public-profile-page.component.css | 272 +++++++++--------- .../public-profile-page.component.html | 36 ++- .../shared/user-lookup/user-lookup.widget.ts | 4 +- frontend/src/styles/styles.scss | 5 + 17 files changed, 289 insertions(+), 149 deletions(-) diff --git a/backend/api/profile.py b/backend/api/profile.py index 4c58de57a..e74042a4a 100644 --- a/backend/api/profile.py +++ b/backend/api/profile.py @@ -67,6 +67,8 @@ def update_profile( pronouns=profile.pronouns, accepted_community_agreement=profile.accepted_community_agreement, bio=profile.bio, + linkedin=profile.linkedin, + website=profile.website, ) user = user_svc.create(user, user) else: @@ -77,6 +79,8 @@ def update_profile( user.onyen = onyen user.accepted_community_agreement = profile.accepted_community_agreement user.bio = profile.bio + user.linkedin = profile.linkedin + user.website = profile.website user = user_svc.update(user, user) user_details = user_svc.get(user.pid) diff --git a/backend/entities/event_registration_entity.py b/backend/entities/event_registration_entity.py index a183257c6..27cc6be5b 100644 --- a/backend/entities/event_registration_entity.py +++ b/backend/entities/event_registration_entity.py @@ -115,4 +115,6 @@ def to_flat_model(self) -> PublicUser: github_avatar=self.user.github_avatar, github=self.user.github, bio=self.user.bio, + linkedin=self.user.linkedin, + website=self.user.website, ) diff --git a/backend/entities/user_entity.py b/backend/entities/user_entity.py index 0f3bc3bac..79d8facdb 100644 --- a/backend/entities/user_entity.py +++ b/backend/entities/user_entity.py @@ -49,6 +49,10 @@ class UserEntity(EntityBase): ) # Bio of the user bio: Mapped[str | None] = mapped_column(String(), nullable=True) + # LinkedIn URL for the user + linkedin: Mapped[str | None] = mapped_column(String(), nullable=True) + # Website of the user + website: Mapped[str | None] = mapped_column(String(), nullable=True) # All of the roles for the given user. # NOTE: This field establishes a many-to-many relationship between the users and roles table. @@ -95,6 +99,8 @@ def from_model(cls, model: User) -> Self: github_avatar=model.github_avatar, accepted_community_agreement=model.accepted_community_agreement, bio=model.bio, + linkedin=model.linkedin, + website=model.website, ) def to_model(self) -> User: @@ -117,6 +123,8 @@ def to_model(self) -> User: pronouns=self.pronouns, accepted_community_agreement=self.accepted_community_agreement, bio=self.bio, + linkedin=self.linkedin, + website=self.website, ) def update(self, model: User) -> None: @@ -138,6 +146,8 @@ def update(self, model: User) -> None: self.github_avatar = model.github_avatar or "" self.accepted_community_agreement = model.accepted_community_agreement self.bio = model.bio + self.linkedin = model.linkedin + self.website = model.website def to_public_model(self) -> PublicUser: return PublicUser( @@ -150,4 +160,6 @@ def to_public_model(self) -> PublicUser: github_avatar=self.github_avatar, github=self.github, bio=self.bio, + linkedin=self.linkedin, + website=self.website, ) diff --git a/backend/models/public_user.py b/backend/models/public_user.py index 457f9e243..0af4936d1 100644 --- a/backend/models/public_user.py +++ b/backend/models/public_user.py @@ -25,3 +25,5 @@ class PublicUser(BaseModel): github_avatar: str | None = None github: str | None = None bio: str | None = None + linkedin: str | None = None + website: str | None = None diff --git a/backend/models/user.py b/backend/models/user.py index abea5e6e4..cee9c2f4d 100644 --- a/backend/models/user.py +++ b/backend/models/user.py @@ -37,6 +37,8 @@ class User(UserIdentity, BaseModel): github_avatar: str | None = None accepted_community_agreement: bool = False bio: str | None = None + linkedin: str | None = None + website: str | None = None class NewUser(User, BaseModel): @@ -65,3 +67,5 @@ class ProfileForm(BaseModel): email: str accepted_community_agreement: bool = False bio: str | None = None + linkedin: str | None = None + website: str | None = None diff --git a/backend/test/services/event/event_test_data.py b/backend/test/services/event/event_test_data.py index 0b4e9b283..efb9b81b1 100644 --- a/backend/test/services/event/event_test_data.py +++ b/backend/test/services/event/event_test_data.py @@ -72,6 +72,8 @@ last_name=root.last_name, pronouns=root.pronouns, email=root.email, + linkedin=root.linkedin, + website=root.website, ) ], ) @@ -99,10 +101,13 @@ organizers=[ PublicUser( id=user.id, + onyen=user.onyen, first_name=user.first_name, last_name=user.last_name, pronouns=user.pronouns, email=user.email, + linkedin=user.linkedin, + website=user.website, ), ], ) @@ -124,6 +129,8 @@ last_name=user.last_name, pronouns=user.pronouns, email=user.email, + linkedin=user.linkedin, + website=user.website, ), PublicUser( id=ambassador.id, @@ -132,6 +139,8 @@ last_name=ambassador.last_name, pronouns=ambassador.pronouns, email=ambassador.email, + linkedin=ambassador.linkedin, + website=ambassador.website, ), ], ) @@ -164,6 +173,8 @@ last_name=user.last_name, pronouns=user.pronouns, email=user.email, + linkedin=user.linkedin, + website=user.website, ), PublicUser( id=ambassador.id, @@ -172,6 +183,8 @@ last_name=ambassador.last_name, pronouns=ambassador.pronouns, email=ambassador.email, + linkedin=ambassador.linkedin, + website=ambassador.website, ), PublicUser( id=root.id, @@ -180,6 +193,8 @@ last_name=root.last_name, pronouns=root.pronouns, email=root.email, + linkedin=root.linkedin, + website=root.website, ), ], ) @@ -201,6 +216,8 @@ last_name=user.last_name, pronouns=user.pronouns, email=user.email, + linkedin=user.linkedin, + website=user.website, ), ], ) diff --git a/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts b/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts index bb8f1971f..12eb557e5 100644 --- a/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts +++ b/frontend/src/app/academics/academics-admin/section/section-editor/section-editor.component.ts @@ -210,7 +210,9 @@ export class SectionEditorComponent { email: '', github_avatar: '', github: '', - bio: '' + bio: '', + linkedin: '', + website: '' }; }) ?? []; } diff --git a/frontend/src/app/models.module.ts b/frontend/src/app/models.module.ts index 09d5527db..2452d89a7 100644 --- a/frontend/src/app/models.module.ts +++ b/frontend/src/app/models.module.ts @@ -22,6 +22,8 @@ export interface Profile { github_avatar: string | null; accepted_community_agreement: boolean; bio: string | null; + linkedin: string | null; + website: string | null; } /** Interface for UserSummary Type (used on frontend for user requests) */ diff --git a/frontend/src/app/profile/profile-editor/profile-editor.component.html b/frontend/src/app/profile/profile-editor/profile-editor.component.html index 58a9c2528..40c0bf89b 100644 --- a/frontend/src/app/profile/profile-editor/profile-editor.component.html +++ b/frontend/src/app/profile/profile-editor/profile-editor.component.html @@ -48,6 +48,22 @@ >Must provide a valid UNC email address. + + LinkedIn Profile Link + + + + Personal Website Link + + Pronouns