diff --git a/CHANGELOG.md b/CHANGELOG.md index 4656515..d9adb8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.0-rc.13 (2024-03-26) + +This was a version bump only, there were no code changes. + ## 1.0.0-rc.12 (2024-03-14) This was a version bump only, there were no code changes. \ No newline at end of file diff --git a/apps/demo/src/app/app.routes.ts b/apps/demo/src/app/app.routes.ts index b4543ed..b20e855 100644 --- a/apps/demo/src/app/app.routes.ts +++ b/apps/demo/src/app/app.routes.ts @@ -26,6 +26,10 @@ import { RegisterOrSignInComponent as BootstrapRegisterOrSignInComponent } from export const appRoutes: Routes = [ { path: '', component: MainComponent }, { path: 'sign-in', redirectTo: 'primeng/sign-in' }, + { path: 'register', redirectTo: 'primeng/register' }, + { path: 'set-password', redirectTo: 'primeng/set-password' }, + { path: 'reset-password', redirectTo: 'primeng/reset-password' }, + { path: 'auth', redirectTo: 'primeng/auth' }, { path: 'private-content', component: PrivateContentComponent, diff --git a/apps/demo/src/app/main/main.component.html b/apps/demo/src/app/main/main.component.html index 6b5ab51..2129e4f 100644 --- a/apps/demo/src/app/main/main.component.html +++ b/apps/demo/src/app/main/main.component.html @@ -59,7 +59,7 @@

