diff --git a/server/apps/immich/src/modules/immich-jwt/strategies/jwt.strategy.ts b/server/apps/immich/src/modules/immich-jwt/strategies/jwt.strategy.ts
index 477ea297e5..a4bded2953 100644
--- a/server/apps/immich/src/modules/immich-jwt/strategies/jwt.strategy.ts
+++ b/server/apps/immich/src/modules/immich-jwt/strategies/jwt.strategy.ts
@@ -18,8 +18,8 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
   ) {
     super({
       jwtFromRequest: ExtractJwt.fromExtractors([
-        immichJwtService.extractJwtFromHeader,
         immichJwtService.extractJwtFromCookie,
+        immichJwtService.extractJwtFromHeader,
       ]),
       ignoreExpiration: false,
       secretOrKey: jwtSecret,
diff --git a/web/src/app.css b/web/src/app.css
index 8b74329938..12b80cf28f 100644
--- a/web/src/app.css
+++ b/web/src/app.css
@@ -6,26 +6,53 @@
 @tailwind utilities;
 
 :root {
-  font-family: 'Work Sans', sans-serif;
+	font-family: 'Work Sans', sans-serif;
 }
 
 body {
-	min-height: 100vh;
+	/* min-height: 100vh; */
 	margin: 0;
 	background-color: #f6f8fe;
-  color: #5f6368;
+	color: #5f6368;
 }
 
 @layer utilities {
-  .immich-form-input {
-    @apply bg-slate-100 p-2 rounded-md focus:border-immich-primary text-sm
-  }
+	.immich-form-input {
+		@apply bg-slate-100 p-2 rounded-md focus:border-immich-primary text-sm;
+	}
 
-  .immich-form-label {
-    @apply font-medium text-sm text-gray-500
-  }
+	.immich-form-label {
+		@apply font-medium text-sm text-gray-500;
+	}
 
-  .immich-btn-primary {
-    @apply bg-immich-primary text-gray-100 border rounded-xl py-2 px-4 transition-all duration-150 hover:bg-immich-primary hover:shadow-lg text-sm font-medium
-  }
-}
\ No newline at end of file
+	.immich-btn-primary {
+		@apply bg-immich-primary text-gray-100 border rounded-xl py-2 px-4 transition-all duration-150 hover:bg-immich-primary hover:shadow-lg text-sm font-medium;
+	}
+
+	.immich-text-button {
+		@apply flex place-items-center place-content-center gap-2 hover:bg-immich-primary/5 p-2 rounded-lg font-medium;
+	}
+
+	/* width */
+	.immich-scrollbar::-webkit-scrollbar {
+		width: 8px;
+	}
+
+	/* Track */
+	.immich-scrollbar::-webkit-scrollbar-track {
+		background: #f1f1f1;
+		border-radius: 16px;
+	}
+
+	/* Handle */
+	.immich-scrollbar::-webkit-scrollbar-thumb {
+		background: rgba(85, 86, 87, 0.408);
+		border-radius: 16px;
+	}
+
+	/* Handle on hover */
+	.immich-scrollbar::-webkit-scrollbar-thumb:hover {
+		background: #4250afad;
+		border-radius: 16px;
+	}
+}
diff --git a/web/src/lib/components/album-page/album-app-bar.svelte b/web/src/lib/components/album-page/album-app-bar.svelte
new file mode 100644
index 0000000000..ba700eb269
--- /dev/null
+++ b/web/src/lib/components/album-page/album-app-bar.svelte
@@ -0,0 +1,39 @@
+<script lang="ts">
+	import { createEventDispatcher, onMount } from 'svelte';
+	import Close from 'svelte-material-icons/Close.svelte';
+
+	export let backIcon = Close;
+	let appBarBorder = '';
+	const dispatch = createEventDispatcher();
+	onMount(() => {
+		window.onscroll = () => {
+			if (window.pageYOffset > 80) {
+				appBarBorder = 'border border-gray-200 bg-gray-50';
+			} else {
+				appBarBorder = '';
+			}
+		};
+	});
+</script>
+
+<div class="fixed top-0 w-full bg-transparent z-[100]">
+	<div
+		id="asset-selection-app-bar"
+		class={`flex justify-between ${appBarBorder} rounded-lg p-2 mx-2 mt-2  transition-all place-items-center`}
+	>
+		<div class="flex place-items-center gap-6">
+			<button
+				on:click={() => dispatch('close-button-click')}
+				id="immich-circle-icon-button"
+				class={`rounded-full p-3 flex place-items-center place-content-center text-gray-600 transition-all hover:bg-gray-200`}
+			>
+				<svelte:component this={backIcon} size="24" />
+			</button>
+			<slot name="leading" />
+		</div>
+
+		<div class="flex place-items-center gap-6 mr-4">
+			<slot name="trailing" />
+		</div>
+	</div>
+</div>
diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte
index c78dd6490b..b87c8dc3cf 100644
--- a/web/src/lib/components/album-page/album-viewer.svelte
+++ b/web/src/lib/components/album-page/album-viewer.svelte
@@ -1,18 +1,30 @@
 <script lang="ts">
-	import { afterNavigate } from '$app/navigation';
+	import { afterNavigate, goto } from '$app/navigation';
 	import { page } from '$app/stores';
-	import { AlbumResponseDto, AssetResponseDto, ThumbnailFormat } from '@api';
+	import { AlbumResponseDto, api, AssetResponseDto, ThumbnailFormat, UserResponseDto } from '@api';
 	import { createEventDispatcher, onMount } from 'svelte';
 	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
+	import Plus from 'svelte-material-icons/Plus.svelte';
 	import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte';
 	import AssetViewer from '../asset-viewer/asset-viewer.svelte';
 	import CircleAvatar from '../shared-components/circle-avatar.svelte';
 	import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte';
+	import AssetSelection from './asset-selection.svelte';
+	import _ from 'lodash-es';
+	import { assets } from '$app/paths';
+	import UserSelection from './user-selection-modal.svelte';
+	import AlbumAppBar from './album-app-bar.svelte';
+	import UserSelectionModal from './user-selection-modal.svelte';
 
 	const dispatch = createEventDispatcher();
 	export let album: AlbumResponseDto;
 
 	let isShowAssetViewer = false;
+	let isShowAssetSelection = false;
+	let isShowShareUserSelection = false;
+	let isEditingTitle = false;
+	let isCreatingSharedAlbum = false;
+
 	let selectedAsset: AssetResponseDto;
 	let currentViewAssetIndex = 0;
 
@@ -20,10 +32,20 @@
 	let thumbnailSize: number = 300;
 	let border = '';
 	let backUrl = '/albums';
+	let currentAlbumName = '';
+	let currentUser: UserResponseDto;
+	let bodyElement: HTMLElement;
 
-	afterNavigate(({ from, to }) => {
+	$: isOwned = currentUser?.id == album.ownerId;
+
+	afterNavigate(({ from }) => {
 		backUrl = from?.pathname ?? '/albums';
+
+		if (from?.pathname === '/sharing') {
+			isCreatingSharedAlbum = true;
+		}
 	});
+
 	$: {
 		if (album.assets.length < 6) {
 			thumbnailSize = Math.floor(viewWidth / album.assets.length - album.assets.length);
@@ -36,20 +58,18 @@
 		const startDate = new Date(album.assets[0].createdAt);
 		const endDate = new Date(album.assets[album.assets.length - 1].createdAt);
 
-		const startDateString = startDate.toLocaleDateString('us-EN', {
+		const timeFormatOption: Intl.DateTimeFormatOptions = {
 			month: 'short',
 			day: 'numeric',
 			year: 'numeric'
-		});
-		const endDateString = endDate.toLocaleDateString('us-EN', {
-			month: 'short',
-			day: 'numeric',
-			year: 'numeric'
-		});
+		};
+
+		const startDateString = startDate.toLocaleDateString('us-EN', timeFormatOption);
+		const endDateString = endDate.toLocaleDateString('us-EN', timeFormatOption);
 		return `${startDateString} - ${endDateString}`;
 	};
 
-	onMount(() => {
+	onMount(async () => {
 		window.onscroll = (event: Event) => {
 			if (window.pageYOffset > 80) {
 				border = 'border border-gray-200 bg-gray-50';
@@ -57,6 +77,15 @@
 				border = '';
 			}
 		};
+
+		currentAlbumName = album.albumName;
+
+		try {
+			const { data } = await api.userApi.getMyUserInfo();
+			currentUser = data;
+		} catch (e) {
+			console.log('Error [getMyUserInfo - album-viewer] ', e);
+		}
 	});
 
 	const viewAsset = (event: CustomEvent) => {
@@ -102,62 +131,152 @@
 		isShowAssetViewer = false;
 		history.pushState(null, '', `${$page.url.pathname}`);
 	};
+
+	// Update Album Name
+	$: {
+		if (!isEditingTitle && currentAlbumName != album.albumName && isOwned) {
+			api.albumApi
+				.updateAlbumInfo(album.id, {
+					ownerId: album.ownerId,
+					albumName: album.albumName
+				})
+				.then(() => {
+					currentAlbumName = album.albumName;
+				})
+				.catch((e) => {
+					console.log('Error [updateAlbumInfo] ', e);
+				});
+		}
+	}
+
+	const createAlbumHandler = async (event: CustomEvent) => {
+		const { assets }: { assets: string[] } = event.detail;
+
+		try {
+			const { data } = await api.albumApi.addAssetsToAlbum(album.id, { assetIds: assets });
+			album = data;
+
+			isShowAssetSelection = false;
+		} catch (e) {
+			console.log('Error [createAlbumHandler] ', e);
+		}
+	};
+
+	const addUserHandler = async (event: CustomEvent) => {
+		const { selectedUsers }: { selectedUsers: UserResponseDto[] } = event.detail;
+
+		try {
+			const { data } = await api.albumApi.addUsersToAlbum(album.id, {
+				sharedUserIds: Array.from(selectedUsers).map((u) => u.id)
+			});
+
+			album = data;
+
+			isShowShareUserSelection = false;
+		} catch (e) {
+			console.log('Error [createAlbumHandler] ', e);
+		}
+	};
+
+	// Prevent scrolling when modal is open
+	$: {
+		if (isShowShareUserSelection == true) {
+			document.body.style.overflow = 'hidden';
+		} else {
+			document.body.style.overflow = '';
+		}
+	}
 </script>
 
-<section class="w-screen h-screen bg-immich-bg">
-	<div class="fixed top-0 w-full bg-immich-bg z-[100]">
-		<div class={`flex justify-between rounded-lg ${border} p-2 mx-2 mt-2 transition-all`}>
-			<a sveltekit:prefetch href={backUrl} title="Go Back">
+<svelte:body bind:this={bodyElement} />
+<section class="bg-immich-bg relative">
+	<AlbumAppBar on:close-button-click={() => goto(backUrl)} backIcon={ArrowLeft}>
+		<svelte:fragment slot="trailing">
+			{#if album.assets.length > 0}
 				<button
 					id="immich-circle-icon-button"
 					class={`rounded-full p-3 flex place-items-center place-content-center text-gray-600 transition-all hover:bg-gray-200`}
-				>
-					<ArrowLeft size="24" />
-				</button>
-			</a>
-			<div class="right-button-group" title="Add Photos">
-				<button
-					id="immich-circle-icon-button"
-					class={`rounded-full p-3 flex place-items-center place-content-center text-gray-600 transition-all hover:bg-gray-200`}
-					on:click={() => dispatch('click')}
+					on:click={() => (isShowAssetSelection = true)}
 				>
 					<FileImagePlusOutline size="24" />
 				</button>
-			</div>
-		</div>
-	</div>
+			{/if}
 
-	<section class="m-6 py-[72px] px-[160px]">
-		<p class="text-6xl text-immich-primary">
-			{album.albumName}
-		</p>
+			{#if isCreatingSharedAlbum && album.sharedUsers.length == 0}
+				<button
+					disabled={album.assets.length == 0}
+					on:click={() => (isShowShareUserSelection = true)}
+					class="immich-text-button border bg-immich-primary text-gray-50 hover:bg-immich-primary/75 px-6 text-sm disabled:opacity-25 disabled:bg-gray-500 disabled:cursor-not-allowed"
+					><span class="px-2">Share</span></button
+				>
+			{/if}
+		</svelte:fragment>
+	</AlbumAppBar>
 
-		<p class="my-4 text-sm text-gray-500">{getDateRange()}</p>
+	<section class="m-auto my-[160px] w-[60%]">
+		<input
+			on:focus={() => (isEditingTitle = true)}
+			on:blur={() => (isEditingTitle = false)}
+			class={`transition-all text-6xl text-immich-primary w-[99%] border-b-2 border-transparent outline-none ${
+				isOwned ? 'hover:border-gray-400' : 'hover:border-transparent'
+			} focus:outline-none focus:border-b-2 focus:border-immich-primary bg-immich-bg`}
+			type="text"
+			bind:value={album.albumName}
+			disabled={!isOwned}
+		/>
 
-		{#if album.sharedUsers.length > 0}
-			<div class="mb-4">
+		{#if album.assets.length > 0}
+			<p class="my-4 text-sm text-gray-500">{getDateRange()}</p>
+		{/if}
+
+		{#if album.shared}
+			<div class="my-4 flex">
 				{#each album.sharedUsers as user}
 					<span class="mr-1">
 						<CircleAvatar {user} />
 					</span>
 				{/each}
+
+				<button
+					style:display={isOwned ? 'block' : 'none'}
+					on:click={() => (isShowShareUserSelection = true)}
+					title="Add more users"
+					class="h-12 w-12 border bg-white transition-colors hover:bg-gray-300 text-3xl flex place-items-center place-content-center rounded-full"
+					>+</button
+				>
 			</div>
 		{/if}
 
-		<div class="flex flex-wrap gap-1 w-full" bind:clientWidth={viewWidth}>
-			{#each album.assets as asset}
-				{#if album.assets.length < 7}
-					<ImmichThumbnail
-						{asset}
-						{thumbnailSize}
-						format={ThumbnailFormat.Jpeg}
-						on:viewAsset={viewAsset}
-					/>
-				{:else}
-					<ImmichThumbnail {asset} {thumbnailSize} on:viewAsset={viewAsset} />
-				{/if}
-			{/each}
-		</div>
+		{#if album.assets.length > 0}
+			<div class="flex flex-wrap gap-1 w-full" bind:clientWidth={viewWidth}>
+				{#each album.assets as asset}
+					{#if album.assets.length < 7}
+						<ImmichThumbnail
+							{asset}
+							{thumbnailSize}
+							format={ThumbnailFormat.Jpeg}
+							on:click={viewAsset}
+						/>
+					{:else}
+						<ImmichThumbnail {asset} {thumbnailSize} on:click={viewAsset} />
+					{/if}
+				{/each}
+			</div>
+		{:else}
+			<!-- Album is empty - Show asset selectection buttons -->
+			<section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center">
+				<div class="w-[300px]">
+					<p class="text-xs">ADD PHOTOS</p>
+					<button
+						on:click={() => (isShowAssetSelection = true)}
+						class="w-full py-8 border bg-white rounded-md mt-5 flex place-items-center gap-6 px-8 transition-all hover:bg-gray-100 hover:text-immich-primary"
+					>
+						<span><Plus color="#4250af" size="24" /> </span>
+						<span class="text-lg text-immich-fg">Select photos</span>
+					</button>
+				</div>
+			</section>
+		{/if}
 	</section>
 </section>
 
@@ -170,3 +289,19 @@
 		on:close={closeViewer}
 	/>
 {/if}
+
+{#if isShowAssetSelection}
+	<AssetSelection
+		assetsInAlbum={album.assets}
+		on:go-back={() => (isShowAssetSelection = false)}
+		on:create-album={createAlbumHandler}
+	/>
+{/if}
+
+{#if isShowShareUserSelection}
+	<UserSelectionModal
+		on:close={() => (isShowShareUserSelection = false)}
+		on:add-user={addUserHandler}
+		sharedUsersInAlbum={new Set(album.sharedUsers)}
+	/>
+{/if}
diff --git a/web/src/lib/components/album-page/asset-selection.svelte b/web/src/lib/components/album-page/asset-selection.svelte
new file mode 100644
index 0000000000..362dc6f2d1
--- /dev/null
+++ b/web/src/lib/components/album-page/asset-selection.svelte
@@ -0,0 +1,197 @@
+<script lang="ts">
+	import { createEventDispatcher, onMount } from 'svelte';
+	import { quintOut } from 'svelte/easing';
+	import { fly } from 'svelte/transition';
+	import { assetsGroupByDate, flattenAssetGroupByDate } from '$lib/stores/assets';
+	import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
+	import CircleOutline from 'svelte-material-icons/CircleOutline.svelte';
+	import moment from 'moment';
+	import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte';
+	import { AssetResponseDto } from '@api';
+	import AlbumAppBar from './album-app-bar.svelte';
+
+	const dispatch = createEventDispatcher();
+
+	export let assetsInAlbum: AssetResponseDto[];
+
+	let selectedAsset: Set<string> = new Set();
+	let selectedGroup: Set<number> = new Set();
+	let existingGroup: Set<number> = new Set();
+	let groupWithAssetsInAlbum: Record<number, Set<string>> = {};
+
+	onMount(() => scanForExistingSelectedGroup());
+
+	const selectAssetHandler = (assetId: string, groupIndex: number) => {
+		const tempSelectedAsset = new Set(selectedAsset);
+
+		if (selectedAsset.has(assetId)) {
+			tempSelectedAsset.delete(assetId);
+
+			const tempSelectedGroup = new Set(selectedGroup);
+			tempSelectedGroup.delete(groupIndex);
+			selectedGroup = tempSelectedGroup;
+		} else {
+			tempSelectedAsset.add(assetId);
+		}
+
+		selectedAsset = tempSelectedAsset;
+
+		// Check if all assets are selected in a group to toggle the group selection's icon
+		if (!selectedGroup.has(groupIndex)) {
+			const assetsInGroup = $assetsGroupByDate[groupIndex];
+			let selectedAssetsInGroupCount = 0;
+
+			assetsInGroup.forEach((asset) => {
+				if (selectedAsset.has(asset.id)) {
+					selectedAssetsInGroupCount++;
+				}
+			});
+
+			// Taking into account of assets in group that are already in album
+			if (groupWithAssetsInAlbum[groupIndex]) {
+				selectedAssetsInGroupCount += groupWithAssetsInAlbum[groupIndex].size;
+			}
+
+			// if all assets are selected in a group, add the group to selected group
+			if (selectedAssetsInGroupCount == assetsInGroup.length) {
+				selectedGroup = selectedGroup.add(groupIndex);
+			}
+		}
+	};
+
+	const selectAssetGroupHandler = (groupIndex: number) => {
+		if (existingGroup.has(groupIndex)) return;
+
+		let tempSelectedGroup = new Set(selectedGroup);
+		let tempSelectedAsset = new Set(selectedAsset);
+
+		if (selectedGroup.has(groupIndex)) {
+			tempSelectedGroup.delete(groupIndex);
+			tempSelectedAsset.forEach((assetId) => {
+				if ($assetsGroupByDate[groupIndex].find((a) => a.id == assetId)) {
+					tempSelectedAsset.delete(assetId);
+				}
+			});
+		} else {
+			tempSelectedGroup.add(groupIndex);
+			tempSelectedAsset = new Set([
+				...selectedAsset,
+				...$assetsGroupByDate[groupIndex].map((a) => a.id)
+			]);
+		}
+
+		// Remove existed assets in the date group
+		if (groupWithAssetsInAlbum[groupIndex]) {
+			tempSelectedAsset.forEach((assetId) => {
+				if (groupWithAssetsInAlbum[groupIndex].has(assetId)) {
+					tempSelectedAsset.delete(assetId);
+				}
+			});
+		}
+
+		selectedAsset = tempSelectedAsset;
+		selectedGroup = tempSelectedGroup;
+	};
+
+	const addSelectedAssets = async () => {
+		dispatch('create-album', {
+			assets: Array.from(selectedAsset)
+		});
+	};
+
+	/**
+	 * This function is used to scan for existing selected group in the album
+	 * and format it into the form of Record<any, Set<string>> to conditionally render and perform interaction
+	 * relationship between the noneselected assets/groups
+	 * with the existing assets/groups
+	 */
+	const scanForExistingSelectedGroup = () => {
+		if (assetsInAlbum) {
+			// Convert to each assetGroup to set of assetIds
+			const distinctAssetGroup = $assetsGroupByDate.map((assetGroup) => {
+				return new Set(assetGroup.map((asset) => asset.id));
+			});
+
+			// Find the group that contains all existed assets with the same set of assetIds
+			for (const assetInAlbum of assetsInAlbum) {
+				distinctAssetGroup.forEach((group, index) => {
+					if (group.has(assetInAlbum.id)) {
+						groupWithAssetsInAlbum[index] = new Set(groupWithAssetsInAlbum[index] || []).add(
+							assetInAlbum.id
+						);
+					}
+				});
+			}
+
+			Object.keys(groupWithAssetsInAlbum).forEach((key) => {
+				if (distinctAssetGroup[parseInt(key)].size == groupWithAssetsInAlbum[parseInt(key)].size) {
+					existingGroup = existingGroup.add(parseInt(key));
+				}
+			});
+		}
+	};
+</script>
+
+<section
+	transition:fly={{ y: 1000, duration: 200, easing: quintOut }}
+	class="absolute top-0 left-0 w-full h-full  bg-immich-bg z-[200]"
+>
+	<AlbumAppBar on:close-button-click={() => dispatch('go-back')}>
+		<svelte:fragment slot="leading">
+			{#if selectedAsset.size == 0}
+				<p class="text-lg">Add to album</p>
+			{:else}
+				<p class="text-lg">{selectedAsset.size} selected</p>
+			{/if}
+		</svelte:fragment>
+
+		<svelte:fragment slot="trailing">
+			<button
+				disabled={selectedAsset.size === 0}
+				on:click={addSelectedAssets}
+				class="immich-text-button border bg-immich-primary text-gray-50 hover:bg-immich-primary/75 px-6 text-sm disabled:opacity-25 disabled:bg-gray-500 disabled:cursor-not-allowed"
+				><span class="px-2">Done</span></button
+			>
+		</svelte:fragment>
+	</AlbumAppBar>
+
+	<section id="image-grid" class="flex flex-wrap gap-14 mt-[160px] px-20">
+		{#each $assetsGroupByDate as assetsInDateGroup, groupIndex}
+			<!-- Asset Group By Date -->
+			<div class="flex flex-col">
+				<!-- Date group title -->
+				<p class="font-medium text-sm text-immich-fg mb-2 flex place-items-center h-6">
+					<span
+						in:fly={{ x: -24, duration: 200, opacity: 0.5 }}
+						out:fly={{ x: -24, duration: 200 }}
+						class="inline-block px-2 hover:cursor-pointer"
+						on:click={() => selectAssetGroupHandler(groupIndex)}
+					>
+						{#if selectedGroup.has(groupIndex)}
+							<CheckCircle size="24" color="#4250af" />
+						{:else if existingGroup.has(groupIndex)}
+							<CheckCircle size="24" color="#757575" />
+						{:else}
+							<CircleOutline size="24" color="#757575" />
+						{/if}
+					</span>
+
+					{moment(assetsInDateGroup[0].createdAt).format('ddd, MMM DD YYYY')}
+				</p>
+
+				<!-- Image grid -->
+				<div class="flex flex-wrap gap-[2px]">
+					{#each assetsInDateGroup as asset}
+						<ImmichThumbnail
+							{asset}
+							on:click={() => selectAssetHandler(asset.id, groupIndex)}
+							{groupIndex}
+							selected={selectedAsset.has(asset.id)}
+							isExisted={assetsInAlbum.findIndex((a) => a.id == asset.id) != -1}
+						/>
+					{/each}
+				</div>
+			</div>
+		{/each}
+	</section>
+</section>
diff --git a/web/src/lib/components/album-page/user-selection-modal.svelte b/web/src/lib/components/album-page/user-selection-modal.svelte
new file mode 100644
index 0000000000..60cfd83bbf
--- /dev/null
+++ b/web/src/lib/components/album-page/user-selection-modal.svelte
@@ -0,0 +1,117 @@
+<script lang="ts">
+	import { createEventDispatcher, onMount } from 'svelte';
+	import { api, UserResponseDto } from '@api';
+	import BaseModal from '../shared-components/base-modal.svelte';
+	import CircleAvatar from '../shared-components/circle-avatar.svelte';
+
+	export let sharedUsersInAlbum: Set<UserResponseDto>;
+	let users: UserResponseDto[] = [];
+	let selectedUsers: Set<UserResponseDto> = new Set();
+
+	const dispatch = createEventDispatcher();
+
+	onMount(async () => {
+		const { data } = await api.userApi.getAllUsers(false);
+
+		users = data;
+
+		// Remove the existed shared users from the album
+		sharedUsersInAlbum.forEach((sharedUser) => {
+			users = users.filter((user) => user.id !== sharedUser.id);
+		});
+	});
+
+	const selectUser = (user: UserResponseDto) => {
+		const tempSelectedUsers = new Set(selectedUsers);
+
+		if (selectedUsers.has(user)) {
+			tempSelectedUsers.delete(user);
+		} else {
+			tempSelectedUsers.add(user);
+		}
+
+		selectedUsers = tempSelectedUsers;
+	};
+
+	const deselectUser = (user: UserResponseDto) => {
+		const tempSelectedUsers = new Set(selectedUsers);
+
+		tempSelectedUsers.delete(user);
+
+		selectedUsers = tempSelectedUsers;
+	};
+</script>
+
+<BaseModal on:close={() => dispatch('close')}>
+	<svelte:fragment slot="title">
+		<span class="flex gap-2 place-items-center">
+			<img src="/immich-logo.svg" width="24" alt="Immich" />
+			<p class="font-medium text-immich-fg">Invite to album</p>
+		</span>
+	</svelte:fragment>
+
+	<div class="max-h-[400px] overflow-y-auto immich-scrollbar">
+		{#if selectedUsers.size > 0}
+			<div class="flex gap-4 py-2 px-5 overflow-x-auto place-items-center mb-2">
+				<p class="font-medium">To</p>
+
+				{#each Array.from(selectedUsers) as user}
+					<button
+						on:click={() => deselectUser(user)}
+						class="flex gap-1 place-items-center border border-gray-400 rounded-full p-1 hover:bg-gray-200 transition-colors"
+					>
+						<CircleAvatar size={28} {user} />
+						<p class="text-xs font-medium">{user.firstName} {user.lastName}</p>
+					</button>
+				{/each}
+			</div>
+		{/if}
+
+		{#if users.length > 0}
+			<p class="text-xs font-medium px-5">SUGGESTIONS</p>
+
+			<div class="my-4">
+				{#each users as user}
+					<button
+						on:click={() => selectUser(user)}
+						class="w-full flex place-items-center gap-4 py-4 px-5 hover:bg-gray-200  transition-all"
+					>
+						{#if selectedUsers.has(user)}
+							<span
+								class="bg-immich-primary text-white rounded-full w-12 h-12 border flex place-items-center place-content-center text-3xl"
+								>✓</span
+							>
+						{:else}
+							<CircleAvatar {user} />
+						{/if}
+
+						<div class="text-left">
+							<p class="text-immich-fg">
+								{user.firstName}
+								{user.lastName}
+							</p>
+							<p class="text-xs ">
+								{user.email}
+							</p>
+						</div>
+					</button>
+				{/each}
+			</div>
+		{:else}
+			<p class="text-sm px-5">
+				Looks like you have shared this album with all users or you don't have any user to share
+				with.
+			</p>
+		{/if}
+
+		{#if selectedUsers.size > 0}
+			<div class="flex place-content-end p-5 ">
+				<button
+					on:click={() => dispatch('add-user', { selectedUsers })}
+					class="text-white bg-immich-primary px-4 py-2 rounded-lg text-sm font-bold transition-colors hover:bg-immich-primary/75"
+					>Add</button
+				>
+			</div>
+		{/if}
+	</div>
+</BaseModal>
diff --git a/web/src/lib/components/shared-components/base-modal.svelte b/web/src/lib/components/shared-components/base-modal.svelte
new file mode 100644
index 0000000000..40297bfbbe
--- /dev/null
+++ b/web/src/lib/components/shared-components/base-modal.svelte
@@ -0,0 +1,31 @@
+<script lang="ts">
+	import { fly } from 'svelte/transition';
+	import { quintOut } from 'svelte/easing';
+	import Close from 'svelte-material-icons/Close.svelte';
+	import { createEventDispatcher } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+</script>
+
+<div
+	id="immich-modal"
+	transition:fly={{ y: 1000, duration: 200, easing: quintOut }}
+	class="absolute top-0 w-screen h-screen z-[9999] bg-black/50 flex place-items-center place-content-center"
+>
+	<div class="bg-white w-[450px] min-h-[200px] max-h-[500px] rounded-lg shadow-md">
+		<div class="flex justify-between place-items-center p-5">
+			<div>
+				<slot name="title">
+					<p>Modal Title</p>
+				</slot>
+			</div>
+			<button on:click={() => dispatch('close')}>
+				<Close size="24" />
+			</button>
+		</div>
+
+		<div class="mt-4">
+			<slot />
+		</div>
+	</div>
+</div>
diff --git a/web/src/lib/components/shared-components/circle-avatar.svelte b/web/src/lib/components/shared-components/circle-avatar.svelte
index 6afbcdeb59..1fea03910b 100644
--- a/web/src/lib/components/shared-components/circle-avatar.svelte
+++ b/web/src/lib/components/shared-components/circle-avatar.svelte
@@ -1,9 +1,11 @@
 <script lang="ts">
 	import { api, UserResponseDto } from '@api';
-	import { onMount } from 'svelte';
 
 	export let user: UserResponseDto;
 
+	// Avatar Size In Pixel
+	export let size: number = 48;
+
 	const getUserAvatar = async () => {
 		try {
 			const { data } = await api.userApi.getProfileImage(user.id, {
@@ -20,12 +22,18 @@
 </script>
 
 {#await getUserAvatar()}
-	<div class="w-12 h-12 rounded-full bg-immich-primary/25" />
+	<div
+		style:width={`${size}px`}
+		style:height={`${size}px`}
+		class={` rounded-full bg-immich-primary/25`}
+	/>
 {:then data}
 	<img
 		src={data}
 		alt="profile-img"
-		class="inline rounded-full w-12 h-12 object-cover border shadow-md"
+		style:width={`${size}px`}
+		style:height={`${size}px`}
+		class={`inline rounded-full  object-cover border shadow-md`}
 		title={user.email}
 	/>
 {/await}
diff --git a/web/src/lib/components/shared-components/immich-thumbnail.svelte b/web/src/lib/components/shared-components/immich-thumbnail.svelte
index 9f13c3da70..8917ffbe12 100644
--- a/web/src/lib/components/shared-components/immich-thumbnail.svelte
+++ b/web/src/lib/components/shared-components/immich-thumbnail.svelte
@@ -15,6 +15,8 @@
 	export let groupIndex = 0;
 	export let thumbnailSize: number | undefined = undefined;
 	export let format: ThumbnailFormat = ThumbnailFormat.Webp;
+	export let selected: boolean = false;
+	export let isExisted: boolean = false;
 
 	let imageData: string;
 	let videoData: string;
@@ -27,6 +29,7 @@
 	let isThumbnailVideoPlaying = false;
 	let calculateVideoDurationIntervalHandler: NodeJS.Timer;
 	let videoProgress = '00:00';
+	let videoAbortController: AbortController;
 
 	const loadImageData = async () => {
 		if ($session.user) {
@@ -42,52 +45,51 @@
 
 	const loadVideoData = async () => {
 		isThumbnailVideoPlaying = false;
+		videoAbortController = new AbortController();
 
-		if ($session.user) {
-			try {
-				const { data } = await api.assetApi.serveFile(
-					asset.deviceAssetId,
-					asset.deviceId,
-					false,
-					true,
-					{
-						responseType: 'blob'
-					}
-				);
-
-				if (!(data instanceof Blob)) {
-					return;
+		try {
+			const { data } = await api.assetApi.serveFile(
+				asset.deviceAssetId,
+				asset.deviceId,
+				false,
+				true,
+				{
+					responseType: 'blob',
+					signal: videoAbortController.signal
 				}
+			);
 
-				videoData = URL.createObjectURL(data);
+			if (!(data instanceof Blob)) {
+				return;
+			}
 
-				videoPlayerNode.src = videoData;
-				// videoPlayerNode.src = videoData + '#t=0,5';
+			videoData = URL.createObjectURL(data);
 
-				videoPlayerNode.load();
+			videoPlayerNode.src = videoData;
 
-				videoPlayerNode.onloadeddata = () => {
-					console.log('first frame load');
-				};
+			videoPlayerNode.load();
 
-				videoPlayerNode.oncanplaythrough = () => {
-					console.log('can play through');
-				};
+			videoPlayerNode.onloadeddata = () => {
+				console.log('first frame load');
+			};
 
-				videoPlayerNode.oncanplay = () => {
-					console.log('can play');
-					videoPlayerNode.muted = true;
-					videoPlayerNode.play();
+			videoPlayerNode.oncanplaythrough = () => {
+				console.log('can play through');
+			};
 
-					isThumbnailVideoPlaying = true;
-					calculateVideoDurationIntervalHandler = setInterval(() => {
-						videoProgress = getVideoDurationInString(Math.round(videoPlayerNode.currentTime));
-					}, 1000);
-				};
+			videoPlayerNode.oncanplay = () => {
+				console.log('can play');
+				videoPlayerNode.muted = true;
+				videoPlayerNode.play();
 
-				return videoData;
-			} catch (e) {}
-		}
+				isThumbnailVideoPlaying = true;
+				calculateVideoDurationIntervalHandler = setInterval(() => {
+					videoProgress = getVideoDurationInString(Math.round(videoPlayerNode.currentTime));
+				}, 1000);
+			};
+
+			return videoData;
+		} catch (e) {}
 	};
 
 	const getVideoDurationInString = (currentTime: number) => {
@@ -137,6 +139,11 @@
 
 	const handleMouseLeaveThumbnail = () => {
 		mouseOver = false;
+
+		// Stop XHR download of video
+		videoAbortController?.abort();
+
+		// Stop video playback
 		URL.revokeObjectURL(videoData);
 
 		clearInterval(calculateVideoDurationIntervalHandler);
@@ -144,28 +151,59 @@
 		isThumbnailVideoPlaying = false;
 		videoProgress = '00:00';
 	};
+
+	$: getThumbnailBorderStyle = () => {
+		if (selected) {
+			return 'border-[20px] border-immich-primary/20';
+		} else if (isExisted) {
+			return 'border-[20px] border-gray-300';
+		} else {
+			return '';
+		}
+	};
+
+	$: getOverlaySelectorIconStyle = () => {
+		if (selected || isExisted) {
+			return '';
+		} else {
+			return 'bg-gradient-to-b from-gray-800/50';
+		}
+	};
+	const thumbnailClickedHandler = () => {
+		if (!isExisted) {
+			dispatch('click', { assetId: asset.id, deviceId: asset.deviceId });
+		}
+	};
 </script>
 
 <IntersectionObserver once={true} let:intersecting>
 	<div
 		style:width={`${thumbnailSize}px`}
 		style:height={`${thumbnailSize}px`}
-		class={`bg-gray-100 relative hover:cursor-pointer ${getSize()}`}
+		class={`bg-gray-100 relative  ${getSize()} ${
+			isExisted ? 'cursor-not-allowed' : 'hover:cursor-pointer'
+		}`}
 		on:mouseenter={handleMouseOverThumbnail}
 		on:mouseleave={handleMouseLeaveThumbnail}
-		on:click={() => dispatch('viewAsset', { assetId: asset.id, deviceId: asset.deviceId })}
+		on:click={thumbnailClickedHandler}
 	>
-		{#if mouseOver}
+		{#if mouseOver || selected || isExisted}
 			<div
 				in:fade={{ duration: 200 }}
-				class="w-full  bg-gradient-to-b from-gray-800/50 via-white/0 to-white/0 absolute p-2  z-10"
+				class={`w-full ${getOverlaySelectorIconStyle()} via-white/0 to-white/0 absolute p-2  z-10`}
 			>
 				<div
 					on:mouseenter={() => (mouseOverIcon = true)}
 					on:mouseleave={() => (mouseOverIcon = false)}
 					class="inline-block"
 				>
-					<CheckCircle size="24" color={mouseOverIcon ? 'white' : '#d8dadb'} />
+					{#if selected}
+						<CheckCircle size="24" color="#4250af" />
+					{:else if isExisted}
+						<CheckCircle size="24" color="#252525" />
+					{:else}
+						<CheckCircle size="24" color={mouseOverIcon ? 'white' : '#d8dadb'} />
+					{/if}
 				</div>
 			</div>
 		{/if}
@@ -209,7 +247,7 @@
 				<div
 					style:width={`${thumbnailSize}px`}
 					style:height={`${thumbnailSize}px`}
-					class={`bg-immich-primary/10 ${getSize()} flex place-items-center place-content-center`}
+					class={`bg-immich-primary/10 ${getSize()} flex place-items-center place-content-center `}
 				>
 					...
 				</div>
@@ -220,7 +258,7 @@
 					in:fade={{ duration: 250 }}
 					src={imageData}
 					alt={asset.id}
-					class={`object-cover ${getSize()} transition-all duration-100 z-0`}
+					class={`object-cover ${getSize()} transition-all duration-100 z-0 ${getThumbnailBorderStyle()}`}
 					loading="lazy"
 				/>
 			{/await}
diff --git a/web/src/lib/components/shared-components/navigation-bar.svelte b/web/src/lib/components/shared-components/navigation-bar.svelte
index 31582efc44..eab2a83436 100644
--- a/web/src/lib/components/shared-components/navigation-bar.svelte
+++ b/web/src/lib/components/shared-components/navigation-bar.svelte
@@ -61,14 +61,17 @@
 			<h1 class="font-immich-title text-2xl text-immich-primary">IMMICH</h1>
 		</a>
 		<div class="flex-1 ml-24">
-			<input class="w-[50%] border rounded-md bg-gray-200 px-8 py-4" placeholder="Search - Coming soon" />
+			<input
+				class="w-[50%] border rounded-md bg-gray-200 px-8 py-4"
+				placeholder="Search - Coming soon"
+			/>
 		</div>
 		<section class="flex gap-4 place-items-center">
 			{#if $page.url.pathname !== '/admin'}
 				<button
 					in:fly={{ x: 50, duration: 250 }}
 					on:click={() => dispatch('uploadClicked')}
-					class="flex place-items-center place-content-center gap-2 hover:bg-immich-primary/5 p-2 rounded-lg font-medium"
+					class="immich-text-button"
 				>
 					<TrayArrowUp size="20" />
 					<span> Upload </span>
@@ -158,7 +161,9 @@
 			</div>
 
 			<div class="mb-6">
-				<button class="border rounded-3xl px-6 py-2 hover:bg-gray-50" on:click={logOut}>Sign Out</button>
+				<button class="border rounded-3xl px-6 py-2 hover:bg-gray-50" on:click={logOut}
+					>Sign Out</button
+				>
 			</div>
 		</div>
 	{/if}
diff --git a/web/src/lib/components/shared-components/upload-panel.svelte b/web/src/lib/components/shared-components/upload-panel.svelte
index d959ff7063..038077dff8 100644
--- a/web/src/lib/components/shared-components/upload-panel.svelte
+++ b/web/src/lib/components/shared-components/upload-panel.svelte
@@ -79,9 +79,7 @@
 		isUploading = value;
 
 		if (isUploading == false) {
-			if ($session.user) {
-				getAssetsInfo($session.user.accessToken);
-			}
+			getAssetsInfo();
 		}
 	});
 </script>
@@ -107,7 +105,7 @@
 					</button>
 				</div>
 
-				<div id="upload-item-list" class="max-h-[400px] overflow-y-auto pr-2 rounded-lg">
+				<div class="max-h-[400px] overflow-y-auto pr-2 rounded-lg immich-scrollbar">
 					{#each $uploadAssetsStore as uploadAsset}
 						<div
 							in:fade={{ duration: 250 }}
@@ -136,7 +134,9 @@
 								<input
 									disabled
 									class="bg-gray-100 border w-full p-1 rounded-md text-[10px] px-2"
-									value={`[${getSizeInHumanReadableFormat(uploadAsset.file.size)}] ${uploadAsset.file.name}`}
+									value={`[${getSizeInHumanReadableFormat(uploadAsset.file.size)}] ${
+										uploadAsset.file.name
+									}`}
 								/>
 
 								<div class="w-full bg-gray-300 h-[15px] rounded-md mt-[5px] text-white relative">
@@ -144,7 +144,9 @@
 										class="bg-immich-primary h-[15px] rounded-md transition-all"
 										style={`width: ${uploadAsset.progress}%`}
 									/>
-									<p class="absolute h-full w-full text-center top-0 text-[10px] ">{uploadAsset.progress}/100</p>
+									<p class="absolute h-full w-full text-center top-0 text-[10px] ">
+										{uploadAsset.progress}/100
+									</p>
 								</div>
 							</div>
 						</div>
@@ -173,28 +175,3 @@
 		{/if}
 	</div>
 {/if}
-
-<style>
-	/* width */
-	#upload-item-list::-webkit-scrollbar {
-		width: 5px;
-	}
-
-	/* Track */
-	#upload-item-list::-webkit-scrollbar-track {
-		background: #f1f1f1;
-		border-radius: 16px;
-	}
-
-	/* Handle */
-	#upload-item-list::-webkit-scrollbar-thumb {
-		background: #4250af68;
-		border-radius: 16px;
-	}
-
-	/* Handle on hover */
-	#upload-item-list::-webkit-scrollbar-thumb:hover {
-		background: #4250afad;
-		border-radius: 16px;
-	}
-</style>
diff --git a/web/src/routes/albums/index.svelte b/web/src/routes/albums/index.svelte
index 121f597d14..e15547b7d5 100644
--- a/web/src/routes/albums/index.svelte
+++ b/web/src/routes/albums/index.svelte
@@ -17,10 +17,10 @@
 			};
 		}
 
-		let allAlbums: AlbumResponseDto[] = [];
+		let albums: AlbumResponseDto[] = [];
 		try {
 			const { data } = await api.albumApi.getAllAlbums();
-			allAlbums = data;
+			albums = data;
 		} catch (e) {
 			console.log('Error [getAllAlbums] ', e);
 		}
@@ -29,7 +29,7 @@
 			status: 200,
 			props: {
 				user: session.user,
-				allAlbums: allAlbums
+				albums: albums
 			}
 		};
 	};
@@ -38,12 +38,47 @@
 <script lang="ts">
 	import AlbumCard from '$lib/components/album-page/album-card.svelte';
 	import { goto } from '$app/navigation';
+	import { onMount } from 'svelte';
 
 	export let user: ImmichUser;
-	export let allAlbums: AlbumResponseDto[];
+	export let albums: AlbumResponseDto[];
 
-	const showAlbum = (event: CustomEvent) => {
-		goto('/albums/' + event.detail.id);
+	onMount(async () => {
+		const { data } = await api.albumApi.getAllAlbums();
+		albums = data;
+
+		// Delete album that has no photos and is named 'Untitled'
+		for (const album of albums) {
+			if (album.albumName === 'Untitled' && album.assets.length === 0) {
+				const isDeleted = await deleteAlbum(album);
+
+				if (isDeleted) {
+					albums = albums.filter((a) => a.id !== album.id);
+				}
+			}
+		}
+	});
+
+	const createAlbum = async () => {
+		try {
+			const { data: newAlbum } = await api.albumApi.createAlbum({
+				albumName: 'Untitled'
+			});
+
+			goto('/albums/' + newAlbum.id);
+		} catch (e) {
+			console.log('Error [createAlbum] ', e);
+		}
+	};
+
+	const deleteAlbum = async (album: AlbumResponseDto) => {
+		try {
+			await api.albumApi.deleteAlbum(album.id);
+			return true;
+		} catch (e) {
+			console.log('Error [deleteAlbum] ', e);
+			return false;
+		}
 	};
 </script>
 
@@ -68,9 +103,7 @@
 				</div>
 
 				<div>
-					<button
-						class="flex place-items-center gap-1 text-sm hover:bg-immich-primary/5 p-2 rounded-lg font-medium hover:text-gray-700"
-					>
+					<button on:click={createAlbum} class="immich-text-button text-sm">
 						<span>
 							<PlusBoxOutline size="18" />
 						</span>
@@ -85,8 +118,10 @@
 
 			<!-- Album Card -->
 			<div class="flex flex-wrap gap-8">
-				{#each allAlbums as album}
-					<a sveltekit:prefetch href={`albums/${album.id}`}> <AlbumCard {album} /></a>
+				{#each albums as album}
+					<a sveltekit:prefetch href={`albums/${album.id}`}>
+						<AlbumCard {album} />
+					</a>
 				{/each}
 			</div>
 		</section>
diff --git a/web/src/routes/photos/index.svelte b/web/src/routes/photos/index.svelte
index 4ca3797823..e2cc935c84 100644
--- a/web/src/routes/photos/index.svelte
+++ b/web/src/routes/photos/index.svelte
@@ -173,7 +173,7 @@
 								<ImmichThumbnail
 									{asset}
 									on:mouseEvent={thumbnailMouseEventHandler}
-									on:viewAsset={viewAssetHandler}
+									on:click={viewAssetHandler}
 									{groupIndex}
 								/>
 							{/each}
diff --git a/web/src/routes/sharing/index.svelte b/web/src/routes/sharing/index.svelte
index 6013df5cac..4cfbf55fb3 100644
--- a/web/src/routes/sharing/index.svelte
+++ b/web/src/routes/sharing/index.svelte
@@ -28,6 +28,28 @@
 			}
 		};
 	};
+
+	const createSharedAlbum = async () => {
+		try {
+			const { data: newAlbum } = await api.albumApi.createAlbum({
+				albumName: 'Untitled'
+			});
+
+			goto('/albums/' + newAlbum.id);
+		} catch (e) {
+			console.log('Error [createAlbum] ', e);
+		}
+	};
+
+	const deleteAlbum = async (album: AlbumResponseDto) => {
+		try {
+			await api.albumApi.deleteAlbum(album.id);
+			return true;
+		} catch (e) {
+			console.log('Error [deleteAlbum] ', e);
+			return false;
+		}
+	};
 </script>
 
 <script lang="ts">
@@ -36,6 +58,7 @@
 	import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
 	import AlbumCard from '$lib/components/album-page/album-card.svelte';
 	import SharedAlbumListTile from '$lib/components/sharing-page/shared-album-list-tile.svelte';
+	import { goto } from '$app/navigation';
 
 	export let user: UserResponseDto;
 	export let sharedAlbums: AlbumResponseDto[];
@@ -62,6 +85,7 @@
 
 				<div>
 					<button
+						on:click={createSharedAlbum}
 						class="flex place-items-center gap-1 text-sm hover:bg-immich-primary/5 p-2 rounded-lg font-medium hover:text-gray-700"
 					>
 						<span>