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;
+}