@if(supabase.isSignedIn){

Display Name: - +

} @if(supabase.isSignedIn){
@@ -86,14 +86,14 @@

Coming soon...

Coming soon... >

Coming soon...

Coming soon... > ng-supabase
- +
diff --git a/apps/demo/src/app/toolbar/toolbar.component.ts b/apps/demo/src/app/toolbar/toolbar.component.ts index 8b07b91..0bf7666 100644 --- a/apps/demo/src/app/toolbar/toolbar.component.ts +++ b/apps/demo/src/app/toolbar/toolbar.component.ts @@ -10,10 +10,13 @@ import { import { ButtonModule } from 'primeng/button'; import { ToolbarModule } from 'primeng/toolbar'; +// ng-supabase. +import { ActiveUserAvatarButtonComponent } from '@ng-supabase/primeng'; + @Component({ selector: 'ng-supabase-toolbar', standalone: true, - imports: [ToolbarModule, ButtonModule], + imports: [ToolbarModule, ButtonModule, ActiveUserAvatarButtonComponent], templateUrl: './toolbar.component.html', styleUrl: './toolbar.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/libs/bootstrap/package.json b/libs/bootstrap/package.json index 6b58588..7991f3b 100644 --- a/libs/bootstrap/package.json +++ b/libs/bootstrap/package.json @@ -1,6 +1,6 @@ { "name": "@ng-supabase/bootstrap", - "version": "1.0.0-rc.12", + "version": "1.0.0-rc.13", "author": "Rusty Green ", "contributors": [ "Rusty Green " diff --git a/libs/core/package.json b/libs/core/package.json index 3c719e4..28254ba 100644 --- a/libs/core/package.json +++ b/libs/core/package.json @@ -1,6 +1,6 @@ { "name": "@ng-supabase/core", - "version": "1.0.0-rc.12", + "version": "1.0.0-rc.13", "author": "Rusty Green ", "contributors": [ "Rusty Green " diff --git a/libs/core/src/index.ts b/libs/core/src/index.ts index 8364cea..06fc473 100644 --- a/libs/core/src/index.ts +++ b/libs/core/src/index.ts @@ -29,9 +29,16 @@ export * from './lib/storage/persistent-storage.service'; export * from './lib/register/register.component'; +export * from './lib/user-avatar/user-avatar.component'; + +export * from './lib/user-avatar-button/user-avatar-button.component'; + +export * from './lib/active-user-avatar-button/active-user-avatar-button.component'; + export * from './lib/register-or-sign-in/register-or-sign-in.component'; export * from './lib/wait-message'; +export * from './lib/initials.pipe'; export * from './lib/route.service'; export * from './lib/supabase-config'; export * from './lib/supabase.service'; diff --git a/libs/core/src/lib/active-user-avatar-button/active-user-avatar-button.component.html b/libs/core/src/lib/active-user-avatar-button/active-user-avatar-button.component.html new file mode 100644 index 0000000..e69de29 diff --git a/libs/core/src/lib/active-user-avatar-button/active-user-avatar-button.component.scss b/libs/core/src/lib/active-user-avatar-button/active-user-avatar-button.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/libs/core/src/lib/active-user-avatar-button/active-user-avatar-button.component.spec.ts b/libs/core/src/lib/active-user-avatar-button/active-user-avatar-button.component.spec.ts new file mode 100644 index 0000000..e9b24bb --- /dev/null +++ b/libs/core/src/lib/active-user-avatar-button/active-user-avatar-button.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActiveUserAvatarButtonComponent } from './active-user-avatar-button.component'; + +describe('ActiveUserAvatarButtonComponent', () => { + let component: ActiveUserAvatarButtonComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ActiveUserAvatarButtonComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ActiveUserAvatarButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/libs/core/src/lib/active-user-avatar-button/active-user-avatar-button.component.ts b/libs/core/src/lib/active-user-avatar-button/active-user-avatar-button.component.ts new file mode 100644 index 0000000..2c72db0 --- /dev/null +++ b/libs/core/src/lib/active-user-avatar-button/active-user-avatar-button.component.ts @@ -0,0 +1,42 @@ +// Angular. +import { Router } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { + OnInit, + inject, + signal, + Component, + ChangeDetectionStrategy, +} from '@angular/core'; + +// Local. +import { SupabaseConfig } from '../supabase-config'; +import { SupabaseService } from '../supabase.service'; + +@Component({ + selector: 'supabase-active-user-avatar-button', + standalone: true, + imports: [CommonModule], + templateUrl: './active-user-avatar-button.component.html', + styleUrl: './active-user-avatar-button.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ActiveUserAvatarButtonComponent implements OnInit { + loading = signal(true); + + protected router = inject(Router); + protected config = inject(SupabaseConfig); + protected supabase = inject(SupabaseService); + + async ngOnInit(): Promise { + await this.supabase.clientReady; + this.loading.set(false); + } + + signOut(): void { + this.supabase.client.auth.signOut(); + if (this.config.routes.postSignOut) { + this.router.navigate([this.config.routes.postSignOut]); + } + } +} diff --git a/libs/core/src/lib/initials.pipe.ts b/libs/core/src/lib/initials.pipe.ts new file mode 100644 index 0000000..843fee7 --- /dev/null +++ b/libs/core/src/lib/initials.pipe.ts @@ -0,0 +1,19 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'initials', + standalone: true, +}) +export class InitialsPipe implements PipeTransform { + transform(fullName: string | null | undefined, numChars: number = 2): string { + if (!fullName) { + return ''; + } + + return fullName + .split(' ') + .slice(0, numChars) + .map((n) => n[0].toUpperCase()) + .join(''); + } +} diff --git a/libs/core/src/lib/supabase-config.ts b/libs/core/src/lib/supabase-config.ts index 5974f4b..0100ebf 100644 --- a/libs/core/src/lib/supabase-config.ts +++ b/libs/core/src/lib/supabase-config.ts @@ -20,6 +20,7 @@ export const DEFAULT_ROUTES: ComponentRoutes = { registerOrSignIn: '/auth', setPassword: '/set-password', resetPassword: '/reset-password', + postSignOut: '/sign-in', }; export interface SupabaseConfigProperties { @@ -31,6 +32,7 @@ export interface SupabaseConfigProperties { register?: RegisterProperties; setPassword?: SetPasswordProperties; routes?: Partial; + profile?: ProfileProperties; } interface ComponentRoutes { @@ -41,6 +43,7 @@ interface ComponentRoutes { setPassword: string; resetPassword: string; userProfile?: string; + postSignOut?: string; } interface UserRegistrationMetadata { @@ -56,6 +59,12 @@ interface RegisterProperties { metadata?: UserRegistrationMetadata[]; } +interface ProfileProperties { + table?: string; + firstNameField?: string; + lastNameField?: string; +} + type SocialSignInFn = (social: SocialSignIn) => boolean | void; interface SignInConfigProperties { @@ -91,6 +100,17 @@ class SetPasswordConfig implements SetPasswordProperties { } } +class ProfileConfig implements ProfileProperties { + table = ''; + userIdField = 'user_id'; + firstNameField = 'first_name'; + lastNameField = 'last_name'; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} + class RegisterConfig implements RegisterConfig { title = ''; metadata: UserRegistrationMetadata[] = []; @@ -139,6 +159,7 @@ export class SupabaseConfig { register: RegisterConfig; routes: ComponentRoutes = DEFAULT_ROUTES; redirectParamName: string | null | undefined = 'redirect'; + profile: ProfileConfig; constructor(init: SupabaseConfigProperties) { Object.assign(this.routes, init.routes); @@ -146,6 +167,7 @@ export class SupabaseConfig { this.setPassword = new SetPasswordConfig(init.setPassword); this.signIn = new SignInConfig(init.signIn); this.register = new RegisterConfig(init.register); + this.profile = new ProfileConfig(init.profile); this.api = new BehaviorSubject({ url: init.apiUrl, key: init.apiKey, diff --git a/libs/core/src/lib/supabase.service.ts b/libs/core/src/lib/supabase.service.ts index 052e7ba..a1e8485 100644 --- a/libs/core/src/lib/supabase.service.ts +++ b/libs/core/src/lib/supabase.service.ts @@ -24,9 +24,11 @@ export class SupabaseService { readonly initialized = new BehaviorSubject(false); readonly session = new BehaviorSubject(null); readonly user = new BehaviorSubject(null); - readonly displayName = new BehaviorSubject(''); + readonly userDisplayName = new BehaviorSubject(''); + readonly userSubheading = new BehaviorSubject(''); + readonly userProfile = new BehaviorSubject(null); readonly signedIn = new BehaviorSubject(false); - + readonly loading = new BehaviorSubject(true); readonly clientReady: Promise; get isSignedIn(): boolean { @@ -42,10 +44,7 @@ export class SupabaseService { private readonly log: LogService, private readonly config: SupabaseConfig ) { - this.user.subscribe((user: User | null) => { - const name = user ? user.email || user.id : ''; - this.displayName.next(name); - }); + this.user.subscribe((user: User | null) => this.setUserInformation(user)); this.clientReady = firstValueFrom( this.initialized.pipe( @@ -66,6 +65,54 @@ export class SupabaseService { ); } + private async setUserInformation(user: User | null): Promise { + const profileTable = this.config.profile.table; + let displayName = ''; + + if (user && profileTable) { + this.log.debug(`Retrieving user profile for user ID '${user.id}'`); + const { error, data } = await this.client + .from(profileTable) + .select() + .eq(this.config.profile.userIdField, user.id) + .limit(1) + .single(); + + if (error) { + this.log.error( + `Failed to retrieve user profile. ${error.details}`, + error as unknown as Error + ); + } + + if (data) { + const firstName = data[this.config.profile.firstNameField]; + const lastName = data[this.config.profile.lastNameField]; + displayName = `${firstName || ''} ${lastName || ''}`.trim(); + this.log.debug( + `Retrieving display name of '${displayName}' from profile` + ); + } else { + this.log.warn(`No profile found for user ID '${user.id}'`); + } + } + + displayName = displayName || this.extractDisplay(user); + const subheading = + displayName === user?.email + ? '' + : user?.user_metadata?.['title'] || user?.email || ''; + + this.userDisplayName.next(displayName); + this.userSubheading.next(subheading); + } + + private extractDisplay(user: User | null): string { + const { first_name, last_name } = user?.user_metadata || {}; + const display = `${first_name || ''} ${last_name || ''}`.trim(); + return user ? display || user.email || user.id : ''; + } + private setup(): void { if (this.isSignedIn) { this.setStateForSignedOut(); @@ -89,6 +136,7 @@ export class SupabaseService { this.authChange.next(event); if (event === 'INITIAL_SESSION') { this.initialized.next(true); + this.loading.next(false); } else if (event === 'SIGNED_IN') { this.signedIn.next(true); this.tryGetSession(); diff --git a/libs/core/src/lib/user-avatar-button/user-avatar-button.component.html b/libs/core/src/lib/user-avatar-button/user-avatar-button.component.html new file mode 100644 index 0000000..e69de29 diff --git a/libs/core/src/lib/user-avatar-button/user-avatar-button.component.scss b/libs/core/src/lib/user-avatar-button/user-avatar-button.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/libs/core/src/lib/user-avatar-button/user-avatar-button.component.spec.ts b/libs/core/src/lib/user-avatar-button/user-avatar-button.component.spec.ts new file mode 100644 index 0000000..ad0293f --- /dev/null +++ b/libs/core/src/lib/user-avatar-button/user-avatar-button.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { UserAvatarButtonComponent } from './user-avatar-button.component'; + +describe('UserAvatarButtonComponent', () => { + let component: UserAvatarButtonComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UserAvatarButtonComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(UserAvatarButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/libs/core/src/lib/user-avatar-button/user-avatar-button.component.ts b/libs/core/src/lib/user-avatar-button/user-avatar-button.component.ts new file mode 100644 index 0000000..a980988 --- /dev/null +++ b/libs/core/src/lib/user-avatar-button/user-avatar-button.component.ts @@ -0,0 +1,13 @@ +// Angular. +import { CommonModule } from '@angular/common'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +@Component({ + selector: 'supabase-user-avatar-button', + standalone: true, + imports: [CommonModule], + templateUrl: './user-avatar-button.component.html', + styleUrl: './user-avatar-button.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class UserAvatarButtonComponent {} diff --git a/libs/core/src/lib/user-avatar/user-avatar.component.html b/libs/core/src/lib/user-avatar/user-avatar.component.html new file mode 100644 index 0000000..e69de29 diff --git a/libs/core/src/lib/user-avatar/user-avatar.component.scss b/libs/core/src/lib/user-avatar/user-avatar.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/libs/core/src/lib/user-avatar/user-avatar.component.spec.ts b/libs/core/src/lib/user-avatar/user-avatar.component.spec.ts new file mode 100644 index 0000000..d30a3b2 --- /dev/null +++ b/libs/core/src/lib/user-avatar/user-avatar.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { UserAvatarComponent } from './user-avatar.component'; + +describe('UserAvatarComponent', () => { + let component: UserAvatarComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UserAvatarComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(UserAvatarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/libs/core/src/lib/user-avatar/user-avatar.component.ts b/libs/core/src/lib/user-avatar/user-avatar.component.ts new file mode 100644 index 0000000..e3b2b69 --- /dev/null +++ b/libs/core/src/lib/user-avatar/user-avatar.component.ts @@ -0,0 +1,12 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'supabase-user-avatar', + standalone: true, + imports: [CommonModule], + templateUrl: './user-avatar.component.html', + styleUrl: './user-avatar.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class UserAvatarComponent {} diff --git a/libs/material/package.json b/libs/material/package.json index 4f01e39..447315b 100644 --- a/libs/material/package.json +++ b/libs/material/package.json @@ -1,6 +1,6 @@ { "name": "@ng-supabase/material", - "version": "1.0.0-rc.12", + "version": "1.0.0-rc.13", "author": "Rusty Green ", "contributors": [ "Rusty Green " diff --git a/libs/primeng/package.json b/libs/primeng/package.json index 8123a2e..c06927c 100644 --- a/libs/primeng/package.json +++ b/libs/primeng/package.json @@ -1,6 +1,6 @@ { "name": "@ng-supabase/primeng", - "version": "1.0.0-rc.12", + "version": "1.0.0-rc.13", "author": "Rusty Green ", "contributors": [ "Rusty Green " diff --git a/libs/primeng/src/index.ts b/libs/primeng/src/index.ts index aa5759c..dde4547 100644 --- a/libs/primeng/src/index.ts +++ b/libs/primeng/src/index.ts @@ -10,4 +10,10 @@ export * from './lib/register-or-sign-in/register-or-sign-in.component'; export * from './lib/register/register.component'; +export * from './lib/user-avatar/user-avatar.component'; + +export * from './lib/user-avatar-button/user-avatar-button.component'; + +export * from './lib/active-user-avatar-button/active-user-avatar-button.component'; + export * from './lib/provide-supabase'; diff --git a/libs/primeng/src/lib/active-user-avatar-button/active-user-avatar-button.component.html b/libs/primeng/src/lib/active-user-avatar-button/active-user-avatar-button.component.html new file mode 100644 index 0000000..9ffcf31 --- /dev/null +++ b/libs/primeng/src/lib/active-user-avatar-button/active-user-avatar-button.component.html @@ -0,0 +1,21 @@ +@if(loading() || (supabase.signedIn | async)){ + +} @else { + + +} diff --git a/libs/primeng/src/lib/active-user-avatar-button/active-user-avatar-button.component.scss b/libs/primeng/src/lib/active-user-avatar-button/active-user-avatar-button.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/libs/primeng/src/lib/active-user-avatar-button/active-user-avatar-button.component.spec.ts b/libs/primeng/src/lib/active-user-avatar-button/active-user-avatar-button.component.spec.ts new file mode 100644 index 0000000..e9b24bb --- /dev/null +++ b/libs/primeng/src/lib/active-user-avatar-button/active-user-avatar-button.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActiveUserAvatarButtonComponent } from './active-user-avatar-button.component'; + +describe('ActiveUserAvatarButtonComponent', () => { + let component: ActiveUserAvatarButtonComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ActiveUserAvatarButtonComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ActiveUserAvatarButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/libs/primeng/src/lib/active-user-avatar-button/active-user-avatar-button.component.ts b/libs/primeng/src/lib/active-user-avatar-button/active-user-avatar-button.component.ts new file mode 100644 index 0000000..319949f --- /dev/null +++ b/libs/primeng/src/lib/active-user-avatar-button/active-user-avatar-button.component.ts @@ -0,0 +1,39 @@ +// Angular. +import { RouterLink } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; + +// 3rd party. +import { MenuItem } from 'primeng/api'; +import { ButtonModule } from 'primeng/button'; + +// ng-supabase. +import { UserAvatarButtonComponent } from '../user-avatar-button/user-avatar-button.component'; +import { + InitialsPipe, + ActiveUserAvatarButtonComponent as CoreActiveUserAvatarButtonComponent, +} from '@ng-supabase/core'; + +@Component({ + selector: 'supabase-active-user-avatar-button', + standalone: true, + imports: [ + RouterLink, + CommonModule, + ButtonModule, + InitialsPipe, + UserAvatarButtonComponent, + ], + templateUrl: './active-user-avatar-button.component.html', + styleUrl: './active-user-avatar-button.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ActiveUserAvatarButtonComponent extends CoreActiveUserAvatarButtonComponent { + @Input() items: MenuItem[] = [ + { + label: 'Sign out', + icon: 'pi pi-power-off', + command: () => this.signOut(), + }, + ]; +} diff --git a/libs/primeng/src/lib/user-avatar-button/user-avatar-button.component.html b/libs/primeng/src/lib/user-avatar-button/user-avatar-button.component.html new file mode 100644 index 0000000..371e925 --- /dev/null +++ b/libs/primeng/src/lib/user-avatar-button/user-avatar-button.component.html @@ -0,0 +1,14 @@ + + + + @if(!loading){ + + } + + + diff --git a/libs/primeng/src/lib/user-avatar-button/user-avatar-button.component.scss b/libs/primeng/src/lib/user-avatar-button/user-avatar-button.component.scss new file mode 100644 index 0000000..ef1581d --- /dev/null +++ b/libs/primeng/src/lib/user-avatar-button/user-avatar-button.component.scss @@ -0,0 +1,17 @@ +a { + display: flex; + flex-direction: row; + align-items: center; + text-decoration: none; + color: inherit; +} + +.title { + color: rgb(73, 80, 87); + font-weight: 700; +} + +.subtitle { + color: rgb(108, 117, 125); + font-weight: 400; +} diff --git a/libs/primeng/src/lib/user-avatar-button/user-avatar-button.component.spec.ts b/libs/primeng/src/lib/user-avatar-button/user-avatar-button.component.spec.ts new file mode 100644 index 0000000..ad0293f --- /dev/null +++ b/libs/primeng/src/lib/user-avatar-button/user-avatar-button.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { UserAvatarButtonComponent } from './user-avatar-button.component'; + +describe('UserAvatarButtonComponent', () => { + let component: UserAvatarButtonComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UserAvatarButtonComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(UserAvatarButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/libs/primeng/src/lib/user-avatar-button/user-avatar-button.component.ts b/libs/primeng/src/lib/user-avatar-button/user-avatar-button.component.ts new file mode 100644 index 0000000..a15bd32 --- /dev/null +++ b/libs/primeng/src/lib/user-avatar-button/user-avatar-button.component.ts @@ -0,0 +1,29 @@ +// Angular. +import { CommonModule } from '@angular/common'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; + +// 3rd party. +import { MenuItem } from 'primeng/api'; +import { MenuModule } from 'primeng/menu'; + +// ng-supabase. +import { UserAvatarButtonComponent as CoreUserAvatarButtonComponent } from '@ng-supabase/core'; + +// Local. +import { UserAvatarComponent } from '../user-avatar/user-avatar.component'; + +@Component({ + selector: 'supabase-user-avatar-button', + standalone: true, + imports: [CommonModule, MenuModule, UserAvatarComponent], + templateUrl: './user-avatar-button.component.html', + styleUrl: './user-avatar-button.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class UserAvatarButtonComponent extends CoreUserAvatarButtonComponent { + @Input() items: MenuItem[] = []; + @Input() initials = ''; + @Input() title = ''; + @Input() subtitle = ''; + @Input() loading: boolean | null | undefined = false; +} diff --git a/libs/primeng/src/lib/user-avatar/user-avatar.component.html b/libs/primeng/src/lib/user-avatar/user-avatar.component.html new file mode 100644 index 0000000..c48eebb --- /dev/null +++ b/libs/primeng/src/lib/user-avatar/user-avatar.component.html @@ -0,0 +1,23 @@ + + + + + @if(loading){ + + } @else{ + {{ title }} + } + + + @if(loading){ + + } @else{ + {{ subtitle }} + } + + diff --git a/libs/primeng/src/lib/user-avatar/user-avatar.component.scss b/libs/primeng/src/lib/user-avatar/user-avatar.component.scss new file mode 100644 index 0000000..55052f3 --- /dev/null +++ b/libs/primeng/src/lib/user-avatar/user-avatar.component.scss @@ -0,0 +1,17 @@ +:host { + display: flex; + flex-direction: row; + align-items: center; + text-decoration: none; + color: inherit; +} + +.title { + color: rgb(73, 80, 87); + font-weight: 700; +} + +.subtitle { + color: rgb(108, 117, 125); + font-weight: 400; +} diff --git a/libs/primeng/src/lib/user-avatar/user-avatar.component.spec.ts b/libs/primeng/src/lib/user-avatar/user-avatar.component.spec.ts new file mode 100644 index 0000000..d30a3b2 --- /dev/null +++ b/libs/primeng/src/lib/user-avatar/user-avatar.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { UserAvatarComponent } from './user-avatar.component'; + +describe('UserAvatarComponent', () => { + let component: UserAvatarComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UserAvatarComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(UserAvatarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/libs/primeng/src/lib/user-avatar/user-avatar.component.ts b/libs/primeng/src/lib/user-avatar/user-avatar.component.ts new file mode 100644 index 0000000..5a719f5 --- /dev/null +++ b/libs/primeng/src/lib/user-avatar/user-avatar.component.ts @@ -0,0 +1,27 @@ +// Angular. +import { CommonModule } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + Input, + signal, +} from '@angular/core'; + +// 3rd party. +import { AvatarModule } from 'primeng/avatar'; +import { SkeletonModule } from 'primeng/skeleton'; + +@Component({ + selector: 'supabase-user-avatar', + standalone: true, + imports: [CommonModule, AvatarModule, SkeletonModule], + templateUrl: './user-avatar.component.html', + styleUrl: './user-avatar.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class UserAvatarComponent { + @Input() initials = ''; + @Input() title = ''; + @Input() subtitle = ''; + @Input() loading: boolean | null | undefined = false; +}