浏览代码

refactor(web): user avatar (#2585)

* refactor(web): user avatar

* change user settings link

* update package lock json

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
Michel Heusschen 2 年之前
父节点
当前提交
e7ad622c02

+ 0 - 11
web/package-lock.json

@@ -11,7 +11,6 @@
 				"axios": "^0.27.2",
 				"axios": "^0.27.2",
 				"copy-image-clipboard": "^2.1.2",
 				"copy-image-clipboard": "^2.1.2",
 				"handlebars": "^4.7.7",
 				"handlebars": "^4.7.7",
-				"justified-layout": "^4.1.0",
 				"leaflet": "^1.9.3",
 				"leaflet": "^1.9.3",
 				"leaflet.markercluster": "^1.5.3",
 				"leaflet.markercluster": "^1.5.3",
 				"lodash-es": "^4.17.21",
 				"lodash-es": "^4.17.21",
@@ -9052,11 +9051,6 @@
 				"node": ">=6"
 				"node": ">=6"
 			}
 			}
 		},
 		},
-		"node_modules/justified-layout": {
-			"version": "4.1.0",
-			"resolved": "https://registry.npmjs.org/justified-layout/-/justified-layout-4.1.0.tgz",
-			"integrity": "sha512-M5FimNMXgiOYerVRGsXZ2YK9YNCaTtwtYp7Hb2308U1Q9TXXHx5G0p08mcVR5O53qf8bWY4NJcPBxE6zuayXSg=="
-		},
 		"node_modules/kind-of": {
 		"node_modules/kind-of": {
 			"version": "6.0.3",
 			"version": "6.0.3",
 			"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
 			"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -18154,11 +18148,6 @@
 			"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
 			"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
 			"dev": true
 			"dev": true
 		},
 		},
-		"justified-layout": {
-			"version": "4.1.0",
-			"resolved": "https://registry.npmjs.org/justified-layout/-/justified-layout-4.1.0.tgz",
-			"integrity": "sha512-M5FimNMXgiOYerVRGsXZ2YK9YNCaTtwtYp7Hb2308U1Q9TXXHx5G0p08mcVR5O53qf8bWY4NJcPBxE6zuayXSg=="
-		},
 		"kind-of": {
 		"kind-of": {
 			"version": "6.0.3",
 			"version": "6.0.3",
 			"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
 			"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",

+ 6 - 8
web/src/lib/components/album-page/album-viewer.svelte

@@ -27,7 +27,7 @@
 	import DownloadAction from '../photos-page/actions/download-action.svelte';
 	import DownloadAction from '../photos-page/actions/download-action.svelte';
 	import RemoveFromAlbum from '../photos-page/actions/remove-from-album.svelte';
 	import RemoveFromAlbum from '../photos-page/actions/remove-from-album.svelte';
 	import AssetSelectControlBar from '../photos-page/asset-select-control-bar.svelte';
 	import AssetSelectControlBar from '../photos-page/asset-select-control-bar.svelte';
-	import CircleAvatar from '../shared-components/circle-avatar.svelte';
+	import UserAvatar from '../shared-components/user-avatar.svelte';
 	import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
 	import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
 	import MenuOption from '../shared-components/context-menu/menu-option.svelte';
 	import MenuOption from '../shared-components/context-menu/menu-option.svelte';
 	import ControlAppBar from '../shared-components/control-app-bar.svelte';
 	import ControlAppBar from '../shared-components/control-app-bar.svelte';
@@ -478,13 +478,11 @@
 			</span>
 			</span>
 		{/if}
 		{/if}
 		{#if album.shared}
 		{#if album.shared}
-			<div class="my-6 flex">
-				{#each album.sharedUsers as user}
-					{#key user.id}
-						<span class="mr-1">
-							<CircleAvatar {user} on:click={() => (isShowShareInfoModal = true)} />
-						</span>
-					{/key}
+			<div class="flex my-6 gap-x-1">
+				{#each album.sharedUsers as user (user.id)}
+					<button on:click={() => (isShowShareInfoModal = true)}>
+						<UserAvatar {user} size="md" autoColor />
+					</button>
 				{/each}
 				{/each}
 
 
 				<button
 				<button

+ 2 - 2
web/src/lib/components/album-page/share-info-modal.svelte

@@ -3,7 +3,7 @@
 	import { AlbumResponseDto, api, UserResponseDto } from '@api';
 	import { AlbumResponseDto, api, UserResponseDto } from '@api';
 	import { clickOutside } from '$lib/utils/click-outside';
 	import { clickOutside } from '$lib/utils/click-outside';
 	import BaseModal from '../shared-components/base-modal.svelte';
 	import BaseModal from '../shared-components/base-modal.svelte';
-	import CircleAvatar from '../shared-components/circle-avatar.svelte';
+	import UserAvatar from '../shared-components/user-avatar.svelte';
 	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
 	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
 	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
 	import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
 	import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
 	import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
@@ -79,7 +79,7 @@
 				class="flex gap-4 p-5 place-items-center justify-between w-full transition-colors hover:bg-gray-50 dark:hover:bg-gray-700"
 				class="flex gap-4 p-5 place-items-center justify-between w-full transition-colors hover:bg-gray-50 dark:hover:bg-gray-700"
 			>
 			>
 				<div class="flex gap-4 place-items-center">
 				<div class="flex gap-4 place-items-center">
-					<CircleAvatar {user} />
+					<UserAvatar {user} size="md" autoColor />
 					<p class="font-medium text-sm">{user.firstName} {user.lastName}</p>
 					<p class="font-medium text-sm">{user.firstName} {user.lastName}</p>
 				</div>
 				</div>
 
 

+ 3 - 3
web/src/lib/components/album-page/user-selection-modal.svelte

@@ -2,7 +2,7 @@
 	import { createEventDispatcher, onMount } from 'svelte';
 	import { createEventDispatcher, onMount } from 'svelte';
 	import { AlbumResponseDto, api, SharedLinkResponseDto, UserResponseDto } from '@api';
 	import { AlbumResponseDto, api, SharedLinkResponseDto, UserResponseDto } from '@api';
 	import BaseModal from '../shared-components/base-modal.svelte';
 	import BaseModal from '../shared-components/base-modal.svelte';
-	import CircleAvatar from '../shared-components/circle-avatar.svelte';
+	import UserAvatar from '../shared-components/user-avatar.svelte';
 	import Link from 'svelte-material-icons/Link.svelte';
 	import Link from 'svelte-material-icons/Link.svelte';
 	import ShareCircle from 'svelte-material-icons/ShareCircle.svelte';
 	import ShareCircle from 'svelte-material-icons/ShareCircle.svelte';
 	import { goto } from '$app/navigation';
 	import { goto } from '$app/navigation';
@@ -72,7 +72,7 @@
 							on:click={() => deselectUser(user)}
 							on:click={() => deselectUser(user)}
 							class="flex gap-1 place-items-center border border-gray-400 rounded-full p-1 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
 							class="flex gap-1 place-items-center border border-gray-400 rounded-full p-1 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
 						>
 						>
-							<CircleAvatar size={28} {user} />
+							<UserAvatar {user} size="sm" autoColor />
 							<p class="text-xs font-medium">{user.firstName} {user.lastName}</p>
 							<p class="text-xs font-medium">{user.firstName} {user.lastName}</p>
 						</button>
 						</button>
 					{/key}
 					{/key}
@@ -95,7 +95,7 @@
 								>✓</span
 								>✓</span
 							>
 							>
 						{:else}
 						{:else}
-							<CircleAvatar {user} />
+							<UserAvatar {user} size="md" autoColor />
 						{/if}
 						{/if}
 
 
 						<div class="text-left">
 						<div class="text-left">

+ 0 - 66
web/src/lib/components/shared-components/circle-avatar.svelte

@@ -1,66 +0,0 @@
-<script lang="ts">
-	import { api, UserResponseDto } from '@api';
-	import { createEventDispatcher } from 'svelte';
-
-	export let user: UserResponseDto;
-
-	// Avatar Size In Pixel
-	export let size = 48;
-
-	const dispatch = createEventDispatcher();
-
-	const getUserAvatar = async () => {
-		const { data } = await api.userApi.getProfileImage(
-			{ userId: user.id },
-			{
-				responseType: 'blob'
-			}
-		);
-
-		if (data instanceof Blob) {
-			return URL.createObjectURL(data);
-		}
-	};
-
-	const getFirstLetter = (text?: string) => {
-		return text?.charAt(0).toUpperCase();
-	};
-
-	const getRandomeBackgroundColor = () => {
-		const colors = ['#DE7FB3', '#E64132', '#FFB800', '#4081EF', '#31A452'];
-		return colors[Math.floor(Math.random() * colors.length)];
-	};
-</script>
-
-{#await getUserAvatar()}
-	<button
-		on:click={() => dispatch('click')}
-		style:width={`${size}px`}
-		style:height={`${size}px`}
-		class={` rounded-full bg-immich-primary/25`}
-	/>
-{:then data}
-	<button on:click={() => dispatch('click')}>
-		<img
-			src={data}
-			alt="profile-img"
-			style:width={`${size}px`}
-			style:height={`${size}px`}
-			class={`inline rounded-full  object-cover border shadow-md`}
-			title={user.email}
-			draggable="false"
-		/>
-	</button>
-{:catch}
-	<button
-		on:click={() => dispatch('click')}
-		style:width={`${size}px`}
-		style:height={`${size}px`}
-		style:background-color={getRandomeBackgroundColor()}
-		class="inline rounded-full object-cover shadow-sm text-white font-semibold"
-	>
-		<div title={user.email}>
-			{getFirstLetter(user.firstName)}{getFirstLetter(user.lastName)}
-		</div>
-	</button>
-{/await}

+ 18 - 51
web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte

@@ -1,78 +1,45 @@
 <script lang="ts">
 <script lang="ts">
-	import { UserResponseDto, api } from '@api';
+	import { UserResponseDto } from '@api';
 	import { createEventDispatcher } from 'svelte';
 	import { createEventDispatcher } from 'svelte';
 	import { fade } from 'svelte/transition';
 	import { fade } from 'svelte/transition';
 	import Cog from 'svelte-material-icons/Cog.svelte';
 	import Cog from 'svelte-material-icons/Cog.svelte';
 	import Logout from 'svelte-material-icons/Logout.svelte';
 	import Logout from 'svelte-material-icons/Logout.svelte';
-	import { goto } from '$app/navigation';
 	import Button from '$lib/components/elements/buttons/button.svelte';
 	import Button from '$lib/components/elements/buttons/button.svelte';
+	import UserAvatar from '../user-avatar.svelte';
+	import { AppRoute } from '$lib/constants';
 
 
 	export let user: UserResponseDto;
 	export let user: UserResponseDto;
 
 
-	// Show fallback while loading profile picture and hide when image loads.
-	let showProfilePictureFallback = true;
-
 	const dispatch = createEventDispatcher();
 	const dispatch = createEventDispatcher();
-
-	const getFirstLetter = (text?: string) => {
-		return text?.charAt(0).toUpperCase();
-	};
 </script>
 </script>
 
 
 <div
 <div
 	in:fade={{ duration: 100 }}
 	in:fade={{ duration: 100 }}
 	out:fade={{ duration: 100 }}
 	out:fade={{ duration: 100 }}
 	id="account-info-panel"
 	id="account-info-panel"
-	class="absolute right-[25px] top-[75px] bg-gray-200 dark:bg-immich-dark-gray dark:border dark:border-immich-dark-gray shadow-lg rounded-3xl w-[360px] text-center z-[100]"
+	class="absolute right-[25px] top-[75px] bg-gray-200 dark:bg-immich-dark-gray dark:border dark:border-immich-dark-gray shadow-lg rounded-3xl w-[360px] z-[100]"
 >
 >
-	<div class="bg-white dark:bg-immich-dark-primary/10 rounded-3xl mx-4 mt-4 pb-4">
-		<div class="flex place-items-center place-content-center">
-			<div
-				class="flex place-items-center place-content-center rounded-full bg-immich-primary dark:bg-immich-dark-primary dark:immich-dark-primary/80 h-20 w-20 text-gray-100 hover:bg-immich-primary dark:text-immich-dark-bg mt-4 select-none"
-			>
-				{#if user.profileImagePath}
-					<img
-						transition:fade={{ duration: 100 }}
-						class:hidden={showProfilePictureFallback}
-						src={api.getProfileImageUrl(user.id)}
-						alt="profile-img"
-						class="inline rounded-full h-20 w-20 object-cover shadow-md border-2 border-immich-primary dark:border-immich-dark-primary"
-						draggable="false"
-						on:load={() => (showProfilePictureFallback = false)}
-					/>
-				{/if}
-				{#if showProfilePictureFallback}
-					<div transition:fade={{ duration: 200 }} class="text-lg">
-						{getFirstLetter(user.firstName)}{getFirstLetter(user.lastName)}
-					</div>
-				{/if}
-			</div>
+	<div
+		class="flex flex-col items-center justify-center gap-4 bg-white dark:bg-immich-dark-primary/10 rounded-3xl mx-4 mt-4 p-4"
+	>
+		<UserAvatar size="lg" {user} />
+
+		<div>
+			<p class="text-lg text-immich-primary dark:text-immich-dark-primary font-medium">
+				{user.firstName}
+				{user.lastName}
+			</p>
+			<p class="text-sm text-gray-500 dark:text-immich-dark-fg">{user.email}</p>
 		</div>
 		</div>
 
 
-		<p class="text-lg text-immich-primary dark:text-immich-dark-primary font-medium mt-4">
-			{user.firstName}
-			{user.lastName}
-		</p>
-
-		<p class="text-sm text-gray-500 dark:text-immich-dark-fg">{user.email}</p>
-
-		<div class="mt-4">
-			<Button
-				color="dark-gray"
-				size="sm"
-				shadow={false}
-				border
-				on:click={() => {
-					goto('/user-settings');
-					dispatch('close');
-				}}
-			>
+		<a href={AppRoute.USER_SETTINGS} on:click={() => dispatch('close')}>
+			<Button color="dark-gray" size="sm" shadow={false} border>
 				<div class="flex gap-2 place-items-center place-content-center px-2">
 				<div class="flex gap-2 place-items-center place-content-center px-2">
 					<Cog size="18" />
 					<Cog size="18" />
 					Account Settings
 					Account Settings
 				</div>
 				</div>
 			</Button>
 			</Button>
-		</div>
+		</a>
 	</div>
 	</div>
 
 
 	<div class="mb-4 flex flex-col">
 	<div class="mb-4 flex flex-col">

+ 3 - 23
web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte

@@ -2,7 +2,6 @@
 	import { goto } from '$app/navigation';
 	import { goto } from '$app/navigation';
 	import { page } from '$app/stores';
 	import { page } from '$app/stores';
 	import { clickOutside } from '$lib/utils/click-outside';
 	import { clickOutside } from '$lib/utils/click-outside';
-	import { imageLoad } from '$lib/utils/image-load';
 	import { createEventDispatcher } from 'svelte';
 	import { createEventDispatcher } from 'svelte';
 	import { fade, fly } from 'svelte/transition';
 	import { fade, fly } from 'svelte/transition';
 	import TrayArrowUp from 'svelte-material-icons/TrayArrowUp.svelte';
 	import TrayArrowUp from 'svelte-material-icons/TrayArrowUp.svelte';
@@ -16,21 +15,15 @@
 	import Magnify from 'svelte-material-icons/Magnify.svelte';
 	import Magnify from 'svelte-material-icons/Magnify.svelte';
 	import IconButton from '$lib/components/elements/buttons/icon-button.svelte';
 	import IconButton from '$lib/components/elements/buttons/icon-button.svelte';
 	import Cog from 'svelte-material-icons/Cog.svelte';
 	import Cog from 'svelte-material-icons/Cog.svelte';
+	import UserAvatar from '../user-avatar.svelte';
 	export let user: UserResponseDto;
 	export let user: UserResponseDto;
 	export let showUploadButton = true;
 	export let showUploadButton = true;
 
 
 	let shouldShowAccountInfo = false;
 	let shouldShowAccountInfo = false;
 	let shouldShowAccountInfoPanel = false;
 	let shouldShowAccountInfoPanel = false;
 
 
-	// Show fallback while loading profile picture and hide when image loads.
-	let showProfilePictureFallback = true;
-
 	const dispatch = createEventDispatcher();
 	const dispatch = createEventDispatcher();
 
 
-	const getFirstLetter = (text?: string) => {
-		return text?.charAt(0).toUpperCase();
-	};
-
 	const logOut = async () => {
 	const logOut = async () => {
 		const { data } = await api.authenticationApi.logout();
 		const { data } = await api.authenticationApi.logout();
 
 
@@ -116,27 +109,14 @@
 
 
 				<div use:clickOutside on:outclick={() => (shouldShowAccountInfoPanel = false)}>
 				<div use:clickOutside on:outclick={() => (shouldShowAccountInfoPanel = false)}>
 					<button
 					<button
-						class="flex place-items-center place-content-center rounded-full bg-immich-primary hover:bg-immich-primary/80 h-12 w-12 text-gray-100 dark:text-immich-dark-bg dark:bg-immich-dark-primary"
+						class="flex"
 						on:mouseover={() => (shouldShowAccountInfo = true)}
 						on:mouseover={() => (shouldShowAccountInfo = true)}
 						on:focus={() => (shouldShowAccountInfo = true)}
 						on:focus={() => (shouldShowAccountInfo = true)}
 						on:blur={() => (shouldShowAccountInfo = false)}
 						on:blur={() => (shouldShowAccountInfo = false)}
 						on:mouseleave={() => (shouldShowAccountInfo = false)}
 						on:mouseleave={() => (shouldShowAccountInfo = false)}
 						on:click={() => (shouldShowAccountInfoPanel = !shouldShowAccountInfoPanel)}
 						on:click={() => (shouldShowAccountInfoPanel = !shouldShowAccountInfoPanel)}
 					>
 					>
-						{#if user.profileImagePath}
-							<img
-								class:hidden={showProfilePictureFallback}
-								src={api.getProfileImageUrl(user.id)}
-								alt="profile-img"
-								class="inline rounded-full h-12 w-12 object-cover shadow-md border-2 border-immich-primary hover:border-immich-dark-primary dark:hover:border-immich-primary dark:border-immich-dark-primary transition-all"
-								draggable="false"
-								use:imageLoad
-								on:image-load={() => (showProfilePictureFallback = false)}
-							/>
-						{/if}
-						{#if showProfilePictureFallback}
-							{getFirstLetter(user.firstName)}{getFirstLetter(user.lastName)}
-						{/if}
+						<UserAvatar {user} size="md" showTitle={false} interactive />
 					</button>
 					</button>
 
 
 					{#if shouldShowAccountInfo && !shouldShowAccountInfoPanel}
 					{#if shouldShowAccountInfo && !shouldShowAccountInfoPanel}

+ 79 - 0
web/src/lib/components/shared-components/user-avatar.svelte

@@ -0,0 +1,79 @@
+<script lang="ts" context="module">
+	export type Color = 'primary' | 'pink' | 'red' | 'yellow' | 'blue' | 'green';
+	export type Size = 'full' | 'sm' | 'md' | 'lg';
+</script>
+
+<script lang="ts">
+	import { imageLoad } from '$lib/utils/image-load';
+	import { api, UserResponseDto } from '@api';
+
+	export let user: UserResponseDto;
+	export let color: Color = 'primary';
+	export let size: Size = 'full';
+	export let rounded = true;
+	export let interactive = false;
+	export let showTitle = true;
+	export let autoColor = false;
+	let showFallback = true;
+
+	const colorClasses: Record<Color, string> = {
+		primary:
+			'bg-immich-primary dark:bg-immich-dark-primary text-immich-dark-fg dark:text-immich-fg',
+		pink: 'bg-pink-400 text-immich-bg',
+		red: 'bg-red-500 text-immich-bg',
+		yellow: 'bg-yellow-500 text-immich-bg',
+		blue: 'bg-blue-500 text-immich-bg',
+		green: 'bg-green-600 text-immich-bg'
+	};
+
+	const sizeClasses: Record<Size, string> = {
+		full: 'w-full h-full',
+		sm: 'w-7 h-7',
+		md: 'w-12 h-12',
+		lg: 'w-20 h-20'
+	};
+
+	// Get color based on the user UUID.
+	function getUserColor() {
+		const seed = parseInt(user.id.split('-')[0], 16);
+		const colors = Object.keys(colorClasses).filter((color) => color !== 'primary') as Color[];
+		const randomIndex = seed % colors.length;
+		return colors[randomIndex];
+	}
+
+	$: colorClass = colorClasses[autoColor ? getUserColor() : color];
+	$: sizeClass = sizeClasses[size];
+	$: title = `${user.firstName} ${user.lastName} (${user.email})`;
+	$: interactiveClass = interactive
+		? 'border-2 border-immich-primary hover:border-immich-dark-primary dark:hover:border-immich-primary dark:border-immich-dark-primary transition-colors'
+		: '';
+</script>
+
+<figure
+	class="{sizeClass} {colorClass} {interactiveClass} shadow-md overflow-hidden"
+	class:rounded-full={rounded}
+	title={showTitle ? title : undefined}
+>
+	{#if user.profileImagePath}
+		<img
+			src={api.getProfileImageUrl(user.id)}
+			alt="Profile image of {title}"
+			class="object-cover w-full h-full"
+			class:hidden={showFallback}
+			draggable="false"
+			use:imageLoad
+			on:image-load={() => (showFallback = false)}
+		/>
+	{/if}
+	{#if showFallback}
+		<span
+			class="flex justify-center items-center w-full h-full select-none"
+			class:text-xs={size === 'sm'}
+			class:text-lg={size === 'lg'}
+			class:font-medium={!autoColor}
+			class:font-semibold={autoColor}
+		>
+			{(user.firstName[0] + user.lastName[0]).toUpperCase()}
+		</span>
+	{/if}
+</figure>

+ 2 - 2
web/src/lib/components/user-settings-page/partner-selection-modal.svelte

@@ -1,7 +1,7 @@
 <script lang="ts">
 <script lang="ts">
 	import { api, UserResponseDto } from '@api';
 	import { api, UserResponseDto } from '@api';
 	import BaseModal from '../shared-components/base-modal.svelte';
 	import BaseModal from '../shared-components/base-modal.svelte';
-	import CircleAvatar from '../shared-components/circle-avatar.svelte';
+	import UserAvatar from '../shared-components/user-avatar.svelte';
 	import ImmichLogo from '../shared-components/immich-logo.svelte';
 	import ImmichLogo from '../shared-components/immich-logo.svelte';
 	import Button from '../elements/buttons/button.svelte';
 	import Button from '../elements/buttons/button.svelte';
 	import { createEventDispatcher, onMount } from 'svelte';
 	import { createEventDispatcher, onMount } from 'svelte';
@@ -56,7 +56,7 @@
 							>✓</span
 							>✓</span
 						>
 						>
 					{:else}
 					{:else}
-						<CircleAvatar {user} />
+						<UserAvatar {user} size="md" autoColor />
 					{/if}
 					{/if}
 
 
 					<div class="text-left">
 					<div class="text-left">

+ 3 - 4
web/src/lib/components/user-settings-page/partner-settings.svelte

@@ -1,6 +1,6 @@
 <script lang="ts">
 <script lang="ts">
 	import { UserResponseDto, api } from '@api';
 	import { UserResponseDto, api } from '@api';
-	import CircleAvatar from '../shared-components/circle-avatar.svelte';
+	import UserAvatar from '../shared-components/user-avatar.svelte';
 	import Close from 'svelte-material-icons/Close.svelte';
 	import Close from 'svelte-material-icons/Close.svelte';
 	import Button from '../elements/buttons/button.svelte';
 	import Button from '../elements/buttons/button.svelte';
 	import PartnerSelectionModal from './partner-selection-modal.svelte';
 	import PartnerSelectionModal from './partner-selection-modal.svelte';
@@ -55,10 +55,9 @@
 <section class="my-4">
 <section class="my-4">
 	{#if partners.length > 0}
 	{#if partners.length > 0}
 		<div class="flex flex-row gap-4">
 		<div class="flex flex-row gap-4">
-			{#each partners as partner}
+			{#each partners as partner (partner.id)}
 				<div class="flex rounded-lg gap-4 py-4 px-5 transition-all">
 				<div class="flex rounded-lg gap-4 py-4 px-5 transition-all">
-					<CircleAvatar user={partner} />
-
+					<UserAvatar user={partner} size="md" autoColor />
 					<div class="text-left">
 					<div class="text-left">
 						<p class="text-immich-fg dark:text-immich-dark-fg">
 						<p class="text-immich-fg dark:text-immich-dark-fg">
 							{partner.firstName}
 							{partner.firstName}

+ 1 - 0
web/src/lib/constants.ts

@@ -17,6 +17,7 @@ export enum AppRoute {
 	SHARED_LINKS = '/sharing/sharedlinks',
 	SHARED_LINKS = '/sharing/sharedlinks',
 	SEARCH = '/search',
 	SEARCH = '/search',
 	MAP = '/map',
 	MAP = '/map',
+	USER_SETTINGS = '/user-settings',
 
 
 	AUTH_LOGIN = '/auth/login',
 	AUTH_LOGIN = '/auth/login',
 	AUTH_LOGOUT = '/auth/logout',
 	AUTH_LOGOUT = '/auth/logout',

+ 6 - 7
web/src/routes/(user)/sharing/+page.svelte

@@ -13,7 +13,7 @@
 	import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
 	import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
 	import { flip } from 'svelte/animate';
 	import { flip } from 'svelte/animate';
 	import AlbumCard from '$lib/components/album-page/album-card.svelte';
 	import AlbumCard from '$lib/components/album-page/album-card.svelte';
-	import CircleAvatar from '$lib/components/shared-components/circle-avatar.svelte';
+	import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
 	import { AppRoute } from '$lib/constants';
 	import { AppRoute } from '$lib/constants';
 
 
 	export let data: PageData;
 	export let data: PageData;
@@ -63,13 +63,12 @@
 				</div>
 				</div>
 
 
 				<div class="flex flex-row flex-wrap gap-4">
 				<div class="flex flex-row flex-wrap gap-4">
-					{#each data.partners as partner}
-						<button
-							on:click={() => goto(`/partners/${partner.id}`)}
+					{#each data.partners as partner (partner.id)}
+						<a
+							href="/partners/{partner.id}"
 							class="flex rounded-lg gap-4 py-4 px-5 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all"
 							class="flex rounded-lg gap-4 py-4 px-5 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all"
 						>
 						>
-							<CircleAvatar user={partner} />
-
+							<UserAvatar user={partner} size="md" autoColor />
 							<div class="text-left">
 							<div class="text-left">
 								<p class="text-immich-fg dark:text-immich-dark-fg">
 								<p class="text-immich-fg dark:text-immich-dark-fg">
 									{partner.firstName}
 									{partner.firstName}
@@ -79,7 +78,7 @@
 									{partner.email}
 									{partner.email}
 								</p>
 								</p>
 							</div>
 							</div>
-						</button>
+						</a>
 					{/each}
 					{/each}
 				</div>
 				</div>
 			</div>
 			</div>